[关闭]
@TryLoveCatch 2022-05-07T16:27:38.000000Z 字数 21025 阅读 3245

Android知识体系之Aop Aspectj

Android知识体系


AspectJ基础知识

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 的语法。

基本概念

把下面的一个例子拿上来,我们来理解一下这些概念:

  1. class Cat {
  2. public void eat() {...}
  3. }
  4. class A {
  5. public void test() {
  6. new Cat().eat();
  7. }
  8. }
  9. @Aspect
  10. public class Test {
  11. @Before("call(* Cat.eat())")
  12. public void testCall(JoinPoint joinPoint) {
  13. // ....
  14. System.out.println("1111")
  15. }
  16. @After("execution(* Cat.eat())")
  17. public void testExecution(JoinPoint joinPoint) {
  18. // ....
  19. }
  20. }

我们来看testCall():

我们再来看testExecution():

Join Point

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 是具体的切入点,可以确定具体织入代码的地方,基本的 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] 属性类型 [类名.]属性名

类型匹配的通配符

通配符 说明
* 匹配任何数量字符
.. 匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数
+ 匹配指定类型的子类型;仅能作为后缀放在类型模式后边

示例

execution
  1. public interface Waiter {
  2. void greetTo(String name);
  3. void serveTo(String name);
  4. }
  5. public class NaiveWaiter implements Waiter {
  6. @Override
  7. public void greetTo(String name) {
  8. System.out.println("NaiveWaiter:greet to " + name + "...");
  9. }
  10. @Override
  11. public void serveTo(String name) {
  12. System.out.println("NaiveWaiter:serving to " + name + "...");
  13. }
  14. public void smile(String clientName,int times){
  15. System.out.println("NaiveWaiter:smile to "+clientName+ times+"times...");
  16. }
  17. }
  18. public class NaughtyWaiter implements Waiter {
  19. public void greetTo(String clientName) {
  20. System.out.println("NaughtyWaiter:greet to " + clientName + "...");
  21. }
  22. public void serveTo(String clientName) {
  23. System.out.println("NaughtyWaiter:serving " + clientName + "...");
  24. }
  25. public void joke(String clientName, int times) {
  26. System.out.println("NaughtyWaiter:play " + times + " jokes to " + clientName + "...");
  27. }
  28. private void privateMethod() {}
  29. }

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
  1. @Before("call(* *.*(..)) && this(foo)")
  2. public void callFromFoo(Foo foo) {
  3. Log.d(TAG, "call from Foo:" + foo);
  4. }

代表在Foo里面调用的任何方法

set
handler

Pointcut 声明

我们来看一个例子:

  1. @Aspect
  2. class Test {
  3. @Pointcut("execution(void Foo.foo(..)")
  4. public void executFoo() {}
  5. @Pointcut("executFoo() && cflowbelow(executFoo()) && target(foo) && args(i)")
  6. public void loopExecutFoo(Foo foo, int i) {}
  7. }

execution() 与 call()

举个例子:

  1. class Cat {
  2. public void eat() {...}
  3. }
  4. class A {
  5. public void test() {
  6. new Cat().eat();
  7. }
  8. }

target() 与 this()

AspectJ之this和target的区别(四)

target() 与 this() 很容易混淆,我们有必要强调一下:

简单地说就是,PointcutA 选取的是 methodA,那么 target 就是 methodA() 这个方法的对象,而 this 就是 methodA 被调用时所在类的对象。

我们来举个例子:

  1. class Cat {
  2. public void eat() {...}
  3. }
  4. public class TargetThisActivity extends AppCompatActivity {
  5. @Override
  6. protected void onCreate(Bundle savedInstanceState) {
  7. super.onCreate(savedInstanceState);
  8. setContentView(R.layout.activity_target_this);
  9. eat();
  10. }
  11. public void eat(){
  12. new Cat().eat();
  13. }
  14. }
  15. @Before("call(* *..eat(..)) && target(com.xxx.Cat)")
  16. public void hookRun(JoinPoint joinPoint) {
  17. Log.d("test", "eat:" + joinPoint.getSourceLocation().getLine() + " " + joinPoint.getTarget() + " " + joinPoint.getThis());
  18. }

我们来分析一下:

call(* *..eat(..)),表示任何调用方法名为eat的方法

调用eat()的地方有两个:

所以,再次强调一下:

因为target(com.xxx.Cat),所以只有第二个调用eat()的地方会被织入代码。

