AspectJ
面向切面编程(AOP,Aspect-oriented programming) 在 Android 中最流行的实践工具就是 AspectJ。
这篇文章就是来介绍 AspectJ 以及在 Android 中使用。
AspectJ
AspectJ 是一种几乎和 Java 完全一样的语言,而且完全兼容 Java(AspectJ 应该就是一种扩展Java)。
使用 AspectJ 有两种方法:
- 纯 AspectJ 的语言。这语言一点也不难,和Java几乎一样,也能在AspectJ中调用Java的任何类库。AspectJ只是多了一些关键词罢了。
- 纯 Java 语言开发,然后使用 AspectJ 注解,简称
@AspectJ
。
不论哪种方法,最后都需要 AspectJ 的编译工具 ajc 来编译。由于 AspectJ 实际上脱胎于 Java,所以 ajc 工具也能编译 java 源码。
AspectJ官方网站
AspectJ文档
AspectJ 简单概念
要使用 AspectJ ,必须要先弄懂几个概念。
Join Points(连接点)
Join Points 是 AspectJ 中最关键的一个概念,它表示程序中可能作为代码注入目标的特定的点和入口。
比如说,一个方法的调用或者方法的执行是一个 Join Points ,设置或者读取一个变量也是一个Join Points 。
理论上说,一个程序中很多地方都可以被看做是 Join Points,但是 AspectJ 中,只有下面所示的几种执行点被认为是 Join Points :
Join Points | 说明 | 示例 |
---|---|---|
method call | 函数调用 | 比如调用Log.e(),这是一处Join Points |
method execution | 函数执行 | 比如Log.e()的执行内部,是一处Join Points。 |
constructor call | 构造方法调用 | 和 method call 类似 |
constructor execution | 构造函数执行 | 和method execution类似 |
field get | 获取某个变量 | 比如读取 DemoActivity.debug 成员 |
field set | 设置某个变量 | 比如设置DemoActivity.debug成员 |
pre-initialization | Object在构造方法中做得一些工作。 | |
initialization | Object在构造方法中做得工作 | |
static initialization | 类初始化 | 比如类的static{} |
handler | 异常处理 | 比如try catch(xxx)中,对应catch内的执行 |
advice execution | 这个后面说 |
简单直白点说, Join Points 就是一个程序中的关键方法(包括构造方法)和代码段(staticblock)。
你想把新的代码插在程序的哪个地方,是插在构造方法中,还是插在某个方法调用前,或者是插在某个方法中,这个地方就是 Join Points ,当然,不是所有地方都能给你插的,只有支持的地方,才叫 Join Points 。
Advice(通知)
Advice 有三种方式 Before、After、Around ,分别表示在目标方法执行之前、执行后和完全替代目标方法执行的代码。
例如:
@Before("execution(* android.app.Activity.on**(..))")public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {}
这里会分成几个部分,我们依次来看:
@Before :这个就是Advice,表示具体插入的方式。
execution :处理Join Point的类型,例如call、execution
(* android.app.Activity.on**(..)) :这个是最重要的表达式,代表源码中需要 hook 的地方。
第一个位置表示返回值,* 表示返回值为任意类型,如果用注解,这里也需要填写
@注解全名
。第二个位置表示具体位置,可是包名路径,其中可以包含 * 来进行通配,几个 * 没区别。
第三个位置()代表这个方法的参数,你可以指定类型,例如android.os.Bundle,或者(..)这样来代表任意类型、任意个数的参数。
同时,这里可以通过&&、||、!来进行条件组合。
public void onActivityMethodBefore :实际切入的代码。
Before 和 After 其实还是很好理解的,也就是在 Pointcuts 之前和之后,插入代码, Around 可以完全替代目标方法执行的代码,他包含了 Before 和 After 的全部功能,代码如下:
@Around("execution(* com.xys.aspectjxdemo.MainActivity.testAOP())")public void onActivityMethodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { String key = proceedingJoinPoint.getSignature().toString(); Log.d(TAG, "onActivityMethodAroundFirst: " + key); proceedingJoinPoint.proceed(); Log.d(TAG, "onActivityMethodAroundSecond: " + key);}
其中,proceedingJoinPoint.proceed() 代表执行原始的方法,在这之前、之后,都可以进行各种逻辑处理,也可以不执行原始方法,进行完全替换。
Pointcut(切入点)
告诉代码注入工具,在何处注入一段特定代码的表达式。
Aspect(切面)
Pointcut 和 Advice 的组合看做切面。例如,我们在应用中通过定义一个 pointcut 和给定恰当的advice,添加一个日志切面。
Weaving(织入)
注入代码(advices)到目标位置(joint points)的过程。
一张图简要总结了一下上述这些概念。
AspectJ 的使用
在 Android中配置 Aspectj 是特别麻烦的,我也试过最原始的配置方式,花了好长时间才搞定,非常麻烦。
好在江户的工作组已经简化的这件事情。他们在 Github 上开源了 gradle_plugin_android_aspectjx 这个库,非常棒,这里就直接使用这个库,省下了很多繁琐的步骤。
接入说明
- 在项目的根目录的build.gradle文件中添加依赖,修改后文件如下
repositories { jcenter()}dependencies { classpath 'com.android.tools.build:gradle:2.3.0' classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:1.0.8' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files}
- 在编写 AspectJ 的项目或者库中,在其 build.gradle 文件中添加 AspectJ 的依赖
compile 'org.aspectj:aspectjrt:1.8.9'
- 在使用 AspectJ 的项目中加入 AspectJX 的插件:
apply plugin: 'android-aspectjx'
aspectjx 默认会遍历项目编译后所有的 .class 文件和依赖的第三方库,去查找符合织入条件的切点,为了提升编译效率,可以加入过滤条件指定遍历某些库或者不遍历某些库。
includeJarFilter
和 excludeJarFilter
可以支持 groupId 过滤,artifactId 过滤,或者依赖路径匹配过滤
aspectjx { //织入遍历符合条件的库 includeJarFilter 'universal-image-loader', 'AspectJX-Demo/library' //排除包含‘universal-image-loader’的库 excludeJarFilter 'universal-image-loader'}
更多详细用法,直接查看 Github 文档。
使用
我们首先应用一个最简单的案例。
创建一个 AspectTest 的类:
@Aspect //必须添加@Aspect 注解public class AspectTest { final String TAG = AspectTest.class.getSimpleName(); @Before("execution(* *..MainActivity+.on**(..))") public void method(JoinPoint joinPoint) throws Throwable { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); String className = joinPoint.getThis().getClass().getSimpleName(); Log.e(TAG, "class:" + className); Log.e(TAG, "method:" + methodSignature.getName()); }}
MainActivty的代码如下:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }}
这样完成了一个简单的应用。
AspectJ 一般会配合注解来使用,在特定注解的位置处理代码。
再来一个案例,通过注解来打印方法的耗时:
定义注解
@Retention(RetentionPolicy.CLASS) //注意,不能是 RetentionPolicy.SOURCE@Target({ElementType.METHOD,ElementType.CONSTRUCTOR})public @interface Dove {}
注意,配合 AspectJ 时,注解的生命周期一定要定义成 CLASS 或 RUNTIME ,如果定义成 SOURCE,根据其原理可知,它找不到这个注解的。
创建 AspectJ 的处理类
@Aspect //必须添加@Aspect 注解public class AspectDovePlugin { @Pointcut("execution(@com.deemons.dove.Dove * *(..))")//方法切入点 public void methodAnnotated() { } @Pointcut("execution(@com.deemons.dove.Dove *.new(..))")//构造器切入点 public void constructorAnnotated() { } @Around("methodAnnotated()||constructorAnnotated()") public Object method(ProceedingJoinPoint joinPoint) throws Throwable {//AspectJ 的一些 API,用于获取信息。 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); String canonicalName = methodSignature.getMethod().getDeclaringClass().getCanonicalName(); String methodName = methodSignature.getName(); long startTime = System.nanoTime(); Object result = joinPoint.proceed();//执行原方法 //拼接方法调用的参数,以及 方法执行的时间。 StringBuilder keyBuilder = new StringBuilder(); keyBuilder.append(canonicalName); keyBuilder.append(String.format(" : %s (", methodName)); for (Object obj : joinPoint.getArgs()) { if (obj != null) { String format = String.format(Locale.getDefault(), "%s = %s", obj.getClass().getSimpleName(), obj.toString()); keyBuilder.append(format); } else { keyBuilder.append("null"); } } long during = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); keyBuilder.append(String.format(Locale.getDefault(), ") --->:[%d ms]", during)); Log.d("Dove", (keyBuilder.toString()));// 打印时间差 return result; }}
使用注解
public class MainActivity extends AppCompatActivity { @Dove @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); test("test param",88); } @Dove private void test(String s,int i) { Log.d("","do test()"); }}
打印结果如下:
test (String = test paramInteger = 88) --->:[0 ms]onCreate (null) --->:[37 ms]
这样就完成了方法耗时以及调用参数的打印工作。
参考
- 安卓AOP三剑客:APT,AspectJ,Javassist
- AspectJ基本用法
- android_aspectj 插件
- 【翻译】Android中的AOP编程
更多相关文章
- Android系统的四大组件详解
- Android中WebView图片实现自适应的方法
- Android开发中Activity切换导致的onCreate重复执行的问题
- android拾遗——四大基本组件介绍与生命周期
- cocos2d-x android c++调用java
- [Android]资源存储方法
- 详解Android提交数据到服务器的两种方式四种方法
- Android(安卓)高级编程--Fragment理解与使用
- android 如何中断一个子线程