@TryLoveCatch
2022-05-07T08:27:38.000000Z
字数 21025
阅读 3659
Android知识体系
AspectJ in Android (一),AspectJ 基础概念
AspectJ in Android (二),AspectJ 语法
Android中AOP实践之三AspectJ解析篇
AspectJ 是使用最为广泛的 AOP 实现方案,适用于 Java 平台,官网地址:http://www.eclipse.org/aspectj/ 。
AspectJ 是在静态织入代码,即在编译期注入代码的。
AspectJ 提供了一套全新的语法实现,完全兼容 Java(跟 Java 之间的区别,只是多了一些关键词而已)。同时,还提供了纯 Java 语言的实现,通过注解的方式,完成代码编织的功能。因此我们在使用 AspectJ 的时候有以下两种方式:
在 Android Studio 上一般使用注解的方式使用 AspectJ,因为 Android Studio 没有 AspectJ 插件,无法识别 AspectJ 的语法。
把下面的一个例子拿上来,我们来理解一下这些概念:
class Cat {public void eat() {...}}class A {public void test() {new Cat().eat();}}@Aspectpublic class Test {@Before("call(* Cat.eat())")public void testCall(JoinPoint joinPoint) {// ....System.out.println("1111")}@After("execution(* Cat.eat())")public void testExecution(JoinPoint joinPoint) {// ....}}
我们来看testCall():
execution(* Cat.eat())我们再来看testExecution():
call(* Cat.eat())new Cat().eat()new Cat().eat()之后,打印“1111”Join Point 表示连接点,即 AOP 可织入代码的点,下表列出了 AspectJ 的所有连接点:
| Join Point | 说明 |
|---|---|
| Method call | 方法被调用 |
| Method execution | 方法执行 |
| Constructor call | 构造函数被调用 |
| Constructor execution | 构造函数执行 |
| Field get | 读取属性 |
| Field set | 写入属性 |
| Pre-initialization | 与构造函数有关,很少用到 |
| Initialization | 与构造函数有关,很少用到 |
| Static initialization | static 块初始化 |
| Handler | 异常处理 |
| Advice execution | 所有 Advice 执行 |
个人感觉Join Point是能力,就是Aspectj有那些功能
Pointcuts 是具体的切入点,可以确定具体织入代码的地方,基本的 Pointcuts 是和 Join Point 相对应的。
| Join Point | Pointcuts |
|---|---|
| Method call | call(MethodPattern) |
| Method execution | execution(MethodPattern) |
| Constructor call | call(ConstructorPattern) |
| Constructor execution | execution(ConstructorPattern) |
| Field get | get(FieldPattern) |
| Field set | set(FieldPattern) |
| Pre-initialization | initialization(ConstructorPattern) |
| Initialization | preinitialization(ConstructorPattern) |
| Static initialization | staticinitialization(TypePattern) |
| Handler | handler(TypePattern) |
| Advice execution | adviceexcution() |
除了上面与 Join Point 对应的选择外,Pointcuts 还有其他选择方法:
| Pointcuts | 说明 |
|---|---|
| within(TypePattern) | 符合 TypePattern 的代码中的 Join Point |
| withincode(MethodPattern) | 在某些方法中的 Join Point |
| withincode(ConstructorPattern) | 在某些构造函数中的 Join Point |
| cflow(Pointcut) | Pointcut 选择出的切入点 P 的控制流中的所有 Join Point,包括 P 本身 |
| cflowbelow(Pointcut) | Pointcut 选择出的切入点 P 的控制流中的所有 Join Point,不包括 P 本身 |
| this(Type or Id) | Join Point 所属的 this 对象是否 instanceOf Type 或者 Id 的类型 |
| target(Type or Id) | Join Point 所在的对象(例如 call 或 execution 操作符应用的对象)是否 instanceOf Type 或者 Id 的类型 |
| args(Type or Id, …) | 方法或构造函数参数的类型 |
| if(BooleanExpression) | 满足表达式的 Join Point,表达式只能使用静态属性、Pointcuts 或 Advice 暴露的参数、thisJoinPoint 对象 |
Pointcut 表达式还可以 !、&&、|| 来组合
| 组合 | 说明 |
|---|---|
| !Pointcut | 选取不符合 Pointcut 的 Join Point |
| Pointcut0 && Pointcut1 | 选取符合 Pointcut0 和 Pointcut1 的 Join Point |
| Pointcut0 || Pointcut1 | 选取符合 Pointcut0 或 Pointcut1 的 Join Point |
上面 Pointcuts 的语法中涉及到一些 Pattern,下面是这些 Pattern 的规则,[]里的内容是可选的:
| Pattern | 规则 |
|---|---|
| MethodPattern | [!] [@Annotation] [public,protected,private] [static] [final] 返回值类型 [类名.]方法名(参数类型列表) [throws 异常类型] |
| ConstructorPattern | [!] [@Annotation] [public,protected,private] [final] [类名.]new(参数类型列表) [throws 异常类型] |
| FieldPattern | [!] [@Annotation] [public,protected,private] [static] [final] 属性类型 [类名.]属性名 |
类型匹配的通配符
| 通配符 | 说明 |
|---|---|
| * | 匹配任何数量字符 |
| .. | 匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数 |
| + | 匹配指定类型的子类型;仅能作为后缀放在类型模式后边 |
public interface Waiter {void greetTo(String name);void serveTo(String name);}public class NaiveWaiter implements Waiter {@Overridepublic void greetTo(String name) {System.out.println("NaiveWaiter:greet to " + name + "...");}@Overridepublic void serveTo(String name) {System.out.println("NaiveWaiter:serving to " + name + "...");}public void smile(String clientName,int times){System.out.println("NaiveWaiter:smile to "+clientName+ times+"times...");}}public class NaughtyWaiter implements Waiter {public void greetTo(String clientName) {System.out.println("NaughtyWaiter:greet to " + clientName + "...");}public void serveTo(String clientName) {System.out.println("NaughtyWaiter:serving " + clientName + "...");}public void joke(String clientName, int times) {System.out.println("NaughtyWaiter:play " + times + " jokes to " + clientName + "...");}private void privateMethod() {}}
spring AspectJ的Execution表达式-备忘笔记(转)
| 例子 | 说明 |
|---|---|
| 通过方法签名定义切点 | |
| execution(public * *(..)) | 匹配所有目标类的public方法,不会匹配NaughtyWaiter的privateMethod() |
| execution(* *(..) throws Exception) | 匹配所有抛出Exception的方法。 |
| execution(* *To(..)) | 匹配目标类所有以To为后缀的方法。它匹配NaiveWaiter和NaughtyWaiter的greetTo()和serveTo()方法。第一个*代表返回类型,而*To代表任意以To为后缀的方法 |
| 通过类定义切点 | |
| execution(* com.baobaotao.Waiter.*(..)) | 匹配Waiter接口的所有方法,它匹配NaiveWaiter和NaughtyWaiter类的greetTo()和serveTo()方法。第一个代表返回任意类型,com.baobaotao.Waiter.代表Waiter接口中的所有方法 |
| execution(* com.baobaotao.Waiter+.*(..)) | 匹 配Waiter接口及其所有实现类的方法,它不但匹配NaiveWaiter和NaughtyWaiter类的greetTo()和serveTo()这 两个Waiter接口定义的方法,同时还匹配NaiveWaiter#smile()和NaughtyWaiter#joke()这两个不在Waiter 接口中定义的方法。 |
| 通过类包定义切点 | 在类名模式串中,“.”表示包下的所有类,而“..”表示包、子孙包下的所有类 |
| execution(* com.baobaotao.*(..)) | 匹配com.baobaotao包下所有类的所有方法; |
| execution(* com.baobaotao..*(..)) | 匹 配com.baobaotao包、子孙包下所有类的所有方法,以及包下接口的实现类。“..”出现在类名中时,后面必须跟“*”,表示包、子孙包下的所有类; |
| execution(* com..*.Dao.find(..)) | 匹配包名前缀为com的任何包下类名后缀为Dao的方法,方法名必须以find为前缀。如com.baobaotao.UserDao#findByUserId()、com.baobaotao.dao.ForumDao#findById()的方法都匹配切点。 |
| 通过方法入参定义切点 | 切点表达式中方法入参部分比较复杂,可以使用“”和“ ..”通配符,其中“”表示任意类型的参数,而“..”表示任意类型参数且参数个数不限。 |
| execution(* joke(String,int))) | 匹 配joke(String,int)方法,且joke()方法的第一个入参是String,第二个入参是int。它匹配 NaughtyWaiter#joke(String,int)方法。如果方法中的入参类型是java.lang包下的类,可以直接使用类名,否则必须使用全限定类名,如joke(java.util.List,int) |
| execution(* joke(String,*))) | 匹 配目标类中的joke()方法,该方法第一个入参为String,第二个入参可以是任意类型,如joke(String s1,String s2)和joke(String s1,double d2)都匹配,但joke(String s1,double d2,String s3)则不匹配; |
| execution(* joke(String,..))) | 匹配目标类中的joke()方法,该方法第 一个入参为String,后面可以有任意个入参且入参类型不限,如joke(String s1)、joke(String s1,String s2)和joke(String s1,double d2,String s3)都匹配。 |
| execution(* joke(Object+))) | 匹 配目标类中的joke()方法,方法拥有一个入参,且入参是Object类型或该类的子类。它匹配joke(String s1)和joke(Client c)。如果我们定义的切点是execution(* joke(Object)),则只匹配joke(Object object)而不匹配joke(String cc)或joke(Client c)。 |
| 通过构造函数定义切点 | |
| execution(@com.logaop.annotation.Log *.new(..)) | 被注解Log修饰的所有构造函数,这个比较特殊 |
call(@retrofit2.http.GET public com.johnny.core.http..(..)) – ‘com.johnny.core.http’开头的包下面的所有 GET 方法调用时
call(android.support.v4.app.Fragment+.new(..)) – support 包中的 Fragment 及其子类的构造函数调用时
@Before("call(* *.*(..)) && this(foo)")public void callFromFoo(Foo foo) {Log.d(TAG, "call from Foo:" + foo);}
代表在Foo里面调用的任何方法
我们来看一个例子:
@Aspectclass Test {@Pointcut("execution(void Foo.foo(..)")public void executFoo() {}@Pointcut("executFoo() && cflowbelow(executFoo()) && target(foo) && args(i)")public void loopExecutFoo(Foo foo, int i) {}}
举个例子:
class Cat {public void eat() {...}}class A {public void test() {new Cat().eat();}}
new Cat().eat()这一行target() 与 this() 很容易混淆,我们有必要强调一下:
简单地说就是,PointcutA 选取的是 methodA,那么 target 就是 methodA() 这个方法的对象,而 this 就是 methodA 被调用时所在类的对象。
我们来举个例子:
class Cat {public void eat() {...}}public class TargetThisActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_target_this);eat();}public void eat(){new Cat().eat();}}@Before("call(* *..eat(..)) && target(com.xxx.Cat)")public void hookRun(JoinPoint joinPoint) {Log.d("test", "eat:" + joinPoint.getSourceLocation().getLine() + " " + joinPoint.getTarget() + " " + joinPoint.getThis());}
我们来分析一下:
call(* *..eat(..)),表示任何调用方法名为eat的方法
调用eat()的地方有两个:
所以,再次强调一下:
因为target(com.xxx.Cat),所以只有第二个调用eat()的地方会被织入代码。
假如我们把上面的call(* *..eat(..)),换成execution(* *..eat(..)),那么会怎么样呢?
execution(* *..eat(..)),表示任何方法名为eat的方法
有两个eat():
Advice 是在切入点上织入的代码,在 AspectJ 中有五种类型:Before、After、AfterReturning、AfterThrowing、Around。
| Advice | 说明 |
|---|---|
| @Before | 在执行 Join Point 之前 |
| @After | 在执行 Join Point 之后,包括正常的 return 和 throw 异常 |
| @AfterReturning | Join Point 为方法调用且正常 return 时,不指定返回类型时匹配所有类型 |
| @AfterThrowing | Join Point 为方法调用且抛出异常时,不指定异常类型时匹配所有类型 |
| @Around | 替代 Join Point 的代码,如果要执行原来代码的话,要使用 ProceedingJoinPoint.proceed() |
需要注意的是:
Aspect 就是 AOP 中的关键单位 – 切面,我们一般会把相关 Pointcut 和 Advice 放在一个 Aspect 类中。
classpath 'org.aspectj:aspectjtools:1.8.9'
import org.aspectj.bridge.IMessageimport org.aspectj.bridge.MessageHandlerimport org.aspectj.tools.ajc.Mainapply plugin: 'com.android.library'android {// ...}dependencies {// ...// 这里!!!!implementation 'org.aspectj:aspectjrt:1.8.9'api project(':logaop-annotation')}// 这里!!!!android.libraryVariants.all { variant ->JavaCompile javaCompile = variant.javaCompilejavaCompile.doLast {String[] args = ["-showWeaveInfo","-1.7","-inpath", javaCompile.destinationDir.toString(),"-aspectpath", javaCompile.classpath.asPath,"-d", javaCompile.destinationDir.toString(),"-classpath", javaCompile.classpath.asPath,"-bootclasspath", android.bootClasspath.join(File.pathSeparator)]MessageHandler handler = new MessageHandler(true);new Main().run(args, handler)def log = project.loggerfor (IMessage message : handler.getMessages(null, true)) {switch (message.getKind()) {case IMessage.ABORT:case IMessage.ERROR:case IMessage.FAIL:log.error message.message, message.thrownbreak;case IMessage.WARNING:case IMessage.INFO:log.info message.message, message.thrownbreak;case IMessage.DEBUG:log.debug message.message, message.thrownbreak;}}}}
import org.aspectj.bridge.IMessageimport org.aspectj.bridge.MessageHandlerimport org.aspectj.tools.ajc.Mainapply plugin: 'com.android.application'android {// ...}dependencies {// ...compile project(path: ':logaop-annotation')// 这里!!!!implementation 'org.aspectj:aspectjrt:1.8.9'}// 这里!!!!final def log = project.loggerfinal def variants = project.android.applicationVariantsvariants.all { variant ->if (!variant.buildType.isDebuggable()) {log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")return;}JavaCompile javaCompile = variant.javaCompilejavaCompile.doLast {String[] args = ["-showWeaveInfo","-1.7","-inpath", javaCompile.destinationDir.toString(),"-aspectpath", javaCompile.classpath.asPath,"-d", javaCompile.destinationDir.toString(),"-classpath", javaCompile.classpath.asPath,"-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]log.debug "ajc args: " + Arrays.toString(args)MessageHandler handler = new MessageHandler(true);new Main().run(args, handler);for (IMessage message : handler.getMessages(null, true)) {switch (message.getKind()) {case IMessage.ABORT:case IMessage.ERROR:case IMessage.FAIL:log.error message.message, message.thrownbreak;case IMessage.WARNING:log.warn message.message, message.thrownbreak;case IMessage.INFO:log.info message.message, message.thrownbreak;case IMessage.DEBUG:log.debug message.message, message.thrownbreak;}}}}
好了,大功告成,可以开始开发了
如果是其他module需要该功能,则每一个需要的module,都需要加上
import org.aspectj.bridge.IMessageimport org.aspectj.bridge.MessageHandlerimport org.aspectj.tools.ajc.Mainapply plugin: 'com.android.library'android {// ...}dependencies {// ...// 这里!!!!implementation 'org.aspectj:aspectjrt:1.8.9'api project(':logaop-annotation')}// 这里!!!!android.libraryVariants.all { variant ->JavaCompile javaCompile = variant.javaCompilejavaCompile.doLast {String[] args = ["-showWeaveInfo","-1.7","-inpath", javaCompile.destinationDir.toString(),"-aspectpath", javaCompile.classpath.asPath,"-d", javaCompile.destinationDir.toString(),"-classpath", javaCompile.classpath.asPath,"-bootclasspath", android.bootClasspath.join(File.pathSeparator)]MessageHandler handler = new MessageHandler(true);new Main().run(args, handler)def log = project.loggerfor (IMessage message : handler.getMessages(null, true)) {switch (message.getKind()) {case IMessage.ABORT:case IMessage.ERROR:case IMessage.FAIL:log.error message.message, message.thrownbreak;case IMessage.WARNING:case IMessage.INFO:log.info message.message, message.thrownbreak;case IMessage.DEBUG:log.debug message.message, message.thrownbreak;}}}}
并且也需要依赖你编写aspectj的那个module,类似上面:logaop-annotation
gradle_plugin_android_aspectjx
直接利用这个gradle插件就可以,具体的可以参考这个的read me
AspectJ in Android (三),AspectJ 两种用法以及常见问题
以 Pointcut 切入点作为区分,AspectJ 有两种用法:
侵入式用法,一般会使用自定义注解,以此作为选择切入点的规则。
下面以 JakeWharton 大神的 hugo 为例,分析自定义注解 AOP 的使用。hugo 是用于在开发环境中打印方法调用信息的,只会打印注解修饰的方法。
首先看下新增的自定义注解:
@Target({TYPE, METHOD, CONSTRUCTOR}) @Retention(CLASS)public @interface DebugLog {}
上面定义了@DebugLog注解,可以修饰类、接口、方法和构造函数,可在 Class 文件中保留,编译期可用。
再看 hugo 的切面代码,代码说明在注释中:
@Aspectpublic class Hugo {private static volatile boolean enabled = true;// @DebugLog 修饰的类、接口的 Join Point@Pointcut("within(@hugo.weaving.DebugLog *)")public void withinAnnotatedClass() {}// synthetic 是内部类编译后添加的修饰语,所以 !synthetic 表示非内部类的// 执行 @DebugLog 修饰的类、接口中的方法,不包括内部类中方法@Pointcut("execution(!synthetic * *(..)) && withinAnnotatedClass()")public void methodInsideAnnotatedType() {}// 执行 @DebugLog 修饰的类中的构造函数,不包括内部类的构造函数@Pointcut("execution(!synthetic *.new(..)) && withinAnnotatedClass()")public void constructorInsideAnnotatedType() {}// 执行 @DebugLog 修饰的方法,或者 @DebugLog 修饰的类、接口中的方法@Pointcut("execution(@hugo.weaving.DebugLog * *(..)) || methodInsideAnnotatedType()")public void method() {}// 执行 @DebugLog 修饰的构造函数,或者 @DebugLog 修饰的类中的构造函数@Pointcut("execution(@hugo.weaving.DebugLog *.new(..)) || constructorInsideAnnotatedType()")public void constructor() {}...@Around("method() || constructor()")public Object logAndExecute(ProceedingJoinPoint joinPoint) throws Throwable {enterMethod(joinPoint); // 打印切入点方法名、参数列表long startNanos = System.nanoTime();Object result = joinPoint.proceed(); // 调用原来的方法long stopNanos = System.nanoTime();long lengthMillis = TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos);exitMethod(joinPoint, result, lengthMillis); // 打印切入点方法名、返回值、方法执行时间return result;}......
从上面代码可以看出 hugo 是以 @DebugLog 作为选择切入点的条件,只需要用 @DebugLog 注解类或者方法就可以打印方法调用的信息。
所以,可以看出侵入式 AspectJ 的特点:
非侵入式,就是不需要使用额外的注解来修饰切入点,不用修改切入点的代码。
下面以 Activity、Fragment 页面统计以及点击事件统计为例,分析非侵入式 Aspectj 在 Android 项目中的使用。
AspectJ 无法在 Activity 中织入代码,因为 Activity 属于 android.jar,是安卓平台代码,Class 文件不会在编译时打包进 apk 中。
但是项目中继承自 Activity 的子类可以作为切入点,因为编译期会变成 Class 文件。
网上一些统计 Activity 例子是这样:
@Before("execution(* android.app.Activity+.on**(..))")public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {...}
上面的代码是以 Activity 及其子类的 onXX 方法作为切入点,但是 Activity 类是无法切入的,而 Activity 的子类的话需要重写的 onXX 方法。那么,就会存在两个问题:
如果要解决这两个问题,可以这样:
有个 BaseActivity 作为所有 Activity 的基类,而且 BaseActivity 重写了 Activity 的所有 onXX 方法时,可以以 BaseActivity 作为切入点。Pointcut 表达式:execution(* BaseActivity.on**(..))
当然,如果直接在BaseActivity里面添加统计内容,应该更方便吧,哈哈
我们统计Fragment 的显示与隐藏,那么就不能仅仅依靠 onResume/onPause 两个来判断,在使用 Fragment Tab 时,tab 切换触发的回调是 onHiddenChanged 方法,而 ViewPager 中切换 Fragment 时触发的是 setUserVisibleHint,所以需要切入这四个方法。
@Pointcut("execution(void onHiddenChanged(boolean)) && within(android.support.v4.app.Fragment) && target(fragment) && args(hidden)")public void onHiddenChanged(Fragment fragment, boolean hidden) {}@Pointcut("execution(void setUserVisibleHint(..)) && within(android.support.v4.app.Fragment) && target(fragment) && args(visible)")public void setUserVisibleHint(Fragment fragment, boolean visible) {}@Pointcut("execution(void onResume()) && within(android.support.v4.app.Fragment) && target(fragment)")public void onResume(Fragment fragment) {}@Pointcut("execution(void onPause()) && within(android.support.v4.app.Fragment) && target(fragment)")public void onPause(Fragment fragment) {}
很容易写出下面代码:
@Pointcut("execution(void android.view.View.OnClickListener.onClick(..)) && args(view)")public void onClick(View view) {}
但是上面的 Pointcut 其实是不全面的,setOnClickListener(new OnClickListener() {…}) 这种匿名内部类写法时没问题,如果是实现 OnClickListener 接口的类则无法切入。所以应该加上 OnClickListener 的子类,使用 OnClickListener+。
@Pointcut("execution(void android.view.View.OnClickListener+.onClick(..)) && args(view)")public void onClick(View view) {}
这种写法还可以监听到 ButterKnife 的点击事件,因为 ButterKnife 使用一个实现了 OnClickListener 的抽象接口。
所以,从上面的示例可以看出非侵入式 AspectJ 的特点:
参考:XMark
我们参考这个,来实现一个功能:打印方法的开始和结束log,并且包含方法的执行时间。
我们定义一个注解,当被这个注解修饰的方法,我们才打印log
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR})public @interface Log {/*** 日志的优先级(默认是0)*/int priority() default 0;}
然后,我们来利用aspectj,植入代码
@Aspectpublic class LogAspectJ {@Pointcut("within(@com.meituan.qcs.android.logaop.annotation.Log *)")public void withinAnnotatedClass() {}@Pointcut("execution(!synthetic * *(..)) && withinAnnotatedClass()")public void methodInsideAnnotatedType() {}@Pointcut("execution(!synthetic *.new(..)) && withinAnnotatedClass()")public void constructorInsideAnnotatedType() {}@Pointcut("execution(@com.meituan.qcs.android.logaop.annotation.Log * *(..)) || methodInsideAnnotatedType()")public void method() {} //方法切入点@Pointcut("execution(@com.meituan.qcs.android.logaop.annotation.Log *.new(..)) || constructorInsideAnnotatedType()")public void constructor() {} //构造器切入点//@Around("(method() || constructor()) && @annotation(log)")@Around(value = "(method() || constructor()) && @annotation(log)", argNames = "joinPoint, log")//@Around("method() || constructor()")public Object logAndExecute(ProceedingJoinPoint joinPoint, Log log) throws Throwable {enterMethod(joinPoint, log);long startNanos = System.nanoTime();Object result = joinPoint.proceed();long stopNanos = System.nanoTime();long lengthMillis = TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos);exitMethod(joinPoint, log, result, lengthMillis);return result;}// ....// ....}
具体的Pointcut就不讲了,上面都说说过了,我们来看下@Around,我一共写了三个,我们来一一看下:
public Object logAndExecute(ProceedingJoinPoint joinPoint) throws Throwable {
注意了!!!!!
@Log(priority=6)@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {String className = mDatas.get(position).className;try {Intent intent = new Intent(this, Class.forName(className));startActivity(intent);} catch (ClassNotFoundException e) {e.printStackTrace();}}
这个方法被执行的时候,就会打印日志了,我们可以来看下生成的代码,在app-build-intermediates-classes下面来看,生成的class文件是否被织入了代码:
@Log(priority = 6)public void onItemClick(AdapterView<?> parent, View view, int position, long id) {StaticPart var10000 = ajc$tjp_0;Object[] var14 = new Object[]{parent, view, Conversions.intObject(position), Conversions.longObject(id)};JoinPoint var13 = Factory.makeJP(var10000, this, this, var14);LogAspectJ var16 = LogAspectJ.aspectOf();Object[] var15 = new Object[]{this, parent, view, Conversions.intObject(position), Conversions.longObject(id), var13};ProceedingJoinPoint var10001 = (new MainActivity$AjcClosure1(var15)).linkClosureAndJoinPoint(69648);Annotation var10002 = ajc$anno$0;if (ajc$anno$0 == null) {var10002 = ajc$anno$0 = MainActivity.class.getDeclaredMethod("onItemClick", AdapterView.class, View.class, Integer.TYPE, Long.TYPE).getAnnotation(Log.class);}var16.logAndExecute(var10001, (Log)var10002);}
auv1107/Hugo
我们来替换android.util.Log的所有打印代码
@Aspectpublic class LogAspectJ2 {@Pointcut("within(com.android.logaop.*)")public void withinPlugin() {}@Pointcut("call(static * android.util.Log.*(String,String))")public void log() {}@Around("log() && !withinPlugin()")public Object printLog(ProceedingJoinPoint joinPoint) throws Throwable {String methodName = joinPoint.getSignature().getName();String tag = (String) joinPoint.getArgs()[0];String msg = (String) joinPoint.getArgs()[1];Log.e("22222", tag + " ---> " + msg);return 0;}}
我们不调用原来的代码,直接打印出来,当然这个是测试代码,我们可以拦截之后,使用其他更好的处理方法。
我们用上面的方法一引入的话,比较麻烦,这个时候我们可以通过自定义Gradle插件来实现。


https://zhuanlan.zhihu.com/p/56172076
2、hugo
http://www.mapeiyu.com/2017/03/22/add-logic-through-annotation/
https://xiaozhuanlan.com/topic/1428095376
https://blog.csdn.net/hp910315/article/details/52701809
3、aspectj
https://blog.csdn.net/woshimalingyi/article/details/73252013
https://github.com/xuexiangjys/XMark
https://github.com/xuexiangjys/XAOP
http://johnnyshieh.me/posts/aspectj-in-android-usage/
https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx