Android AOP框架 AspectJ的使用

什么是AOP

AOP为Aspect Oriented Programming的缩写,意思是面向切面编程。
AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。也就是时候可以,在不需要修改目的程序代码的前提下,可以插入新的代码块,并且好处是,唯一修改,多处生效。
本质上是在编译时期由java文件生成class文件时,将aop代码插入到原来的程序中。
AOP应用场景 Spring中为了将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
Android应用场景:
条件判断:
检查权限是否开启
检查网络状态是否可用
检查用户登录
统计:
用户行为统计
等。。。
AOP与OOP 面向对象的特点是继承、多态和封装。为了符合单一职责的原则,OOP将功能分散到不同的对象中去。让不同的类设计不同的方法,这样代码就分散到一个个的类中。可以降低代码的复杂程度,提高类的复用性。往往分散代码的同时,也增加了代码的重复性,比如某些情况下,两个类,都需要同一个方法,那么按照面向对象的思路,我们要分别在两个类中都写一遍,又比如,在两个类中的方法加入日志统计,那么我们按照以往的编程思想,就要两个类的方法中都加入日志内容。也许他们是完全相同的,但是因为OOP的设计让类与类之间无法联系,而不能将这些重复的代码统一起来。AOP就是为了解决这类问题而产生的,它是在运行时动态地将代码切入到类的指定方法、指定位置上的编程思想。AOP 是 OOP 的有力补充,合理的使用,会对我们的开发带来极大的方便。
用户登录状态
检查网络状态是否可用
在实际开发中为例 在 个人中心页面,许多功能是需要检测用户登录状态的。
// An highlighted block setOnclick(){ case 1: if(checkUser()) doEvent1(); break; case 2: if(checkUser()) doEvent2(); break; }

aop来实现------>
case 1: doEvent1(); @CheckUser void doEvent1(){}

用CheckUser这个注解去监听doEvent1() 方法,先去判断是否要登录,再执行原来的doEvent1()方法。
类似的 多个不同的类中有相似的判断,也可以用aop去处理,这样的好处是减少了大量的重复代码,比如添加日志功能,或者测试程序性能,这些与本身程序无关的逻辑。
AspectJ AspectJ的特点:它是Java语言的无缝扩展,几乎是与java完全一致的语言,兼容java平台,易于学习使用,除了使用AspectJ特殊的语言外,AspectJ还支持原生的Java,只要加上对应的AspectJ注解就好。
基础概念
  1. Join Points(连接点)
    Join Points,简称JPoints,是AspectJ的核心思想之一,它就像一把刀,把程序的整个执行过程切成了一段段不同的部分。例如,构造函数被调用、构造函数内部执行、函数被调用、函数执行内部、异常处理等等,这些都是Join Points,实际上,也就是你想把新的代码插在程序的哪个地方,是插在构造方法中,还是插在某个方法调用前,或者是插在某个方法中,这个地方就是Join Points,当然,不是所有地方都能给你插的,只有能插的地方,才叫Join Points。
  2. Pointcuts(切入点)
    告诉代码注入工具,在何处注入一段特定代码的表达式。例如,在哪些 joint points 应用一个特定的 Advice。切入点可以选择唯一一个,比如执行某一个方法,也可以有多个选择,可简单理解为带条件的Join Points,作为我们需要的代码切入点。
  3. Advice(通知)
    如何注入到我的class文件中的代码。典型的 Advice 类型有 before、after 和 around,分别表示在目标方法执行之前、执行后和完全替代目标方法执行的代码。
  4. Aspect(切面)
    Pointcut 和 Advice 的组合看做切面。例如,我们在应用中通过定义一个 pointcut 和给定恰当的advice,添加一个日志切面。
  5. Weaving(织入)
    注入代码(advices)到目标位置(joint points)的过程。
