
1.前言
- 最近隨著項(xiàng)目迭代業(yè)務(wù)與使用的用戶逐漸增多,陸續(xù)的出現(xiàn)了一些性能或者BUG的問題(例如:1.某些功能執(zhí)行響應(yīng)慢;2.按鍵復(fù)電;等等)。
- 根據(jù)上述部分性能問題,提出相對應(yīng)的解決方案:
- 某些功能執(zhí)行響應(yīng)慢 ---> 性能監(jiān)控
- 按鍵復(fù)點(diǎn) ---> 按鍵防抖
- 上述2個解決方案的最終實(shí)現(xiàn)原理:就是在執(zhí)行函數(shù)的前或者后臺添加對應(yīng)的邏輯處理。
- 選擇實(shí)現(xiàn)方式:
1.直接在方法前后添加對應(yīng)的邏輯。
2.使用靜態(tài)/動態(tài)代理。
3.使用AOP。 - 1與2方式:當(dāng)涉及修改的方法或者類較多時(shí),實(shí)現(xiàn)比較復(fù)雜,而且侵入性大,更不方便后期擴(kuò)展與維護(hù)。
- 第3方式:侵入性很少;實(shí)現(xiàn)簡單;解耦性很大;易維護(hù)。而這種方式是如何使用?原理是什么?。
- 今天就為大家揭開
AOP的面紗。 - 文章中實(shí)例 linhaojian的Github
2.介紹
2.1 AOP 是什么
-
AOP為Aspect Oriented Programming的縮寫。意為:面向切面編程。
2.2 AOP 作用
- 通過預(yù)編譯方式和運(yùn)行期動態(tài)代理,來改變原來執(zhí)行結(jié)果的技術(shù)。
2.3 AOP 特點(diǎn)
- 使原執(zhí)行邏輯與改變執(zhí)行邏輯解耦。
- 對原代碼侵入性少。
- 容易擴(kuò)展輔助功能(例如:日志)。
2.4 Android 中應(yīng)用
- 日志
- 持久化
- 性能監(jiān)控
- 數(shù)據(jù)校驗(yàn)
- 緩存
- 按鈕防抖
- 其他更多
2.5 AOP 術(shù)語
- 通知、增強(qiáng)處理(Advice):就是你想要的功能,也就是上面說的日志、耗時(shí)計(jì)算等。
- 連接點(diǎn)(JoinPoint):允許你通知(Advice)的地方,那可就真多了,基本每個方法的前、后(兩者都有也行),或拋出異常是時(shí)都可以是連接點(diǎn)(spring只支持方法連接點(diǎn))。AspectJ還可以讓你在構(gòu)造器或?qū)傩宰⑷霑r(shí)都行,不過一般情況下不會這么做,只要記住,和方法有關(guān)的前前后后都是連接點(diǎn)。
- 切入點(diǎn)(Pointcut):上面說的連接點(diǎn)的基礎(chǔ)上,來定義切入點(diǎn),你的一個類里,有15個方法,那就有十幾個連接點(diǎn)了對吧,但是你并不想在所有方法附件都使用通知(使用叫織入,下面再說),你只是想讓其中幾個,在調(diào)用這幾個方法之前、之后或者拋出異常時(shí)干點(diǎn)什么,那么就用切入點(diǎn)來定義這幾個方法,讓切點(diǎn)來篩選連接點(diǎn),選中那幾個你想要的方法。
- 切面(Aspect):切面是通知和切入點(diǎn)的結(jié)合?,F(xiàn)在發(fā)現(xiàn)了吧,沒連接點(diǎn)什么事,連接點(diǎn)就是為了讓你好理解切點(diǎn)搞出來的,明白這個概念就行了。通知說明了干什么和什么時(shí)候干(什么時(shí)候通過before,after,around等AOP注解就能知道),而切入點(diǎn)說明了在哪干(指定到底是哪個方法),這就是一個完整的切面定義。
- 織入(weaving): 把切面應(yīng)用到目標(biāo)對象來創(chuàng)建新的代理對象的過程。
2.6 AOP 注解
- @Aspect:聲明切面,標(biāo)記類
- @Pointcut(切點(diǎn)表達(dá)式):定義切點(diǎn),標(biāo)記方法
- @Before(切點(diǎn)表達(dá)式):前置通知,切點(diǎn)之前執(zhí)行
- @Around(切點(diǎn)表達(dá)式):環(huán)繞通知,切點(diǎn)前后執(zhí)行
- @After(切點(diǎn)表達(dá)式):后置通知,切點(diǎn)之后執(zhí)行
- @AfterReturning(切點(diǎn)表達(dá)式):返回通知,切點(diǎn)方法返回結(jié)果之后執(zhí)行
- @AfterThrowing(切點(diǎn)表達(dá)式):異常通知,切點(diǎn)拋出異常時(shí)執(zhí)行
@Pointcut、@Before、@Around、@After、@AfterReturning、@AfterThrowing需要在切面類中使用,即在使用@Aspect的類中。
2.7 切點(diǎn)表達(dá)式

