AOP概念理解 深入理解Android之AOP 如果对AOP没什么概念,建议阅读该篇文章
AOP Signature 格式说明 传送门
刚接触aspectj时在网上查阅了不少资料,参考了一些网友的经验分享,结果很少有成功的,最后还是找到一篇可以正常编译运行的经验分享,这里我也稍作整理供大家参考,自己也留个笔记,方便以后翻阅查找。
拿自己的demo为例,可能跟大家的工程结构不一样,各位可以酌情参考,我是新建了一个Android Project,有一个默认的app module,然后aspectj类直接在app里创建的,大家也可以创建单独的android lib module,把aspectj文件在这里新建,然后在app module中引用,需要配置的脚本内容如下:
1.1 project根目录下的build.gradle
首先需要在工程根目录下的build.gradle的buildscript中添加aspectj相关依赖,如下:
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.2'
//☆☆☆ add for test AspectJ Begin
classpath 'org.aspectj:aspectjtools:1.9.2'
classpath 'org.aspectj:aspectjweaver:1.9.2'
//☆☆☆ End
}
}
1.2 app module中的build.gradle 配置
这里需要配置两块内容,一个是依赖的sdk,另一个是aspectJ生效配置,具体如下:
//Part One
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
//aspectjrt的依赖
implementation group: 'org.aspectj', name: 'aspectjrt', version: '1.9.2'
}
//Part Two
/*Aspectj配置*/
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
//log 打印工具和构建配置
final def log = project.logger
final def variants = project.android.applicationVariants
variants.all { variant ->
// 通过debug判断是否需要打入aspectj,比如咱们切入的是打印执行时间工具代码,那如果release不需要打则return掉
if (!variant.buildType.isDebuggable()) {
log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
return;
}
//执行到这里,使 aspectj 配置生效
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)]
log.debug "ajc args: " + Arrays.toString(args)
//主要这两句使aspectj 生效
MessageHandler handler = new MessageHandler(true);
new Main().run(args, handler);
//在编译时打印信息如警告、error 等等
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;
}
}
}
经过以上几部配置,我们先sync一下工程,就可以编写自己的Aspect类了,在这个类里,你需要定义目标PointCut和Advice。
比如我们想在Activity的声明周期方法中开始添加一个打印方法名称的aspect,我们需要创建切面类,然后定义PointCut,如:
@Aspect
public class ActivityAspect {
@Pointcut("execution(* com.example.htestproj.MainActivity.on**(..))")
public void actlifecycle() {
}
}
这里我们使用两个注解,一个@Aspect,它用来描述当前类为aspect类,然后我新建了一个空实现的方法,用@Pointcut注解标注,其value内容为"execution(* com.example.htestproj.MainActivity.on**(..))", 该Pointcut表示是一个execution类型的JoinPoint,它筛选的对象为onXX开头的方法。
关于signature的书写规则可以参考一下这篇文章:aspect signature规则,详见文中4.2.2部分;
定义好Pointcut之后就可以创建advice了,通过设置advice我们可以指定代码注入的时机,是位于pointcut之前还是之后,或者两者都有,这里按照我之前的想法,想在各声明周期方法执行之前打印一下方法的名称,所以这里我需要用@Before类型的advice来标注一个方法,如:
@Before("actlifecycle()")
public void beforeLifeCycle(JoinPoint joinPoint) throws Throwable {
MethodSignature ms = (MethodSignature)joinPoint.getSignature();
String methodName = ms.getName();
String cname = joinPoint.getThis().getClass().getSimpleName();
Log.d("hgl_tag", "inject log before "+methodName+" in class: " + cname);
}
这里需要注意一点,即advice注解的value就是要关联的Pointcut对应的方法名,方法名后边的括号需要加上,不然不生效。通过代码可以看到,声明的方法需要包含一个JoinPoint参数,通过这个JP我们可以拿到方法的名字,以及当前class等信息,其他信息大家可以看一下源码了解一下都提供了获取哪些信息的方法。
测试的activity代码很简单:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onResume() {
super.onResume();
isDebugModel(BuildConfig.BUILD_TYPE);
}
@Override
protected void onPause() {
super.onPause();
}
}
打包运行APP,logcat可以看到如下信息:
2020-01-19 16:12:50.762 30627-30627/? D/hgl_tag: inject log before onCreate in class: MainActivity
2020-01-19 16:12:50.900 30627-30627/? D/hgl_tag: inject log before onResume in class: MainActivity
说明我们通过aspectj成功实现AOP,完成了面向切换的代码注入;
在实际使用中,我们关注的切面可能比较分散,且没有统一的命名规则,这时候我们可以借助自定义注解来实现切面注入。来看一个具体例子:
3.1 定义注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OmegaTrack {
String params();
}
3.2使用注解
//在目标方法前添加注解,设置参数
@OmegaTrack(params = "actvity_onPause")
@Override
protected void onPause() {
super.onPause();
}
3.3 声明pointcut
@Pointcut("execution(@com.example.annotation.OmegaTrack * *(..))")
public void omegaTrackData() {
}
/**
* AOP Advice 通过自定义注解实现AOP埋点
*/
@Around("omegaTrackData()")
public Object trackMethod(ProceedingJoinPoint pdj) throws Throwable {
Object result = null;
MethodSignature method = (MethodSignature)pdj.getSignature();
String methodName = method.getName();
OmegaTrack omegaTrackAn = method.getMethod().getAnnotation(OmegaTrack.class);
String params = omegaTrackAn.params();
Log.d("hgl_tag", "omegatrack:" + methodName + ",参数为:" + Arrays.asList(pdj.getArgs()) + " omage params: " + params);
trackOmegaEvent(params);
try {
result = pdj.proceed();
Log.d("hgl_tag", "omegatrack: " + methodName + ",返回结果为:" + result);
} catch (Throwable e) {
/*异常通知方法*/
Log.d("hgl_tag", "omegatrack: " + methodName + ",异常为:" + e);
throw new RuntimeException();
}
return result;
}
打包编译运行即可看到用@OmegaTrack标注的方法被注入了代码。
最终被注入aspect注入代码的class文件在哪儿呢?经过一番搜索终于发现它的位置: /HTestProj/app/build/intermediates/transforms/dexBuilder/debug/0/com , 不过该目录下的文件是dex形式的需要做dex2jar的转化在查看。