@TryLoveCatch
2022-05-07T16:27:38.000000Z
字数 21025
阅读 3245
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();
}
}
@Aspect
public 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 {
@Override
public void greetTo(String name) {
System.out.println("NaiveWaiter:greet to " + name + "...");
}
@Override
public 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里面调用的任何方法
我们来看一个例子:
@Aspect
class 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 {
@Override
protected 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.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
apply plugin: 'com.android.library'
android {
// ...
}
dependencies {
// ...
// 这里!!!!
implementation 'org.aspectj:aspectjrt:1.8.9'
api project(':logaop-annotation')
}
// 这里!!!!
android.libraryVariants.all { variant ->
JavaCompile javaCompile = variant.javaCompile
javaCompile.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.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;
}
}
}
}
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
apply plugin: 'com.android.application'
android {
// ...
}
dependencies {
// ...
compile project(path: ':logaop-annotation')
// 这里!!!!
implementation 'org.aspectj:aspectjrt:1.8.9'
}
// 这里!!!!
final def log = project.logger
final def variants = project.android.applicationVariants
variants.all { variant ->
if (!variant.buildType.isDebuggable()) {
log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
return;
}
JavaCompile javaCompile = variant.javaCompile
javaCompile.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.thrown
break;
case IMessage.WARNING:
log.warn message.message, message.thrown
break;
case IMessage.INFO:
log.info message.message, message.thrown
break;
case IMessage.DEBUG:
log.debug message.message, message.thrown
break;
}
}
}
}
好了,大功告成,可以开始开发了
如果是其他module需要该功能,则每一个需要的module,都需要加上
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
apply plugin: 'com.android.library'
android {
// ...
}
dependencies {
// ...
// 这里!!!!
implementation 'org.aspectj:aspectjrt:1.8.9'
api project(':logaop-annotation')
}
// 这里!!!!
android.libraryVariants.all { variant ->
JavaCompile javaCompile = variant.javaCompile
javaCompile.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.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;
}
}
}
}
并且也需要依赖你编写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 的切面代码,代码说明在注释中:
@Aspect
public 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,植入代码
@Aspect
public 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)
@Override
public 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的所有打印代码
@Aspect
public 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