假如我们把上面的call(* *..eat(..)),换成execution(* *..eat(..)),那么会怎么样呢?

execution(* *..eat(..)),表示任何方法名为eat的方法

有两个eat():

Advice

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

Aspect 就是 AOP 中的关键单位 – 切面,我们一般会把相关 Pointcut 和 Advice 放在一个 Aspect 类中。

Android Studio引入aspectj

方法一

  1. classpath 'org.aspectj:aspectjtools:1.8.9'
  1. import org.aspectj.bridge.IMessage
  2. import org.aspectj.bridge.MessageHandler
  3. import org.aspectj.tools.ajc.Main
  4. apply plugin: 'com.android.library'
  5. android {
  6. // ...
  7. }
  8. dependencies {
  9. // ...
  10. // 这里!!!!
  11. implementation 'org.aspectj:aspectjrt:1.8.9'
  12. api project(':logaop-annotation')
  13. }
  14. // 这里!!!!
  15. android.libraryVariants.all { variant ->
  16. JavaCompile javaCompile = variant.javaCompile
  17. javaCompile.doLast {
  18. String[] args = [
  19. "-showWeaveInfo",
  20. "-1.7",
  21. "-inpath", javaCompile.destinationDir.toString(),
  22. "-aspectpath", javaCompile.classpath.asPath,
  23. "-d", javaCompile.destinationDir.toString(),
  24. "-classpath", javaCompile.classpath.asPath,
  25. "-bootclasspath", android.bootClasspath.join(File.pathSeparator)
  26. ]
  27. MessageHandler handler = new MessageHandler(true);
  28. new Main().run(args, handler)
  29. def log = project.logger
  30. for (IMessage message : handler.getMessages(null, true)) {
  31. switch (message.getKind()) {
  32. case IMessage.ABORT:
  33. case IMessage.ERROR:
  34. case IMessage.FAIL:
  35. log.error message.message, message.thrown
  36. break;
  37. case IMessage.WARNING:
  38. case IMessage.INFO:
  39. log.info message.message, message.thrown
  40. break;
  41. case IMessage.DEBUG:
  42. log.debug message.message, message.thrown
  43. break;
  44. }
  45. }
  46. }
  47. }
  1. import org.aspectj.bridge.IMessage
  2. import org.aspectj.bridge.MessageHandler
  3. import org.aspectj.tools.ajc.Main
  4. apply plugin: 'com.android.application'
  5. android {
  6. // ...
  7. }
  8. dependencies {
  9. // ...
  10. compile project(path: ':logaop-annotation')
  11. // 这里!!!!
  12. implementation 'org.aspectj:aspectjrt:1.8.9'
  13. }
  14. // 这里!!!!
  15. final def log = project.logger
  16. final def variants = project.android.applicationVariants
  17. variants.all { variant ->
  18. if (!variant.buildType.isDebuggable()) {
  19. log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
  20. return;
  21. }
  22. JavaCompile javaCompile = variant.javaCompile
  23. javaCompile.doLast {
  24. String[] args = ["-showWeaveInfo",
  25. "-1.7",
  26. "-inpath", javaCompile.destinationDir.toString(),
  27. "-aspectpath", javaCompile.classpath.asPath,
  28. "-d", javaCompile.destinationDir.toString(),
  29. "-classpath", javaCompile.classpath.asPath,
  30. "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
  31. log.debug "ajc args: " + Arrays.toString(args)
  32. MessageHandler handler = new MessageHandler(true);
  33. new Main().run(args, handler);
  34. for (IMessage message : handler.getMessages(null, true)) {
  35. switch (message.getKind()) {
  36. case IMessage.ABORT:
  37. case IMessage.ERROR:
  38. case IMessage.FAIL:
  39. log.error message.message, message.thrown
  40. break;
  41. case IMessage.WARNING:
  42. log.warn message.message, message.thrown
  43. break;
  44. case IMessage.INFO:
  45. log.info message.message, message.thrown
  46. break;
  47. case IMessage.DEBUG:
  48. log.debug message.message, message.thrown
  49. break;
  50. }
  51. }
  52. }
  53. }

好了,大功告成,可以开始开发了

注意

如果是其他module需要该功能,则每一个需要的module,都需要加上

  1. import org.aspectj.bridge.IMessage
  2. import org.aspectj.bridge.MessageHandler
  3. import org.aspectj.tools.ajc.Main
  4. apply plugin: 'com.android.library'
  5. android {
  6. // ...
  7. }
  8. dependencies {
  9. // ...
  10. // 这里!!!!
  11. implementation 'org.aspectj:aspectjrt:1.8.9'
  12. api project(':logaop-annotation')
  13. }
  14. // 这里!!!!
  15. android.libraryVariants.all { variant ->
  16. JavaCompile javaCompile = variant.javaCompile
  17. javaCompile.doLast {
  18. String[] args = [
  19. "-showWeaveInfo",
  20. "-1.7",
  21. "-inpath", javaCompile.destinationDir.toString(),
  22. "-aspectpath", javaCompile.classpath.asPath,
  23. "-d", javaCompile.destinationDir.toString(),
  24. "-classpath", javaCompile.classpath.asPath,
  25. "-bootclasspath", android.bootClasspath.join(File.pathSeparator)
  26. ]
  27. MessageHandler handler = new MessageHandler(true);
  28. new Main().run(args, handler)
  29. def log = project.logger
  30. for (IMessage message : handler.getMessages(null, true)) {
  31. switch (message.getKind()) {
  32. case IMessage.ABORT:
  33. case IMessage.ERROR:
  34. case IMessage.FAIL:
  35. log.error message.message, message.thrown
  36. break;
  37. case IMessage.WARNING:
  38. case IMessage.INFO:
  39. log.info message.message, message.thrown
  40. break;
  41. case IMessage.DEBUG:
  42. log.debug message.message, message.thrown
  43. break;
  44. }
  45. }
  46. }
  47. }

并且也需要依赖你编写aspectj的那个module,类似上面:logaop-annotation

方法二

gradle_plugin_android_aspectjx
直接利用这个gradle插件就可以,具体的可以参考这个的read me

AspectJ使用方式

AspectJ in Android (三),AspectJ 两种用法以及常见问题
以 Pointcut 切入点作为区分,AspectJ 有两种用法:

侵入式

侵入式用法,一般会使用自定义注解,以此作为选择切入点的规则。

下面以 JakeWharton 大神的 hugo 为例,分析自定义注解 AOP 的使用。hugo 是用于在开发环境中打印方法调用信息的,只会打印注解修饰的方法。

首先看下新增的自定义注解:

  1. @Target({TYPE, METHOD, CONSTRUCTOR}) @Retention(CLASS)
  2. public @interface DebugLog {
  3. }

上面定义了@DebugLog注解,可以修饰类、接口、方法和构造函数,可在 Class 文件中保留,编译期可用。

再看 hugo 的切面代码,代码说明在注释中:

  1. @Aspect
  2. public class Hugo {
  3. private static volatile boolean enabled = true;
  4. // @DebugLog 修饰的类、接口的 Join Point
  5. @Pointcut("within(@hugo.weaving.DebugLog *)")
  6. public void withinAnnotatedClass() {}
  7. // synthetic 是内部类编译后添加的修饰语,所以 !synthetic 表示非内部类的
  8. // 执行 @DebugLog 修饰的类、接口中的方法,不包括内部类中方法
  9. @Pointcut("execution(!synthetic * *(..)) && withinAnnotatedClass()")
  10. public void methodInsideAnnotatedType() {}
  11. // 执行 @DebugLog 修饰的类中的构造函数,不包括内部类的构造函数
  12. @Pointcut("execution(!synthetic *.new(..)) && withinAnnotatedClass()")
  13. public void constructorInsideAnnotatedType() {}
  14. // 执行 @DebugLog 修饰的方法,或者 @DebugLog 修饰的类、接口中的方法
  15. @Pointcut("execution(@hugo.weaving.DebugLog * *(..)) || methodInsideAnnotatedType()")
  16. public void method() {}
  17. // 执行 @DebugLog 修饰的构造函数,或者 @DebugLog 修饰的类中的构造函数
  18. @Pointcut("execution(@hugo.weaving.DebugLog *.new(..)) || constructorInsideAnnotatedType()")
  19. public void constructor() {}
  20. ...
  21. @Around("method() || constructor()")
  22. public Object logAndExecute(ProceedingJoinPoint joinPoint) throws Throwable {
  23. enterMethod(joinPoint); // 打印切入点方法名、参数列表
  24. long startNanos = System.nanoTime();
  25. Object result = joinPoint.proceed(); // 调用原来的方法
  26. long stopNanos = System.nanoTime();
  27. long lengthMillis = TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos);
  28. exitMethod(joinPoint, result, lengthMillis); // 打印切入点方法名、返回值、方法执行时间
  29. return result;
  30. }
  31. ...
  32. ...

从上面代码可以看出 hugo 是以 @DebugLog 作为选择切入点的条件,只需要用 @DebugLog 注解类或者方法就可以打印方法调用的信息。

总结

所以,可以看出侵入式 AspectJ 的特点:

非侵入式

非侵入式,就是不需要使用额外的注解来修饰切入点,不用修改切入点的代码。

下面以 Activity、Fragment 页面统计以及点击事件统计为例,分析非侵入式 Aspectj 在 Android 项目中的使用。

Activity 页面统计

AspectJ 无法在 Activity 中织入代码,因为 Activity 属于 android.jar,是安卓平台代码,Class 文件不会在编译时打包进 apk 中。
但是项目中继承自 Activity 的子类可以作为切入点,因为编译期会变成 Class 文件。

网上一些统计 Activity 例子是这样:

  1. @Before("execution(* android.app.Activity+.on**(..))")
  2. public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
  3. ...
  4. }