AspectJ語法1.png

AspectJ語法2.png

AspectJ語法3.png
- 示例:
// 返回類型:* 類名+函數(shù)名:com.lhj.test_apt..*ck(..)
// com.lhj.test_apt..*ck(..) :意思是com.lhj.test_apt包下所有類中以ck結(jié)尾的函數(shù)
@Pointcut("execution(* com.lhj.test_apt..*ck(..))")
3.使用
- 3.1 項(xiàng)目根目錄(build.gradle)的dependencies中,添加如下代碼:
dependencies {
classpath 'org.aspectj:aspectjtools:1.8.13' // add
classpath 'org.aspectj:aspectjrt:1.8.13' // add
}
- 3.2 在主項(xiàng)目或者需要引用的library的build.gradle中,示例如下:
apply plugin: 'com.android.library'
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 17
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// aop
implementation 'org.aspectj:aspectjrt:1.8.13'
}
project.android.libraryVariants.all { variant ->
JavaCompile javaCompile = variant.javaCompile
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.8",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", project.android.bootClasspath.join(
File.pathSeparator)]
MessageHandler handler = new MessageHandler(true);
new Main().run(args, handler)
def log = project.logger
for (IMessage message : handler.getMessages(null, true)) {
switch (message.getKind()) {
case IMessage.ABORT:
case IMessage.ERROR:
case IMessage.FAIL:
log.error message.message, message.thrown
break;
case IMessage.WARNING:
case IMessage.INFO:
log.info message.message, message.thrown
break;
case IMessage.DEBUG:
log.debug message.message, message.thrown
break;
}
}
}
}
- 3.3 定義切面類(例如:在源碼一些方法上添加額外日志打印):
/**
* 添加切面注解
*/
@Aspect
public class AopTest {
private static final String TAG = "linhaojian";
/**
* 定義切入點(diǎn)(定義那些類或者方法需要改變)
*/
@Pointcut("execution(* com.lhj.test_apt..*ck(..))")
public void pointcut1() {
}
/**
* 使用注解方式,定義注解
*/
@Pointcut("execution(@com.lhj.test_apt.DebugLog * *ck(..))")
public void pointcut() {
}
/**
* 前置通知,切點(diǎn)之前執(zhí)行
* @param point
*/
@Before("pointcut()")
public void logBefore(JoinPoint point){
Log.e(TAG,"logBefore");
}
/**
* 環(huán)繞通知,切點(diǎn)前后執(zhí)行
* @param joinPoint
* @throws Throwable
*/
@Around("pointcut()")
public void logAround(ProceedingJoinPoint joinPoint) throws Throwable {
Log.e(TAG,"logAround");
// 1.執(zhí)行切點(diǎn)函數(shù)(如果不調(diào)用該方法,切點(diǎn)函數(shù)不被執(zhí)行)
joinPoint.proceed();
}
/**
* 后置通知,切點(diǎn)之后執(zhí)行
* @throws Throwable
*/
@After("pointcut()")
public void logAfter(JoinPoint point){
Log.e(TAG,"logAfter");
}
/**
* 返回通知,切點(diǎn)方法返回結(jié)果之后執(zhí)行
* @throws Throwable
*/
@AfterReturning("pointcut()")
public void logAfterReturning(JoinPoint point, Object returnValue){
Log.e(TAG,"logAfterReturning ");
}
/**
* 異常通知,切點(diǎn)拋出異常時(shí)執(zhí)行
* @throws Throwable
*/
@AfterThrowing(value = "pointcut()",throwing = "ex")
public void logAfterThrowing(Throwable ex){
Log.e(TAG,"logAfterThrowing : "+ex.getMessage());
}
}
- 注意:注釋1中,如果使用了@Around注釋,記得調(diào)用joinPoint.proceed(),不然切點(diǎn)的函數(shù)是不會被調(diào)用的。
- 3.4 切點(diǎn)的使用方式:
- 無侵入式:@Pointcut定義不包含注解時(shí),就能實(shí)現(xiàn)源代碼零修改的改變。
- 低侵入式:@Pointcut定義包含注解時(shí),就需要在源代碼的函數(shù)中添加相應(yīng)的注解,如下代碼塊。
public class AopActivity extends AppCompatActivity {
private Button button;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aop);
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
click();
}
});
}
@DebugLog
public void click(){
Log.e("linhaojian","onClick()");
}
}
- DebugLog注解代碼
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DebugLog {
}
-
3.5 運(yùn)行結(jié)果
執(zhí)行結(jié)果.png
4.原理
- 在分析其原理前,我們先看看包含切點(diǎn)功能的類,編譯之后的class文件內(nèi)容:
public class AopActivity extends AppCompatActivity {
private Button button;
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(layout.aop);
this.button = (Button)this.findViewById(id.button);
this.button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
AopActivity.this.click();
}
});
}
@DebugLog
public void click() {
// 1
JoinPoint var1 = Factory.makeJP(ajc$tjp_0, this, this);
try {
try {
// 2
AopTest.aspectOf().logBefore(var1);
// 3
click_aroundBody1$advice(this, var1, AopTest.aspectOf(), (ProceedingJoinPoint)var1);
} catch (Throwable var4) {
AopTest.aspectOf().logAfter(var1);
throw var4;
}
AopTest.aspectOf().logAfter(var1);
AopTest.aspectOf().logAfterReturning(var1, (Object)null);
} catch (Throwable var5) {
AopTest.aspectOf().logAfterThrowing(var5);
throw var5;
}
}
}
@Aspect
public class AopTest {
//...
public static AopTest aspectOf() {
if (ajc$perSingletonInstance == null) {
throw new NoAspectBoundException("com.lhj.test_apt.AopTest", ajc$initFailureCause);
} else {
return ajc$perSingletonInstance;
}
}
public static boolean hasAspect() {
return ajc$perSingletonInstance != null;
}
static {
try {
// 4
ajc$postClinit();
} catch (Throwable var1) {
ajc$initFailureCause = var1;
}
}
}
- 從上面編譯后的class文件,可以看很清楚編譯時(shí)改變了原方法的執(zhí)行流程或結(jié)果,添加了切面類中定義的一些額外執(zhí)行邏輯(例如:logBefore、logAfter);
- 注釋1:通過工廠類,創(chuàng)建ProceedingJoinPoint的實(shí)例;
- 注釋2:AopTest.aspectOf()代表獲取AopTest的單例(單例時(shí)在注釋4中通過static塊初始化);
- 注釋3:其實(shí)就是將原方法的執(zhí)行內(nèi)容封裝至ProceedingJoinPoint,使它們之間關(guān)聯(lián)。
5.總結(jié)
- 到此,
AOP就結(jié)束完畢。 - 如果喜歡我的分享,可以點(diǎn)擊 關(guān)注 或者 贊,你們支持是我分享的最大動力 。
- linhaojian的Github
歡迎關(guān)注linhaojian_CSDN博客或者linhaojian_簡書!
不定期分享關(guān)于安卓開發(fā)的干貨。
寫技術(shù)文章初心
- 技術(shù)知識積累
- 技術(shù)知識鞏固
- 技術(shù)知識分享
- 技術(shù)知識交流