下面以AspectJ为例介绍下Aop在android开发中的应用。
AspectJ接入项目
接入方式介绍 在项目主module下 创建 一个 aspectj.gradle 文件,文件内容如下
import org.aspectj.bridge.IMessage import org.aspectj.bridge.MessageHandler import org.aspectj.tools.ajc.Main buildscript { repositories { mavenCentral() } dependencies { classpath 'org.aspectj:aspectjtools:1.8.9' } } repositories { mavenCentral() }dependencies { //compile project(':gintonic') compile 'org.aspectj:aspectjrt:1.8.9' }final def log = project.logger final def variants = project.android.applicationVariantsvariants.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.5", "-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; } } } }

接下来在主moudle 中依赖 apply from:‘aspectj.gradle’
以上完成了项目对AspectJ的依赖,接下来去使用AspectJ的功能,
有两种方式可以通过实现切入程序 一种是以原始的AspectJ语法,另一种是通过注解的方式。
使用
方式1 注解方式:
结构可以参考下图
Android AOP框架 AspectJ的使用
文章图片

首先声明性能检测的注解
PerformanceTrace
package org.android.aop.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** *Retention 注解保留策略 枚举类型 *SOURCE注解在编译的时候丢弃,只在原文件中有效 *CLASS注解在class文件中有效,运行时不会被VM保留 *RUNTIME 注解在class文件中,并在运行时由VM保留,因此可以反射地读取注释 */ @Retention(RetentionPolicy.CLASS) /** *Target 注解的作用目标 *@Target(ElementType.TYPE)接口、类、枚举、注解 *@Target(ElementType.FIELD)字段、枚举的常量 *@Target(ElementType.METHOD) 方法 *@Target(ElementType.PARAMETER) 方法参数 *@Target(ElementType.CONSTRUCTOR)构造函数 *@Target(ElementType.LOCAL_VARIABLE) 局部变量 *@Target(ElementType.ANNOTATION_TYPE) 注解 *@Target(ElementType.PACKAGE) 包 */ @Target({ ElementType.CONSTRUCTOR, ElementType.METHOD }) public @interface PerformanceTrace {}

接下来声明@Aspect
/** * 切面声明 切面的切点 函数和构造函数 * @author Administrator */ @Aspect public class AspectTrace {//ydc start private static final String TAG = "LinearLayout"; //1 注解方式: private static final String POINTCUT_METHOD = "execution(@org.android.aop.annotation.PerformanceTrace * *(..))"; private static final String POINTCUT_CONSTRUCTOR = "execution(@org.android.aop.annotation.PerformanceTrace*.new(..))"; @Pointcut(POINTCUT_METHOD) public void methodAnnotatedWithPerformanceTrace() {}@Pointcut(POINTCUT_CONSTRUCTOR) public void constructorAnnotatedPerformanceTrace() {}@Around("methodAnnotatedWithPerformanceTrace() || constructorAnnotatedPerformanceTrace()") public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable { //Log.e(TAG,"weaveJoinPoint"); //获取方法信息对象 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); //获取当前对象 String className = methodSignature.getDeclaringType().getSimpleName(); String methodName = methodSignature.getName(); Object[] paramValues = joinPoint.getArgs(); String[] paramNames = ((CodeSignature) joinPoint .getSignature()).getParameterNames(); for(int i=0; i

这样,我们可以使用上面的注解@PerformanceTrace 去检测我们想要检测得函数的执行耗时。比如这样:
@PerformanceTrace private void doTestAop(String a){ Log.e("LinearLayout","执行原方法Thread.sleep 500ms"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } }

在需要检测得方法上添加@PerformanceTrace注解,在编译阶段,我们的切面程序就会添加到doTestAop函数上,可以在下面这个目录下查看编译生成的class文件
Android AOP框架 AspectJ的使用
文章图片

@PerformanceTrace private void doTestAop(String a) { JoinPoint var3 = Factory.makeJP(ajc$tjp_0, this, this, a); AspectTrace var10000 = AspectTrace.aspectOf(); Object[] var4 = new Object[]{this, a, var3}; var10000.weaveJoinPoint((new HomeActivity$AjcClosure1(var4)).linkClosureAndJoinPoint(69648)); }

这里使用的是around 表示对方法整体进行拦截,前面介绍 Advice 类型有 before、after 和 around,分别表示在目标方法执行之前、执行后和完全替代目标方法执行的代码。
当使用@After的时候,@After(“methodAnnotatedWithDemandTrace() || constructorAnnotatedDemandTrace()”)
@PerformanceTrace private void doTestAop(String a) { JoinPoint var2 = Factory.makeJP(ajc$tjp_0, this, this, a); try { Log.e("LinearLayout", "执行原方法Thread.sleep 500ms"); } catch (Throwable var5) { AspectTrace.aspectOf().weaveJoinPoint2(var2); throw var5; }AspectTrace.aspectOf().weaveJoinPoint2(var2); }

当使用@Before的时候,@Before(“methodAnnotatedWithDemandTrace() || constructorAnnotatedDemandTrace()”)
@PerformanceTrace private void doTestAop(String a) { JoinPoint var2 = Factory.makeJP(ajc$tjp_0, this, this, a); AspectTrace.aspectOf().weaveJoinPoint2(var2); Log.e("LinearLayout", "执行原方法Thread.sleep 500ms"); }

方式2 AspectJ语法
示例:@Before("execution(* android.app.Activity.on(…))")**
使用execution或者call 去将代码注入到指定的方法位置。
//Before和After的用法 @Before("execution(* android.app.Activity.on*(android.os.Bundle))") public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable { String key = joinPoint.getSignature().toString(); Log.e(TAG, "onActivityMethodBefore: " + key); } @After("execution(* android.app.Activity.on*(android.os.Bundle))") public void onActivityMethodAfter(JoinPoint joinPoint) throws Throwable { String key = joinPoint.getSignature().toString(); Log.e(TAG, "onActivityMethodAfter: " + key); }

例如 监听Activity的on开始的方法。
protected void onCreate(Bundle savedInstanceState) { JoinPoint var3 = Factory.makeJP(ajc$tjp_0, this, this, savedInstanceState); try { AspectTrace.aspectOf().onActivityMethodBefore(var3); super.onCreate(savedInstanceState); .... 省略代码 .... }); } catch (Throwable var6) { AspectTrace.aspectOf().onActivityMethodAfter(var3); throw var6; }AspectTrace.aspectOf().onActivityMethodAfter(var3); }

