Android切面编程主要是使用 AspectJ 只是其中的一种类型,事实上 AspectJ 是通过注解的形式来标注切入点,切入对象等,然后在代码编译期间将代码织入到 java字节码中, 从而实现切面编程意义。
AOP是一个概念,并没有设定具体语言的实现,它能克服那些只有单继承特性语言的缺点(如Java),AOP具体实现有很多种方式,AspectJ 只是其中一种。
AspectJ通过注解的形式来标注切入点、切入对象等,然后在代码编译期间将代码织入到java的字节码中。
Android使用 AspectJ 的步骤
1: AspectJ 注解
@Aspect
该注解用来标注一个类,标明当前类是切面类,以便AspectJ能够识别。如:
@Aspect
public class FragmentAspectJInjector {
}
@Pointcut
标注一个方法,用来定义切点,参数为切点表达式。如定义一个切点是在Fragment的onResume生命周期方法执行的时候:
@Pointcut("execution(* android.app.Fragment+.onResume(..))")
public void fragmentOnResumePointcut() {
}
@Around,@Before,@After
标注一个方法,定义具体织入的代码,参数为切点表达式。 这里可以使用“&&、||、!”来组合不同的Pointcut定义。如我们在Fragment的onResume生命周期前打印下当前的Fragment对象:
@Around("fragmentOnResumePointcut()")
public void fragmentOnResume(final ProceedingJoinPoint joinPoint) throws Throwable {
Object target = joinPoint.getTarget();
Log.e(TAG, "fragmentOnResume: fragment = " + target);
joinPoint.proceed();
}
AspectJ 注解还有很多,如@AfterReturning
、@AfterThrowing
等,这里不再一一列举,大家有兴趣可以查看官方文档。上述这些注解基本已经可以满足我们的需求了。
第1步:新建一个Android project
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<FrameLayout
android:id="@+id/fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getFragmentManager().beginTransaction().replace(R.id.fragment, new BlankFragment()).commit();
}
}
第2步:添加AspectJX依赖
根据AspectJX的github文档,我们在项目根目录的build.gradle里添加依赖
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.0-alpha07'
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.8'
}
}
在APP module中的build.gradle里应用插件
apply plugin: 'android-aspectjx'
第3步:添加切面类
@Aspect
public class FragmentAspectJInjector {
private static final String TAG = "FragmentAspectJInjector";
@Pointcut("execution(* android.app.Fragment+.onResume(..))")
public void fragmentOnResumePointcut() {
}
@Around("fragmentOnResumePointcut()")
public void fragmentOnResume(final ProceedingJoinPoint joinPoint) throws Throwable {
Object target = joinPoint.getTarget();
Log.e(TAG, "fragmentOnResume: fragment = " + target);
joinPoint.proceed();
}
第4步:构建并运行APP
通过logcat窗口可以看到如下输出:可以看到我们的切点已经织入成功了。
E/FragmentAspectJInjector: fragmentOnResume: fragment = BlankFragment{8fafb1e #1 id=0x7f0800a4}\
AspectJ 缺点
- 如果相应的class没有实现相应的切点方法将无法织入,如上文中的没有BlankFragment实现onResume方法的话,将无法织入代码。
- 无法处理Lambda语法
- 会有一系列兼容性问题,如R8、gradle版本不同等
- 性能较差,APP项目比较大时编译时间明显加长。