小菜在尝试 Android 性能优化过程中,需要统计的监测各个方法执行调用时间,以及对应 Systrace 生成时;较为优雅的方式就是采用 AOP 切片模式,而 AOP 模式中较为成熟和简单的当属 AspectJ;小菜进行简单集成与测试;
AspectJ
基本简介
AOP(Aspect Oriented Programming) 是一种面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术;可以通过 AOP 对业务逻辑进行整体的切面拆分,却又不影响业务逻辑,提高了开发效率和可重用性;
AspectJ 适用于 Java 平台,是使用较为广泛的 AOP 切面方案;提供了纯 Java 语言实现,通过注解的方式,在编译期进行代码注入;即在编译阶段,就对目标类进行修改,得到的 .class 文件已经是修改过的,生成静态的 AOP 代理类;小菜刚了解 AspectJ,需要了解几个最基本的概念;
1. Aspect
Aspect 是 AOP 中的切面文件,一般将需要在注解开发的 Java 类文件顶部注明 @Aspect,不能修饰接口;
2. JoinPoint
JoinPoint 作为连接点,是程序运行时的一些执行点;例如方法调用时,或读写变量以及异常处理等;官网 介绍非常详细,小菜提醒注意 call() & execution() 这两个方法;
小菜了解 call() & execution() 织入对象不同,call() 是在指定方法被调用时调用,而 execution() 是在方法执行内部时被调用;
3. Pointcut
Pointcut 是具体的切入点,目标是提供一种方法,使开发者可以选择感兴趣的 JoinPoint;
通配符 | 说明 |
---|---|
* | 匹配任何数量字符 |
.. | 匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数 |
+ | 匹配指定类型的子类型;仅能作为后缀放在类型模式后边 |
4. Advice
Advice 用于逻辑处理时切面功能的实现;注解修饰的方法需为 public,Around 使用的是 ProceedingJoinPoint,其他的是 JoinPoint;
Advice | 说明 | 返回类型 |
---|---|---|
@Before | 执行 JoinPoint 之前 | 必须为 void |
@After | 执行 JoinPoint 之后,包括正常的 return 和 throw 异常 | 必须为 void |
@AfterReturning | JoinPoint 为方法调用且正常 return 时,不指定返回类型时匹配所有类型 | 必须为 void |
@AfterThrowing | JoinPoint 为方法调用且抛出异常时,不指定异常类型时匹配所有类型 | 必须为 void |
@Around | 执行 JoinPoint 之前和之后 | -- |
集成测试
AspectJ 的集成非常简单,仅需简单的几个步骤:
1. 根目录 dependencies 中引入插件;
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.5'
2. 在应用 module 中应用插件;
apply plugin: 'android-aspectjx'
3. 编辑记录时间切片的 AOP 文件;
@Aspect
public class TimeAOP {
@Around("call(* com.package.name.Application.**(..))")
public void getApplicationTime(ProceedingJoinPoint joinPoint) {
long time = System.currentTimeMillis();
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
Log.e("TimeAOP:", "Application " + joinPoint.getSignature().getName() + " Time = " + (System.currentTimeMillis() - time));
}
@Pointcut("execution(* com.package.name.MainActivity.**(..))")
public void callMethod() {
}
@Around("callMethod()")
public void getActivityTime(ProceedingJoinPoint joinPoint) {
long time = System.currentTimeMillis();
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
Log.e("TimeAOP:", "Activity " + joinPoint.getSignature().getName() + " Time = " + (System.currentTimeMillis() - time));
}
}
4. 在应用 module 中配置排除冲突的 aspectjx
android {
...
aspectjx {
enabled true
include "packagename"
exclude 'com.google','com.appsflyer','com.android' // 可根据具体的业务确定是否排除
}
}
5. 在应用 module 中添加,此步骤可省略
// 不是必须的,但是为了有时候去掉上面插件不报错就需要增加
api 'org.aspectj:aspectjrt:1.9.4'
问题 & 修复
AspectJ 的集成只有简单的几步,但小菜却折腾了很久;小菜先在 Demo 中集成测试,一切正常;但是应用在历史项目中,却坎坷颇多;
Q1:[TAG] Failed to resolve variable '${httpcore.version}'
A1:
其原因是小菜未在 module 中配置 aspectjx,添加对应的 aspectjx 配置即可;
Q2:Cause: zip file is empty
A2:
根据问题,小菜以为是 Gradle 版本不一致,反复 clean / rebuild 多次后依旧不生效,同时在其他 module 中添加 'android-aspectjx',结果并不生效;最终小菜发现自己编辑的 AOP 切片文件格式有误,少了一个 * 导致的;因此小菜建议 AOP 文件先编辑最简单方法,逐步完善操作;
AspectJ 的功能非常强大,小菜刚学习很多切入规则还不熟悉,仅尝试了最基本的 @Around 方式获取方法的耗时时间;小菜建议在编辑规则过程中,多审查几遍,防止出现因规则错误导致的不容易查找的崩溃;
通过这次学习,小菜深得体会,不管看似多么简单的应用,只有实际上手操作才能体会深刻,官网是最详细的学习资料;如有错误,请多多指导!
来源: 阿策小和尚