上面的代码是以 Activity 及其子类的 onXX 方法作为切入点,但是 Activity 类是无法切入的,而 Activity 的子类的话需要重写的 onXX 方法。那么,就会存在两个问题:

如果要解决这两个问题,可以这样:

有个 BaseActivity 作为所有 Activity 的基类,而且 BaseActivity 重写了 Activity 的所有 onXX 方法时,可以以 BaseActivity 作为切入点。Pointcut 表达式:execution(* BaseActivity.on**(..))

当然,如果直接在BaseActivity里面添加统计内容,应该更方便吧,哈哈

Fragment 页面统计

我们统计Fragment 的显示与隐藏,那么就不能仅仅依靠 onResume/onPause 两个来判断,在使用 Fragment Tab 时,tab 切换触发的回调是 onHiddenChanged 方法,而 ViewPager 中切换 Fragment 时触发的是 setUserVisibleHint,所以需要切入这四个方法。

  1. @Pointcut("execution(void onHiddenChanged(boolean)) && within(android.support.v4.app.Fragment) && target(fragment) && args(hidden)")
  2. public void onHiddenChanged(Fragment fragment, boolean hidden) {}
  3. @Pointcut("execution(void setUserVisibleHint(..)) && within(android.support.v4.app.Fragment) && target(fragment) && args(visible)")
  4. public void setUserVisibleHint(Fragment fragment, boolean visible) {}
  5. @Pointcut("execution(void onResume()) && within(android.support.v4.app.Fragment) && target(fragment)")
  6. public void onResume(Fragment fragment) {}
  7. @Pointcut("execution(void onPause()) && within(android.support.v4.app.Fragment) && target(fragment)")
  8. public void onPause(Fragment fragment) {}

