什么是AOP
AOP是 Aspect Oriented Programming 的缩写,即面向切面编程,和平常遇到的面向对象OOP编程不一样的是,OOP是将功能模块化对象化,AOP是针对同一类的问题统一化处理。例如做日志埋点,性能监控,动态权限控制等。
AspectJ
AspectJ实际上是对AOP编程的实践,目前还有很多的AOP实现,如ASMDex,但笔者选用的是AspectJ。
在Android项目中使用AspectJ
如果使用原生AspectJ在项目中配置会非常麻烦,在GitHub上有个开源的SDK gradle_plugin_android_aspectjx基于gradle配置即可。
接入说明
请自行查看开源项目中的接入配置过程
AspectJ 之 Join Points介绍
Join Points在AspectJ中是关键的概念。Join Points可以看做是程序运行时的一个执行点,比如:一个函数的调用可以看做是个Join Points,相当于代码切入点。但在AspectJ中,只有下面几种执行点是认为是Join Points:
Join Points | 说明 | 实例 |
---|---|---|
method call | 函数调用 | 比如调用Log.e(),这是一个个Join Point |
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 中,对应catch内的执行 |
advice execution | 这个是AspectJ 的内容 | - |
Pointcuts 介绍
一个程序会有多个Join Points,即使同一个函数,也还分为call 和 execution 类型的Join Points,但并不是所有的Join Points 都是我们关心的,Pointcuts 就是提供一种使得开发者能够值选择所需的JoinPoints的方法。
Advice
Advice就是我们插入的代码可以以何种方式插入,有Before 还有 After、Around。
下面看个例子:
@Before(“execution(* android.app.Activity.on**(..)))”) public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable{ }
这里会分成好几个部分,我们依次来看:
Before 和 After 其实还是很好理解的,也就是在Pointcuts之前和之后,插入代码,那么Android呢,从字面含义上来讲,也就是在方法前后各插入代码,他包含了 Before和 After 的全部功能,代码如下:
@(“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()代表执行原始的方法,在这之前、之后,都可以进行各种逻辑处理。
自定义Pointcuts
自定义Pointcuts可以让我们更加精准的切入一个或多个指定的切入点。
首先我们要定义一个注解类
@Retention(RetentionPolicy.CLASS) @Target({ElementType.CONSTRUCTOR, ElementType.METHOD}) public @interface DebugTrace { }
在需要插入代码的地方加入这个注解,例如在MainActivity中加入:
public class MainActivity extends AppCompatActivity{ final String TAG = MainActivity.class.getSimpleName(); @Override protedcted void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); logTest(); } @DebugTrace public void logTest(){ Log.e(TAG,”log test"); } }
最后创建切入代码
@Pointcut(“execution(@com.kun.aspectjtest.aspect.DebugTrace * *..*.*(..))”) public void DebugTraceMethod(){} @Before(“DebugTraceMethod()”) public void beforeDebugTraceMethod(JoinPoint joinPoint) throws Throwable{ String key = joinPoint.getSignature().toString(); Log.e(TAG, “beforeDebugTraceMethod:”+key); }
Call
在AspectJ的切入点表达式中,我们前面都是使用的execution,实际上还有一种类型—call,那么这两种语法有什么区别呢?对call来说:
Call (Before) Pointcut{ Pointcut Method } Call (After)
对Execution来说:
Pointcut{ execution (Before) Pointcut Method execution (After) }
Withincode
这个语法通常来进行一些切入点条件的过滤,作更加精确的切入控制,如下:
public class MainActivity extends AppCompatActivity{ final String TAG = MainActivity.class.getSimpleName(); @Orveride protected void onCreate(Bundle savedInstanceState){ super.onCreate(saveInstanceState); setContentView(R.layout.activity_main); aspectJ1(); aspectJ2(); aspectJ3(); } public void aspectJTest(){ Log.e(TAG,”execute aspectJTest"); } public void aspectJ1(){ aspectJTest(); } public void aspectJ2(){ aspectJTest(); } public void aspectJ3(){ aspectJTest(); } }
aspectJ1(),aspectJ2(),aspectJ3()都调用了aspectJTest方法,但只想在aspectJ2调用aspectJTest时插入代码,这个时候就需要使用到Pointcut和withcode组合的方式,来精确定位切入点。
@Pointcut(“(call(* *..aspectJTest()))&&withincode(* *..aspectJ2())”) public void invokeAspectJTestInAspectJ2(){ } @Before(“invokeAspectJTestInAspectJ2()”) public void beforeInvokeaspectJTestInAspectJ2(JoinPoint joinPoint) throws Throwable{ Log.e(TAG,”method:”+getMethodName(joinPoint).getName()); } private MethodSignature getMethodName(JoinPoint joinPoint){ if(joinPoint == null) return null; 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:Execution failed for task ':app:transformClassesWithDexBuilderForDebug'.
> Unexpected scopes found in folder '/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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。