Android中AOP的实际运用
一、AOP简介
AOP即面向切面编程,区别于OOP(面向对象编程)的功能模块化,AOP主要侧重于解决某一类的问题。曾经我也不知道面向切面到底是切的谁,直到我看到下面这个图才理解。
从上图可以看出,面向切面编程是在不影响原业务功能的情况下,将我们所需要的功能插入进原业务代码中。
通俗的讲,比如在我们的android商城应用中,点击按钮跳转到功能界面时,很多地方都判断用户是否已登录,若用户已登录则跳转到功能界面,一般,我们的代码会这么写:
public void goToFun() {
if(LoginUtil.isLogin()) {
startActivity(new Intent(MainActivity.this, FunctonActivity.class));
} else {
startActivity(new Intent(MainActivity.this, LoginActivity.class));
}
}
而在AOP思想中,代码会写成这样:
@CheckLogin
public void goToFun() {
startActivity(new Intent(MainActivity.this, FunctonActivity.class));
}
乍一看,你可能会觉得代码并没有精简多少,但是若判断场景更加复杂、判断场景更多的时候,这样的写法很显然会非常冗余,并影响阅读体验。AOP的精髓就在于,不影响原代码业务逻辑的情况下,通过编译时注入代码的方式,通用的插入新的代码逻辑,效率高,侵入性低。
二、应用场景
AOP思想是用来解决一系列相同问题的方案,它可以运用的场景很多,比如:日志打印,性能监测,埋点方案等。
三、AOP实现方法
AOP是一种编程思想,实现它的框架有很多,其中最有名的是AspectJ,本文也是用AspectJ框架实现的。
1.AspectJ简介
拥有自己的编译器和语法,可以在编译期间将需要植入的代码编译成Java代码插入到你的源代码文件当中,它是一种几乎和Java完全一样的语言,而且完全兼容Java(AspectJ应该就是一种扩展Java,但它不是像Groovy那样的拓展)。
基础概念
在学习AspectJ之前,要先简单的了解一下几个概念,这个稍微了解一下就好了,不懂也没关系,看后面的示例就好了。
Advice(通知): 注入到class文件中的代码。典型的 Advice 类型有 before、after 和 around,分别表示在目标方法执行之前、执行后和完全替代目标方法执行的代码。 除了在方法中注入代码,也可能会对代码做其他修改,比如在一个class中增加字段或者接口。
Joint point(连接点): 程序中可能作为代码注入目标的特定的点,例如一个方法调用或者方法入口。
Pointcut(切入点): 告诉代码注入工具,在何处注入一段特定代码的表达式。例如,在哪些 joint points 应用一个特定的 Advice。切入点可以选择唯一一个,比如执行某一个方法,也可以有多个选择,比如,标记了一个定义成@DebguTrace 的自定义注解的所有方法。
Aspect(切面):Pointcut 和 Advice 的组合看做切面。例如,我们在应用中通过定义一个 pointcut 和给定恰当的advice,添加一个日志切面。
Weaving(织入): 注入代码(advices)到目标位置(joint points)的过程。
切入点表达式规则
切入点表达式帮助我们定位需要在哪些类和方法上面进行切面,可以指定某一个类,或者某个包下的某一些类,只要满足表达式规则的类,均会被切到。
表达式一般有两种写法,第一种表达式:
execution (public * com.sample.service.impl..*. *(..))
其中:
-
execution()
:表达式主体,必须要写,表达式的条件就从这个主体中判断; -
public
:第一个参数表示作用的方法可见级别,可以省略; - 第一个
*
:表示作用方法的返回值类型,*
表示所有返回值类型; -
com.sample.service.impl
:表示作用的包的路径; -
..*
:表示包下的所有子类及子孙类; -
*(..)
:其中*
表示方法名,(..)
表示任意参数。
因此,第一种表达式表达的意思是:切入点为com.sample.service.impl
包下的所有类及子类中的所有public
方法。
第二种表达式:
execution(@com.zw.kotlindemo.aop.CheckCostTime * *(..))
相比第一种写法,多了一个@com.zw.kotlindemo.aop.CheckCostTime
,后面的* *(..))
和第一种是一样的,多出的部分表示的是切入点的方法必须有此注解标示才能匹配。
2.示例
下面,我们就通过AspectJ来实现一个AOP案例,主要实现功能为,监测Activity的每一个生命周期的调用时间。
2.1 导入依赖
在模块(app)的build.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'
classpath 'org.aspectj:aspectjweaver:1.8.9'
}
}
repositories {
mavenCentral()
}
final def log = project.logger
final def variants = project.android.applicationVariants
variants.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.8",
"-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;
}
}
}
}
并且,在模块的build.gradle
文件中,添加依赖库:
implementation 'org.aspectj:aspectjrt:1.8.9'
2.2 新建注解,表示和我们这次功能相关
@Target(ElementType.METHOD) // 表示作用在方法上
@Retention(RetentionPolicy.RUNTIME) // 表示在代码运行时也生效
public @interface CheckCostTime {
String value() default "unknown";
}
2.3 新建一个Aspect类。
@Aspect
public class CheckCostTimeAspect {
}
2.4 在Aspect类中,声明切入点
@Pointcut("execution(@com.zw.kotlindemo.aop.CheckCostTime * *(..))")
public void executionCostTime() { }
表示从CheckCostTime
这个接口切入,其中,* *
表示可以为任意包名,(..)
表示为任意参数。
2.5 在Aspect类中,声明通知
由于我们要监测方法的执行时间,所以必须在方法开始和方法结束都需要记录时间,因此,我们选用@Around
注解。
@Around("executionCostTime()")
public Object checkCostTime(ProceedingJoinPoint joinPoint) throws Throwable {
// 通过反射获取是否有CheckCostTime注解
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
CheckCostTime checkCostTime = methodSignature.getMethod().getAnnotation(CheckCostTime.class);
if(checkCostTime != null) {
// 被插入的方法执行前
String funName = checkCostTime.value();
long startTime = System.currentTimeMillis();
// 被插入的方法执行时
Object obj = joinPoint.proceed();
// 被插入的方法执行后
long endTime = System.currentTimeMillis();
Log.e("ceshi","function " + funName + " cost: " + (endTime - startTime) + "ms");
return obj;
}
return joinPoint.proceed();
}
Aspect类完整代码
package com.zw.kotlindemo.aop;
import android.util.Log;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
@Aspect
public class CheckCostTimeAspect {
@Pointcut("execution(@com.zw.kotlindemo.aop.CheckCostTime * *(..))")
public void executionCostTime() {
}
@Around("executionCostTime()")
public Object checkCostTime(ProceedingJoinPoint joinPoint) throws Throwable {
// 通过反射获取是否有CheckCostTime注解
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
CheckCostTime checkCostTime = methodSignature.getMethod().getAnnotation(CheckCostTime.class);
if(checkCostTime != null) {
// 被插入的方法执行前
String funName = checkCostTime.value();
long startTime = System.currentTimeMillis();
// 被插入的方法执行时
Object obj = joinPoint.proceed();
// 被插入的方法执行后
long endTime = System.currentTimeMillis();
Log.e("ceshi","function " + funName + " cost: " + (endTime - startTime) + "ms");
return obj;
}
return joinPoint.proceed();
}
}
2.6 在Activity中运用
public class FourActivity extends AppCompatActivity {
@CheckCostTime("onCreate")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_four);
}
@CheckCostTime("onStart")
@Override
protected void onStart() {
super.onStart();
}
@CheckCostTime("onResume")
@Override
protected void onResume() {
super.onResume();
}
@CheckCostTime("onStop")
@Override
protected void onStop() {
super.onStop();
}
@CheckCostTime("onPause")
@Override
protected void onPause() {
super.onPause();
}
@CheckCostTime("onDestroy")
@Override
protected void onDestroy() {
super.onDestroy();
}
}
2.7 打印结果
2019-07-05 15:55:40.271 16583-16583/com.zw.kotlindemo E/ceshi: function onCreate cost: 31ms
2019-07-05 15:55:40.275 16583-16583/com.zw.kotlindemo E/ceshi: function onStart cost: 0ms
2019-07-05 15:55:40.277 16583-16583/com.zw.kotlindemo E/ceshi: function onResume cost: 0ms
2019-07-05 15:55:44.743 16583-16583/com.zw.kotlindemo E/ceshi: function onPause cost: 0ms
2019-07-05 15:55:45.063 16583-16583/com.zw.kotlindemo E/ceshi: function onStop cost: 0ms
2019-07-05 15:55:45.065 16583-16583/com.zw.kotlindemo E/ceshi: function onDestroy cost: 1ms
总结
AOP的优点:
侵入性低:在程序编译时注入代码,不影响原始业务代码;
通用性强:专注解决一系列相同问题,减少代码冗余度;
耦合度低:修改自身业务逻辑不影响原代码的执行。
AOP的缺点:
- 性能问题:由于AOP的实现原理还是通过反射,对代码的执行效率可能会产生影响。