一、OOP与AOP
作为Android开发者,相信大家对OOP(面向对象编程)不陌生。OOP面向对象编程思想,更习惯于将业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。而AOP,面向切面编程则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。
举个栗子,在日常开发中,我们经常需要判断手机网络状态、用户登录状态、埋点日志等。
如果按照OOP的编程思想,则我们需要在每个需要以上操作的地方添加执行代码。如果将些逻辑判断的过程抽象为一个横切面,用很简单的方法插入到业务流程中,将带来更高的效率和清晰的结构。
二、AOP的使用场景
AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理,拦截分发,Hook等等。
三、重要概念
结合下面这张图便于理解:
-
Cross-cutting concerns(横切关注点): 尽管面向对象模型中大多数类会实现单一特定的功能,但通常也会开放一些通用的附属功能给其他类。例如,我们希望在数据访问层中的类中添加日志,同时也希望当UI层中一个线程进入或者退出调用一个方法时添加日志。尽管每个类都有一个区别于其他类的主要功能,但在代码里,仍然经常需要添加一些相同的附属功能。
-
Advice(通知): 注入到class文件中的代码。典型的 Advice 类型有 before、after 和 around,分别表示在目标方法执行之前、执行后和完全替代目标方法执行的代码。 除了在方法中注入代码,也可能会对代码做其他修改,比如在一个class中增加字段或者接口。
-
Joint point(连接点): 程序中可能作为代码注入目标的特定的点,例如一个方法调用或者方法入口。
-
Pointcut(切入点): 告诉代码注入工具,在何处注入一段特定代码的表达式。例如,在哪些 joint points 应用一个特定的 Advice。切入点可以选择唯一一个,比如执行某一个方法,也可以有多个选择,比如,标记了一个定义成@DebguTrace 的自定义注解的所有方法。
-
Aspect(切面): Pointcut 和 Advice 的组合看做切面。例如,我们在应用中通过定义一个 pointcut 和给定恰当的advice,添加一个日志切面。
-
Weaving(织入): 注入代码(advices)到目标位置(joint points)的过程
(如果现在还不是很理解,没关系,结合下面代码很容易明白)
四、AOP编程实战
1、引入方式1
直接引入aspectj,在moudle下配置build.gradle
apply plugin: 'com.android.application'
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;
}
}
}
}
android {
compileSdkVersion 27
......
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.1.1'
......
compile 'org.aspectj:aspectjrt:1.8.9'
}
这种方式稍显麻烦,其实还有一个更简便的引入方式(前人栽树后人乘凉,感觉前辈!)
2、引入方法2
- 在项目根目录下的build.gradle中,添加依赖:
dependencies {
......
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0'
}
- 在app的build.gradle中,添加:
apply plugin: 'android-aspectjx'
dependencies {
......
implementation 'org.aspectj:aspectjrt:1.8.9'
}
3、编写类
1)自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckNetwork {
}
2)创建切面类
@Aspect
public class CheckNetworkAspect {
int count = 0;
@Pointcut("execution(@com.kwmax.alltestdemo.aop.CheckNetwork * *(..))")
public void execCheckNetwork(){
}
@Around("execCheckNetwork()")
public Object checkNetwork(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
CheckNetwork annotation = signature.getMethod().getAnnotation(CheckNetwork.class);
if (annotation != null) {
Context context = getContext(joinPoint.getThis());
if (NetWorkUtils.isNetworkConnected(context)) {
Toast.makeText(context, count+"当前网络正常", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, count+"此时没有网络连接", Toast.LENGTH_SHORT).show();
}
count++;
return joinPoint.proceed();
}
return null;
}
/**
* 通过对象获取上下文
*/
private Context getContext(Object object) {
if (object instanceof Activity) {
return (Activity) object;
} else if (object instanceof Fragment) {
Fragment fragment = (Fragment) object;
return fragment.getActivity();
} else if (object instanceof android.app.Fragment) {
android.app.Fragment fragment = (android.app.Fragment) object;
return fragment.getActivity();
} else if (object instanceof View) {
View view = (View) object;
return view.getContext();
}
return null;
}
}
需要注意的是:
@Pointcut来标识所要寻找的切点,* *(..) “**”表示是任意包名 “..”表示任意类型任意多个参数。
这里切入点就为我们的自定义注解的方法,所以要填写自定义注解的路径。
@Around()此处写的是@Pointcut注解下的方法名,要一致。除此之外,还有@Before和@After注解可用,代表在方法前插入代码和方法后插入代码
如果在自定义的注解中定义了方法,通过如下可以拿到
String content = annotation.value();
int type = annotation.type();
自定义注解上的参数赋值如下
@BehaviorTrace(value = "打开首页", type = 1)
public void behaviorTrace(){
Log.i(TAG,"用户行为检查");
}
4、测试
我们在执行方法之前检查网络状态:
@CheckNetwork
private void test() {
//测试方法
}