Click 事件统计

很容易写出下面代码:

  1. @Pointcut("execution(void android.view.View.OnClickListener.onClick(..)) && args(view)")
  2. public void onClick(View view) {}

但是上面的 Pointcut 其实是不全面的,setOnClickListener(new OnClickListener() {…}) 这种匿名内部类写法时没问题,如果是实现 OnClickListener 接口的类则无法切入。所以应该加上 OnClickListener 的子类,使用 OnClickListener+。

  1. @Pointcut("execution(void android.view.View.OnClickListener+.onClick(..)) && args(view)")
  2. public void onClick(View view) {}

这种写法还可以监听到 ButterKnife 的点击事件,因为 ButterKnife 使用一个实现了 OnClickListener 的抽象接口。

总结

所以,从上面的示例可以看出非侵入式 AspectJ 的特点:

实例

打印方法的开始和结束log,并且包含方法的执行时间。

参考:XMark

我们参考这个,来实现一个功能:打印方法的开始和结束log,并且包含方法的执行时间。

注解

我们定义一个注解,当被这个注解修饰的方法,我们才打印log

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR})
  3. public @interface Log {
  4. /**
  5. * 日志的优先级(默认是0)
  6. */
  7. int priority() default 0;
  8. }

aspectj library

然后,我们来利用aspectj,植入代码

  1. @Aspect
  2. public class LogAspectJ {
  3. @Pointcut("within(@com.meituan.qcs.android.logaop.annotation.Log *)")
  4. public void withinAnnotatedClass() {
  5. }
  6. @Pointcut("execution(!synthetic * *(..)) && withinAnnotatedClass()")
  7. public void methodInsideAnnotatedType() {
  8. }
  9. @Pointcut("execution(!synthetic *.new(..)) && withinAnnotatedClass()")
  10. public void constructorInsideAnnotatedType() {
  11. }
  12. @Pointcut("execution(@com.meituan.qcs.android.logaop.annotation.Log * *(..)) || methodInsideAnnotatedType()")
  13. public void method() {
  14. } //方法切入点
  15. @Pointcut("execution(@com.meituan.qcs.android.logaop.annotation.Log *.new(..)) || constructorInsideAnnotatedType()")
  16. public void constructor() {
  17. } //构造器切入点
  18. //@Around("(method() || constructor()) && @annotation(log)")
  19. @Around(value = "(method() || constructor()) && @annotation(log)", argNames = "joinPoint, log")
  20. //@Around("method() || constructor()")
  21. public Object logAndExecute(ProceedingJoinPoint joinPoint, Log log) throws Throwable {
  22. enterMethod(joinPoint, log);
  23. long startNanos = System.nanoTime();
  24. Object result = joinPoint.proceed();
  25. long stopNanos = System.nanoTime();
  26. long lengthMillis = TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos);
  27. exitMethod(joinPoint, log, result, lengthMillis);
  28. return result;
  29. }
  30. // ....
  31. // ....
  32. }

