在Android项目中使用AspectJ的方法
什么是AOP
AOP是AspectOrientedProgramming的缩写,即面向切面编程,和平常遇到的面向对象OOP编程不一样的是,OOP是将功能模块化对象化,AOP是针对同一类的问题统一化处理。例如做日志埋点,性能监控,动态权限控制等。
AspectJ
AspectJ实际上是对AOP编程的实践,目前还有很多的AOP实现,如ASMDex,但笔者选用的是AspectJ。
在Android项目中使用AspectJ
如果使用原生AspectJ在项目中配置会非常麻烦,在GitHub上有个开源的SDKgradle_plugin_android_aspectjx基于gradle配置即可。
接入说明
请自行查看开源项目中的接入配置过程
AspectJ之JoinPoints介绍
JoinPoints在AspectJ中是关键的概念。JoinPoints可以看做是程序运行时的一个执行点,比如:一个函数的调用可以看做是个JoinPoints,相当于代码切入点。但在AspectJ中,只有下面几种执行点是认为是JoinPoints:
Pointcuts介绍
一个程序会有多个JoinPoints,即使同一个函数,也还分为call和execution类型的JoinPoints,但并不是所有的JoinPoints都是我们关心的,Pointcuts就是提供一种使得开发者能够值选择所需的JoinPoints的方法。
Advice
Advice就是我们插入的代码可以以何种方式插入,有Before还有After、Around。
下面看个例子:
@Before(“execution(*android.app.Activity.on**(..)))”) publicvoidonActivityMethodBefore(JoinPointjoinPoint)throwsThrowable{ }
这里会分成好几个部分,我们依次来看:
- @Before:Advice,也就是具体的插入点
- execution:处理JoinPoint的类型,例如call、execution
- (*android.app.Activity.on**(..)):这个是最重要的表达式,第一个*表示返回值,*表示返回值为任意类型,后面这个就是典型的包名路径,其中可以包含*来进行通配,几个*没有区别。同时这里可以通过&&、||、!来进行条件组合。()代表这个方法的参数,你可以指定类型,例如android.os.Bundle,或者(..)这样来代表任意类型、任意个数的参数。
- publicvoidonActivityMehodBefore:实际切入的代码。
Before和After其实还是很好理解的,也就是在Pointcuts之前和之后,插入代码,那么Android呢,从字面含义上来讲,也就是在方法前后各插入代码,他包含了Before和After的全部功能,代码如下:
@(“execution(*com.xys.aspectjxdemo.MainActivity.testAOP()))”) publicvoidonActivityMethodAround(ProceedingJoinPointproceedingJoinPoint)throwsThrowable{ Stringkey=proceedingJoinPoint.getSignature().toString(); Log.d(TAG,”onActivityMethodAroundFirst:”+key); proceedingJoinPoint.proceed(); Log.d(TAG,”onActivityMethodAroundSecond:”+key); }
以上代码中,proceedingJoinPoint.proceed()代表执行原始的方法,在这之前、之后,都可以进行各种逻辑处理。
自定义Pointcuts
自定义Pointcuts可以让我们更加精准的切入一个或多个指定的切入点。
首先我们要定义一个注解类
@Retention(RetentionPolicy.CLASS) @Target({ElementType.CONSTRUCTOR,ElementType.METHOD}) public@interfaceDebugTrace{ }
在需要插入代码的地方加入这个注解,例如在MainActivity中加入:
publicclassMainActivityextendsAppCompatActivity{ finalStringTAG=MainActivity.class.getSimpleName(); @Override protedctedvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); logTest(); } @DebugTrace publicvoidlogTest(){ Log.e(TAG,”logtest"); } }
最后创建切入代码
@Pointcut(“execution(@com.kun.aspectjtest.aspect.DebugTrace**..*.*(..))”) publicvoidDebugTraceMethod(){} @Before(“DebugTraceMethod()”) publicvoidbeforeDebugTraceMethod(JoinPointjoinPoint)throwsThrowable{ Stringkey=joinPoint.getSignature().toString(); Log.e(TAG,“beforeDebugTraceMethod:”+key); }
Call
在AspectJ的切入点表达式中,我们前面都是使用的execution,实际上还有一种类型—call,那么这两种语法有什么区别呢?对call来说:
Call(Before) Pointcut{ PointcutMethod } Call(After)
对Execution来说:
Pointcut{ execution(Before) PointcutMethod execution(After) }
Withincode
这个语法通常来进行一些切入点条件的过滤,作更加精确的切入控制,如下:
publicclassMainActivityextendsAppCompatActivity{ finalStringTAG=MainActivity.class.getSimpleName(); @Orveride protectedvoidonCreate(BundlesavedInstanceState){ super.onCreate(saveInstanceState); setContentView(R.layout.activity_main); aspectJ1(); aspectJ2(); aspectJ3(); } publicvoidaspectJTest(){ Log.e(TAG,”executeaspectJTest"); } publicvoidaspectJ1(){ aspectJTest(); } publicvoidaspectJ2(){ aspectJTest(); } publicvoidaspectJ3(){ aspectJTest(); } }
aspectJ1(),aspectJ2(),aspectJ3()都调用了aspectJTest方法,但只想在aspectJ2调用aspectJTest时插入代码,这个时候就需要使用到Pointcut和withcode组合的方式,来精确定位切入点。
@Pointcut(“(call(**..aspectJTest()))&&withincode(**..aspectJ2())”) publicvoidinvokeAspectJTestInAspectJ2(){ } @Before(“invokeAspectJTestInAspectJ2()”) publicvoidbeforeInvokeaspectJTestInAspectJ2(JoinPointjoinPoint)throwsThrowable{ Log.e(TAG,”method:”+getMethodName(joinPoint).getName()); } privateMethodSignaturegetMethodName(JoinPointjoinPoint){ if(joinPoint==null)returnnull; return(MethodSignature)joinPoint.getSignature(); }
execution语法
execution()是最常用的切点函数,其语法如下所示:
例如下面这段语法:@Around(“execution(**..MainActivity+.on*(..))")
整个表达式可以分为五个部分:
1.execution()是表达式主体
2.第一个*号代表返回类型,*号代表所有的类型。
3.包名表示需要拦截的包名,这里使用*.代表匹配所有的包名。
4.第二个*号表示类名,后面跟.MainActivity是指具体的类名叫MainActivity。
5.*(..)最后这个星号表示方法名,+.代表具体的函数名,*号通配符,包括括弧号里面表示方法的参数,两个dot代表任意参数。
遇到的错误
1.以下错误可以使用gradle2.2.3解决,由于目前还不适配gradle3.0导致的
Error:Executionfailedfortask':app:transformClassesWithDexBuilderForDebug'.
>Unexpectedscopesfoundinfolder'/Users/ram/WorkSpace/AndroidWorkSpace/MyDemo/app/build/intermediates/transforms/AspectTransform/debug'.Required:PROJECT,SUB_PROJECTS,EXTERNAL_LIBRARIES.Found:EXTERNAL_LIBRARIES,PROJECT,PROJECT_LOCAL_DEPS,SUB_PROJECTS,SUB_PROJECTS_LOCAL_DEPS
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。