可以看到
分别在原onCreate的开始,和结尾插入了AspectTrace.aspectOf().onActivityMethodBefore(var3);
与 AspectTrace.aspectOf().onActivityMethodAfter(var3);
这里除了用execution 还可以用call, call与 execution的区别call 是在方法调用的外面,execution 是在方法内部
@Around("execution(* collectPageName(String,com.womai.MyApp,String))") public Object executionMethod(ProceedingJoinPoint joinPoint) throws Throwable { joinPoint.proceed(); MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); String className = methodSignature.getDeclaringType().getSimpleName(); String methodName = methodSignature.getName(); String result = "aroundWeaverPoint"; Log.e(TAG,"----------------------------->aroundWeaverPoint"+"|"+joinPoint.getThis()+"|"+joinPoint.getArgs()+"|"+joinPoint.getKind()+"|"+joinPoint.getArgs()+"|"+className+"|"+methodName); returnresult; //替换原方法的返回值 } @Before("call(* collectPageName(String,com.womai.MyApp,String))") public void callMethod(JoinPoint joinPoint){ Log.e(TAG,"beforecall"); }

编译后的方法
protected void onStart() { String var8 = ""; MyApp var9 = this.myApp; String var10 = this.className; var10000 = ajc$tjp_1; Object[] var12 = new Object[]{var10, var9, var8}; JoinPoint var7 = Factory.makeJP(var10000, this, this, var12); AspectTrace.aspectOf().callMethod(var7); //原方法 this.collectPageName(var10, var9, var8); }private void collectPageName(String key, MyApp myApp, String title) { StaticPart var10000 = ajc$tjp_2; Object[] var8 = new Object[]{key, myApp, title}; JoinPoint var7 = Factory.makeJP(var10000, this, this, var8); AspectTrace var10 = AspectTrace.aspectOf(); Object[] var9 = new Object[]{this, key, myApp, title, var7}; var10.executionMethod((new AbstractActivity$AjcClosure1(var9)).linkClosureAndJoinPoint(69648)); }

ProceedingJoinPoint 是继承 JoinPoint 并暴露了proceed() 方法,这个方法的作用是执行原函数。调用原方法的执行, joinPoint.proceed();
call,execution,within,withincode 具体语法使用示例
call和execution 【Android AOP框架 AspectJ的使用】二者区别在于 call 是 函数被调用的地方, execution 是函数内部执行。
语法结构:execution / call ([修饰符] 返回值类型 方法名(参数) [异常模式]) 蓝色表示可选部分。
例子:
execution / call(public *.*(..))所有的public方法。 execution / call(* hello(..))所有的hello()方法 execution / call(String hello(..))所有返回值为String的hello方法。 execution / call(* hello(String))所有参数为String类型的hello() execution / call(* hello(String..))至少有一个参数,且第一个参数类型为String的hello方法 execution / call(* com.aspect..*(..))所有com.aspect包,以及子孙包下的所有方法 execution / call(* com..*.*Dao.find*(..))com包下的所有一Dao结尾的类的一find开头的方法

within 和 withincode
// within 织入 除了HomeActivity以外的 collectPageName方法 @Pointcut("execution(* collectPageName(String,com.womai.MyApp,String))&& !within(com.womai.activity.home.HomeActivity)")//&& within(com.womai public void withinMethod() {}@Before("withinMethod()") public void withinCall(JoinPoint joinPoint){ Log.e(TAG,"withinCall" +""+joinPoint.toString()); }

withincode
// 在testAOP2()方法内 @Pointcut("withincode(* com.xys.aspectjxdemo.MainActivity.testAOP2(..))") public void invokeAOP2() { }// 调用testAOP()方法的时候 @Pointcut("call(* com.xys.aspectjxdemo.MainActivity.testAOP(..))") public void invokeAOP() { }// 同时满足前面的条件,即在testAOP2()方法内调用testAOP()方法的时候才切入 @Pointcut("invokeAOP() && invokeAOP2()") public void invokeAOPOnlyInAOP2() { }@Before("invokeAOPOnlyInAOP2()") public void beforeInvokeAOPOnlyInAOP2(JoinPoint joinPoint) { String key = joinPoint.getSignature().toString(); Log.d(TAG, "onDebugToolMethodBefore: " + key); }

参考更多 AspectJ 语法文档
AspectJ in Android
AOP 之 AspectJ 全面剖析 in Android
AOP之AspectJ在Android中的应用

    推荐阅读