AspectJ 是 Android平台上一种比较高效和简单的实现编译时AOP技术的方案。java
百度百科定义:在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,经过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP能够对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度下降,提升程序的可重用性,同时提升了开发的效率。android
简单的来说,AOP是一种:能够在不改变原来代码的基础上,经过“动态注入”代码,来改变原来执行结果的技术。git
app/build.grade加入如下配置项:github
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.1'
}
}
repositories {
mavenCentral()
}
android {
...
}
dependencies {
...
compile 'org.aspectj:aspectjrt:1.8.1'
}
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.5",
"-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;
}
}
}
}复制代码
JPoint的分类以及对应的Pointcut以下所示:
编程
Pointcut中的Signature以下所示:
缓存
Pointcut中的Signature由一段表达式组成,每一个关键词之间都有空格,如下是对关键词的说明:
bash
如下是Advice用法说明:
并发
间接JPoint高级语法,以下所示:
app
首先定义两个Model类,验证结果用,代码以下:框架
StuModel:
package com.yy.live.aoptraining.model;
import android.util.Log;
/**
* @className: StuModel
* @classDescription: stu model for aspectj
* @author: leibing
* @email: leibing@yy.com
* @createTime:2017/11/3
*/
public class StuModel {
// TAG
private final static String TAG = "AOP StuModel";
// stu name
private String stuName;
static {
Log.e(TAG, " StuModel static block");
}
/**
* construction
*
* @param stuName
*/
public StuModel(String stuName) {
this.stuName = stuName;
Log.e(TAG, " StuModel Construction");
}
/**
* get stu name
*
* @return
*/
public String getStuName() {
Log.e(TAG, " get stu name");
return stuName;
}
/**
* set stu name
*
* @param stuName
*/
public void setStuName(String stuName) {
this.stuName = stuName;
Log.e(TAG, " set stu name");
}
/**
* create throws
*/
public void createThrows(){
Log.e(TAG, " createThrows");
try {
String a = null;
a.toString();
}catch (Exception ex){
ex.printStackTrace();
}
}
}复制代码
UserModel:
package com.yy.live.aoptraining.model;
import android.util.Log;
/**
* @className: UserModel
* @classDescription: user model for aspectj
* @author: leibing
* @email: leibing@yy.com
* @createTime:2017/11/3
*/
public class UserModel {
// TAG
private final static String TAG = "AOP UserModel";
// user name
private String userName;
static {
Log.e(TAG, " UserModel static block");
}
/**
* construction
*
* @param userName
*/
public UserModel(String userName) {
this.userName = userName;
Log.e(TAG, " UserModel Construction");
}
/**
* get user name
*
* @return
*/
public String getUserName() {
Log.e(TAG, " get user name");
return this.userName;
}
/**
* set user name
*
* @param userName
*/
public void setUserName(String userName) {
this.userName = userName;
Log.e(TAG, " set user name");
}
/**
* work
*/
public void work() {
Log.e(TAG, " work");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
/**
* create throws
*/
public void createThrows(){
Log.e(TAG, " createThrows");
try {
Integer.parseInt("abc");
}catch (Exception ex){
ex.printStackTrace();
}
}
}复制代码
构造函数被调用
/**
* 构造函数被调用
*/
@Pointcut("call(com.yy.live.aoptraining.model..*.new(..))")
public void callConstructor() {
}
/**
* 执行(构造函数被调用)JPoint以前
*
* @param joinPoint
*/
@Before("callConstructor()")
public void beforeConstructorCall(JoinPoint joinPoint) {
Log.e(TAG, " before->" + joinPoint.getThis().toString() + "#" + joinPoint.getSignature().getName());
}
/**
* 执行(构造函数被调用)JPoint以后
*
* @param joinPoint
*/
@After("callConstructor()")
public void afterConstructorCall(JoinPoint joinPoint) {
Log.e(TAG, " after->" + joinPoint.getThis().toString() + "#" + joinPoint.getSignature().getName());
}复制代码
结果
11-09 11:26:39.840 18747-18747/com.yy.live.aoptraining E/AOP ConstructorAspect: before->com.yy.live.aoptraining.constructor.ConstructorActivity@54b5f45#<init>
11-09 11:26:39.842 18747-18747/com.yy.live.aoptraining E/AOP UserModel: UserModel static block
11-09 11:26:39.842 18747-18747/com.yy.live.aoptraining E/AOP UserModel: UserModel Construction
11-09 11:26:39.842 18747-18747/com.yy.live.aoptraining E/AOP ConstructorAspect: after->com.yy.live.aoptraining.constructor.ConstructorActivity@54b5f45#<init>复制代码
从上面结果能够看到在UserModel构造函数以前后分别插入了相关的日志,从而实现了对构造函数被调用AOP处理。
构造函数执行内部
/**
* 构造函数执行内部
*/
@Pointcut("execution(com.yy.live.aoptraining.model..*.new(..))")
public void executionConstructor() {}
/**
* (构造函数执行内部)替换原来的代码,若是要执行原来的代码,需使用joinPoint.proceed(),不能和Before、After一块儿使用
* @param joinPoint
* @throws Throwable
*/
@Around("executionConstructor()")
public void aroundConstructorExecution(ProceedingJoinPoint joinPoint) throws Throwable {
Log.e(TAG, " around->" + joinPoint.getThis().toString() + "#" + joinPoint.getSignature().getName());
// 执行原代码
joinPoint.proceed();
}复制代码
结果
11-09 11:32:38.677 24213-24213/com.yy.live.aoptraining E/AOP UserModel: UserModel static block
11-09 11:32:38.678 24213-24213/com.yy.live.aoptraining E/AOP ConstructorAspect: around->com.yy.live.aoptraining.model.UserModel@379a83e#<init>
11-09 11:32:38.678 24213-24213/com.yy.live.aoptraining E/AOP UserModel: UserModel Construction
11-09 11:32:38.679 24213-24213/com.yy.live.aoptraining E/AOP StuModel: StuModel static block
11-09 11:32:38.679 24213-24213/com.yy.live.aoptraining E/AOP ConstructorAspect: around->com.yy.live.aoptraining.model.StuModel@a03b69f#<init>
11-09 11:32:38.679 24213-24213/com.yy.live.aoptraining E/AOP StuModel: StuModel Construction复制代码
从上面结果能够看到在UserModel、StuModel构造函数以前分别插入了相关的日志,从而实现了对构造函数执行内部AOP处理,@Around实现了和@Before、@Afrer同样的效果,可是与其不能共用。
此处粗略说下读取属性,代码以下:
/**
* 读变量
*/
@Pointcut("get(String com.yy.live.aoptraining.model.UserModel.userName)")
public void getField() {
}
/**
* (读变量)替换原来的代码,若是要执行原来的代码,需使用joinPoint.proceed(),不能和Before、After一块儿使用
*
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("getField()")
public String aroundFieldGet(ProceedingJoinPoint joinPoint) throws Throwable {
// 执行原代码
Object obj = joinPoint.proceed();
String userName = obj.toString();
Log.e(TAG, " around->userName = " + userName);
// 可在此处偷天换日更改类原有属性的值
return "李四";
}复制代码
结果
11-09 11:46:03.788 3942-3942/com.yy.live.aoptraining E/AOP UserModel: UserModel static block
11-09 11:46:03.788 3942-3942/com.yy.live.aoptraining E/AOP UserModel: UserModel Construction
11-09 11:46:03.788 3942-3942/com.yy.live.aoptraining E/AOP UserModel: get user name
11-09 11:46:03.789 3942-3942/com.yy.live.aoptraining E/AOP FieldAspect: around->userName = 张三
11-09 11:46:03.789 3942-3942/com.yy.live.aoptraining E/AOP FieldActivity: userName = 李四复制代码
结果显示已完美动态更改了属性值。
话很少说,show code:
/**
* 函数被调用
*/
@Pointcut("call(* com.yy.live.aoptraining.model.UserModel.**(..))")
public void callMethod() {
}
/**
* 执行(函数被调用)JPoint以前
*
* @param joinPoint
*/
@Before("callMethod()")
public void beforeMethodCall(JoinPoint joinPoint) {
Log.e(TAG, " before->" + joinPoint.getTarget().toString() + "#" + joinPoint.getSignature().getName());
beforeTarget = joinPoint.getTarget().toString();
beforeSignatureName = joinPoint.getSignature().getName();
beforeTime = System.currentTimeMillis();
}
/**
* 执行(函数被调用)JPoint以后
*
* @param joinPoint
*/
@After("callMethod()")
public void afterMethodCall(JoinPoint joinPoint) {
Log.e(TAG, " after->" + joinPoint.getTarget().toString() + "#" + joinPoint.getSignature().getName());
afterTarget = joinPoint.getTarget().toString();
afterSignatureName = joinPoint.getSignature().getName();
afterTime = System.currentTimeMillis();
if (afterTarget != null
&& afterSignatureName != null
&& afterTarget.equals(beforeTarget)
&& afterSignatureName.equals(beforeSignatureName)) {
long castTime = afterTime - beforeTime;
Log.e(TAG, " monitor->" + joinPoint.getTarget().toString() + "#" + joinPoint.getSignature().getName() + "#cost " + castTime + " ms");
}
}
/**
* 替换原方法返回值
* 注:@Pointcut能够不单独定义方法,直接使用,以下:
*
* @param joinPoint
* @return
* @throws Throwable
* @Around("execution(* com.yy.live.aoptraining.model.UserModel.getUserName(..))")
*/
@Around("execution(* com.yy.live.aoptraining.model.UserModel.getUserName(..))")
public String aroundGetUserNameMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable {
String originUserName = joinPoint.proceed().toString();
Log.e(TAG, " origin userName = " + originUserName);
// 此处可对原方法作偷天换日处理
return "王五";
}复制代码
结果
11-09 12:02:50.681 18513-18513/com.yy.live.aoptraining E/AOP MethodAspect: before->com.yy.live.aoptraining.model.UserModel@33363ec#work
11-09 12:02:50.681 18513-18513/com.yy.live.aoptraining E/AOP UserModel: work
11-09 12:02:52.684 18513-18513/com.yy.live.aoptraining E/AOP MethodAspect: after->com.yy.live.aoptraining.model.UserModel@33363ec#work
11-09 12:02:52.684 18513-18513/com.yy.live.aoptraining E/AOP MethodAspect: monitor->com.yy.live.aoptraining.model.UserModel@33363ec#work#cost 2003 ms复制代码
结果显示在方法执行先后作了AOP日志插入并统计了该方法在主线程耗时时间。
AOP的使用场景包括异常处理、统计异常,代码以下:
/**
* 异常处理,用于统计全部出现Exception的点
* 不支持@After、@Around
*/
@Before("handler(java.lang.Exception)")
public void handler() {
Log.e(TAG, " handler");
}
/**
* 异常退出,用于收集抛出异常的方法信息
* @AfterThrowing
* @param throwable
*/
@AfterThrowing(pointcut = "call(* *..*(..))", throwing = "throwable")
public void anyFuncThrows(Throwable throwable) {
Log.e(TAG, " Throwable: ", throwable);
}复制代码
结果
11-09 12:10:55.419 25880-25880/com.yy.live.aoptraining E/AOP UserModel: createThrows
11-09 12:10:55.420 25880-25880/com.yy.live.aoptraining E/AOP MethodAspect: Throwable:
java.lang.NumberFormatException: For input string: "abc"
at java.lang.Integer.parseInt(Integer.java:521)
at java.lang.Integer.parseInt(Integer.java:556)
at com.yy.live.aoptraining.model.UserModel.createThrows(UserModel.java:79)
at com.yy.live.aoptraining.method.MethodActivity.onCreate(MethodActivity.java:28)
at android.app.Activity.performCreate(Activity.java:6910)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1123)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2746)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2864)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1567)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:156)
at android.app.ActivityThread.main(ActivityThread.java:6577)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:942)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:832)
11-09 12:10:55.420 25880-25880/com.yy.live.aoptraining E/AOP MethodAspect: handler复制代码
经过AOP能够统计对应的异常状况而且将对应的异常放到一个统一的地方集中处理。
此处用一个6.0版本以上动态权限申请做为示例,首先写一个动态权限注解接口,代码以下:
package com.yy.live.aoptraining.permission;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @className: YPermission
* @classDescription: 动态权限申请注解
* @author: leibing
* @email: leibing@yy.com
* @createTime:2017/11/3
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface YPermission {
String value();
}复制代码
接着写对应的Aspect,代码以下:
package com.yy.live.aoptraining.permission;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.util.Log;
import com.yy.live.aoptraining.AppManager;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
/**
* @className: PermissionAspect
* @classDescription: permission aspectj
* @author: leibing
* @email: leibing@yy.com
* @createTime:2017/11/3
*/
@Aspect
public class PermissionAspect {
// TAG
private final static String TAG = "AOP PermissionAspect";
/**
* 函数执行内部(采用注解动态权限处理)
*
* @param permission
*/
@Pointcut("execution(@com.yy.live.aoptraining.permission.YPermission * *(..)) && @annotation(permission)")
public void methodAnnotatedWithMPermission(YPermission permission) {
}
/**
* (函数执行内部(采用注解动态权限处理))替换原来的代码,若是要执行原来的代码,需使用joinPoint.proceed(),不能和Before、After一块儿使用
*
* @param joinPoint
* @param permission
* @throws Throwable
*/
@Around("methodAnnotatedWithMPermission(permission)")
public void checkPermission(final ProceedingJoinPoint joinPoint, YPermission permission) throws Throwable {
Log.e(TAG, " checkPermission");
// 权限
String permissionStr = permission.value();
// 模拟权限申请
if (AppManager.getInstance().currentActivity() != null) {
new AlertDialog.Builder(AppManager.getInstance().currentActivity()).setTitle("提示")
.setMessage(permissionStr)
.setNegativeButton("取消", null)
.setPositiveButton("容许", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.e(TAG, " checkPermission allow");
try {
// 继续执行原方法
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}).create().show();
}
}
}复制代码
代码注释也比较详细,不细讲了。
本文粗略的描述了Aspectj一些基本的用法、应用场景并作了示例分析。一提及AOP,你们想到的就是埋点、埋点、仍是埋点,埋点只是AOP最基础的功能罢了,还有不少更高级的用法:性能监控、权限验证、数据校验、缓存、其余(项目中特别的一些需求)。目前我就遇到两个问题,就是接收到广播出现屡次重复的问题,因而就想办法去过滤,因而就想到了用Handler作延迟处理,效果不太理想,虽然解决了一时的问题,而且还有个问题就是每一个广播接收器处都要写一遍,代码有点冗余,若是此处采用AOP就很是简单了,写一个注解,采用相似动态权限申请的方式去作一个统一处理就较完美的解决了这个问题,之后维护起来也很方便;另一个是点击按钮的时候,有时会屡次触发事件,这种状况会引起并发执行的相关bug,因而就是在点击的时候设置按钮为不可点击,逻辑处理完再设置为可点击,而后每一个这样的事件都要写一遍,若采用AOP,则所有集中处理了。相似的问题,应该还有挺多的。也许你们在担忧采用Aspectj会带来相关问题:性能问题?这个不用担忧,Aspectj是属于编译时的,不会对app性能形成影响;增长apk包大小?
反编译任意主流apk去看,apk包中代码永远是占据小部分大小,资源 + so包等才是重心,去查看了Aspectj编译时插入的代码(4KB)占apk大小(1.5MB),几乎微乎其微,基本没影响;插件不支持multiple dex,插件方法数超65535?反编译查看代码发现使用Aspectj切入的页面,只生成了一个ajc$preClinit初始化切点的方法,这对插件方法数的影响微乎其微。综上述得之,使用Aspectj会带来更多的便捷,提升工做效率,下降维护成本。