Android AOP
概念
切面(Aspect)
通俗来说就是“何时何地发生何事”,其组成如下:
Aspect = Advice (what & when) + Pointcut (where)

通知(Advice)
通知(Advice)定义了何时(when)发生何事(what)。
Spring AOP 的切面(Aspect)可以搭配下面五种通知(Adive)注解使用:

切点(Pointcut)
切点(Pointcut)定义了切面在何处(where)执行。
pointcut可以控制你把哪些advice应用于jointpoint上去,通常你使用pointcuts通过正则表达式来把明显的名字和模式进行匹配应用。决定了那个jointpoint会获得通知。分为call、execution、target、this、within等关键字。
Spring AOP 的切点(Pointcut)使用 AspectJ 的“切点表达式语言(Pointcut Expression Language)”进行定义。但要注意的是,Spring 仅支持其中一个子集:

切点表达式的语法如下:
这里还有一些匹配规则,可以作为示例来进行讲解:

连接点(jointpoint)
连接点是切面插入应用程序的地方,该点能被方法调用,而且也会被抛出意外。连接点是应用程序提供给切面插入的地方,在插入地建立AspectJ程序与源程序的连接。

AOP的实现原理
在编译期对目标对象、方法做标记,对目标类、方法进行重构,将PointCut插入目标中,截获该目标的信息以及上下文环境,以达到非侵入代码监控的目的。
- 编写Aspect:声明Aspect、PointCut和Advise。
- ajc编织: AspectJ编译器在编译期间对所切点所在的目标类进行了重构,在编译层将AspectJ程序与目标程序进行双向关联,生成新的目标字节码,即将AspectJ的切点和其余辅助的信息类段插入目标方法和目标类中,同时也传回了目标类以及其实例引用。这样便能够在AspectJ程序里对目标程序进行监听甚至操控。
- execution & call

- execution:它截获的是方法真正执行的代码区。Around方法就是专门为它存在的,调用Around可以控制原方法的执行与否,可以选择执行也可以选择替换。
Pointcut{ execution(Before) Pointcut Method execution(After) } - call:它截获的是方法的调用区,并不截获代码真正的执行区域,它截获的是方法调用之前与调用之后(与before、after配合使用),在调用方法的前后插入JoinPoint和before、after通知。它截获的信息并没有execution那么多,它无法控制原来方法的执行与否,只是在方法调用前后插入切点,因此它比较适合做一些轻量的监控(方法调用耗时,方法的返回值等)。
Call(Before) Pointcut{ Pointcut Method } Call(After)
- execution:它截获的是方法真正执行的代码区。Around方法就是专门为它存在的,调用Around可以控制原方法的执行与否,可以选择执行也可以选择替换。
- Around替代原理:目标方法体被Around方法替换,原方法重新生成,名为XXX_aroundBody(),如果要调用原方法需要在AspectJ程序的Around方法体内调用joinPoint.proceed()还原方法执行,是这样达到替换原方法的目的。达到这个目的需要双方互相引用,桥梁便是Aspect类,目标程序插入了Aspect类所在的包获取引用。AspectJ通过在目标类里面加入Closure(闭包)类,该类构造函数包含了目标类实例、目标方法参数、JoinPoint对象等信息,同时该类作为切点原方法的执行代理,该闭包通过Aspect类调用Around方法传入Aspect程序。这样便达到了关联的目的,便可以在Aspect程序中监控和修改目标程序。
- Before与After:Before与After只是在方法被调用前和调用之后添加JoinPoint和通知方法(直接插入原程序方法体中),调用AspectJ程序定义的Advise方法,它并不替代原方法,是在方法call之前和之后做一个插入操作。After分为returnning和throwing两类,前者是在正常returning之后调用,后者是在throwing发生之后调用。默认的After是在finally处调用,因此它包含了前面的两种情况。
- within 和 withincode
- within:针对的是类
- withincode: 针对的是方法
例如,假设方法functionA, functionB都调用了dummy,但只想在functionB调用dummy时织入代码。
使用的一个例子:
//-------- MainActivity.java
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
functionA();
functionB();
}
private void functionA() { dummy(); }
private void functionB() { dummy(); }
private void dummy() { }
}
//-------- MethodTracer.java
@Aspect
public class MethodTracer {
// withincode: 在functionB方法内
@Pointcut("withincode(void org.sdet.aspectj.MainActivity.functionB(..))")
public void invokeFunctionB() {}
// call: 调用dummy方法
@Pointcut("call(void org.sdet.aspectj.MainActivity.dummy(..))")
public void invokeDummy() {}
// 在functionB内调用dummy方法
@Pointcut("invokeDummy() && invokeFunctionB()")
public void invokeDummyInsideFunctionB() {}
@Before("invokeDummyInsideFunctionB()")
public void beforeInvokeDummyInsideFunctionB(JoinPoint joinPoint) {
System.out.printf("Before.InvokeDummyInsideFunctionB.advice() called on '%s'", joinPoint);
}
}
//-------- 编译后的MainActivity.java
public class MainActivity extends Activity {
private static final JoinPoint.StaticPart ajc$tjp_0;
private static void ajc$preClinit() {
Factory localFactory = new Factory("MainActivity.java", MainActivity.class);
ajc$tjp_0 = localFactory.makeSJP("method-call", localFactory.makeMethodSig("2", "dummy", "org.sdet.aspectj.MainActivity", "", "", "", "void"), 56);
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(2130903040);
functionA();
functionB();
}
private void functionA() {
dummy();
}
// 只有functionB调用dummy时才会织入代码
private void functionB() {
MainActivity localMainActivity = this;
JoinPoint localJoinPoint = Factory.makeJP(ajc$tjp_0, this, localMainActivity);
MethodTracer.aspectOf().beforeInvokeDummyInsideFunctionB(localJoinPoint);
localMainActivity.dummy();
}
使用方法
编写切面代码
@Aspect public class AspectTest { private static final String POINTCUT_METHOD = "execution(* android.app.Activity.on**(..))"; @Pointcut(POINTCUT_METHOD) public void methodAnnotatedWithDebugTrace() { } @Before("methodAnnotatedWithDebugTrace()") public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable { String key = joinPoint.getSignature().toString(); Log.d(TAG, "onActivityMethodBefore: " + key); } }业务代码
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }配置gradle
主工程和Module都需要对构建脚本添加一些任务,目的就是为了建立两者的通信,使得IDE使用ajc编译代码。
final def log = project.logger
final def variants = project.android.applicationVariants
android.libraryVariants.all { variant ->
LibraryPlugin plugin = project.plugins.getPlugin(LibraryPlugin)
JavaCompile javaCompile = variant.javaCompile
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.5",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", plugin.project.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;
}
}
}
常见问题
(1)问题:AspectJ中Signature提供的getDeclareType返回的是声明类型,无法获取运行时类型,因此无法准确获取接口运行时类别。
方案:使用target关键字约束pointCut,获取目标对象,通过反射获取其运行时类别。
(2)问题:使用target关键字约束pointcut获取目标对象Object之后,无法获取静态方法(不属于对象)
方案:单独将静态方法提出来,再与前面的target关键字约束的集合取并集。
(3)问题:使用Before、After通知,测试方法耗时的精确度误差较大
方案:改用execution+around。两点,第一:由于Before、After是在原方法调用前后插入通知(会影响本来所在方法快的执行速率);第二:同时Before、After两个操作无法保证是原子操作,多线程情况下会有误差。因此该用execution关键字,截获方法体的真正执行处,使用Around通知,替代原方法(原方法被更名,但结构不变),在Around通知体内调用原方法计时,这样能够真正还原方法执行耗时;