具体的Pointcut就不讲了,上面都说说过了,我们来看下@Around,我一共写了三个,我们来一一看下:

  1. public Object logAndExecute(ProceedingJoinPoint joinPoint) throws Throwable {

注意了!!!!!

使用

  1. @Log(priority=6)
  2. @Override
  3. public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
  4. String className = mDatas.get(position).className;
  5. try {
  6. Intent intent = new Intent(this, Class.forName(className));
  7. startActivity(intent);
  8. } catch (ClassNotFoundException e) {
  9. e.printStackTrace();
  10. }
  11. }

这个方法被执行的时候,就会打印日志了,我们可以来看下生成的代码,在app-build-intermediates-classes下面来看,生成的class文件是否被织入了代码:

  1. @Log(
  2. priority = 6
  3. )
  4. public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
  5. StaticPart var10000 = ajc$tjp_0;
  6. Object[] var14 = new Object[]{parent, view, Conversions.intObject(position), Conversions.longObject(id)};
  7. JoinPoint var13 = Factory.makeJP(var10000, this, this, var14);
  8. LogAspectJ var16 = LogAspectJ.aspectOf();
  9. Object[] var15 = new Object[]{this, parent, view, Conversions.intObject(position), Conversions.longObject(id), var13};
  10. ProceedingJoinPoint var10001 = (new MainActivity$AjcClosure1(var15)).linkClosureAndJoinPoint(69648);
  11. Annotation var10002 = ajc$anno$0;
  12. if (ajc$anno$0 == null) {
  13. var10002 = ajc$anno$0 = MainActivity.class.getDeclaredMethod("onItemClick", AdapterView.class, View.class, Integer.TYPE, Long.TYPE).getAnnotation(Log.class);
  14. }
  15. var16.logAndExecute(var10001, (Log)var10002);
  16. }

替换android.util.Log

auv1107/Hugo
我们来替换android.util.Log的所有打印代码

aspectj library

  1. @Aspect
  2. public class LogAspectJ2 {
  3. @Pointcut("within(com.android.logaop.*)")
  4. public void withinPlugin() {}
  5. @Pointcut("call(static * android.util.Log.*(String,String))")
  6. public void log() {}
  7. @Around("log() && !withinPlugin()")
  8. public Object printLog(ProceedingJoinPoint joinPoint) throws Throwable {
  9. String methodName = joinPoint.getSignature().getName();
  10. String tag = (String) joinPoint.getArgs()[0];
  11. String msg = (String) joinPoint.getArgs()[1];
  12. Log.e("22222", tag + " ---> " + msg);
  13. return 0;
  14. }
  15. }

我们不调用原来的代码,直接打印出来,当然这个是测试代码,我们可以拦截之后,使用其他更好的处理方法。

自定义Gradle插件

我们用上面的方法一引入的话,比较麻烦,这个时候我们可以通过自定义Gradle插件来实现。

原理

时机

aop方案对比

参考

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

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注