在 AspectJ in Android 使用初探 一 中介绍了基本概念以及如何集成到 Android 项目中,在这里具体化 AspectJ 的具体使用。
在介绍具体的使用方法前,明确几个基本的概念帮助理解
Join Point
就是程序中可能作为代码注入目标的特定的点,例如一个方法调用或者方法入口。被 Join Point 标记的代码位置就是你想把新的代码插在程序的那一个地方,理论上一个程序中很多地方都可以被看做是 Join Point
,但是 AspectJ
中,只有下面几种几种方法可以被标记为 Join Point
。
Join Point | 说明 | 实例 |
---|---|---|
method call | 函数调用 | 比如调用Log.e(),这是一处JPoint |
method execution | 函数执行 | 比如Log.e()的执行内部,是一处JPoint。注意它和method call的区别。method call是调用某个函数的地方。而execution是某个函数执行的内部。 |
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(xxx)中,对应catch内的执行 |
advice execution | 这个是AspectJ的内容,稍后再说 |
上面介绍了 Join Points
,但是在项目中并不是要拿所有的 Joint Points
作切面,关于如何选择 Join Points
作为我们切入点就是现在要谈到的 Point Cuts 概念。
Point Cuts 的基本语法如下:
@注解 | 访问权限 | 返回值类型 | 类名 | 函数名 | 参数 |
---|
具体结合 Join Points
使用方法如下
Joint Point | 说明 | Pointcut语法说明 |
---|---|---|
method execution | 一般指定方法的执行 | execution(MethodSignnature) |
method call | 函数被调用 | call(MethodSignnature) |
constructor call | 构造函数被调用 | call(ConstructorSignature) |
constructor execution | 构造函数执行内部 | execution(ConstructorSignature) |
field get | 读变量 | get(FieldSIgnature) |
field set | 写变量 | set(FieldSIgnature) |
handler | 异常处理 | handler(TypeSignature) 注意:只能和@Before()配合使用,不支持@After、@Around等 |
advice execution | advice执行 | adciceexectuin() |
具体 Signature 参考
Sigbature | 语法(间隔一个空格) |
---|---|
MethodSignature | @注解 访问权限 返回值类型 类名.函数名(参数) |
ConstructorSignature | @注解 访问权限 类名.new(参数) |
FieldSignature | @注解 访问权限 变量类型 类名.类成员变量名 |
相关通配符
通配符 | 意义 | 举个栗子 |
---|---|---|
* | 用于匹配除.号之外的任意字符 | *Model:以Model结尾的子类,比如TabelModel,TreeModel |
.. | 表示任意子package | java..* :表示java任意子类 |
+ | 表示子类 | java..*Model+:表示Java任意package中名字以Model结尾的子类,比如TabelModel,TreeModel |
(..) | 代表参数个数和类型都是任意 |
上面是几种 Point Cuts
的语法,但是就如 Java 语言中的 “&&”、“||”、“!” 一样,各个 Point Cuts
之间可以有以上操作,并且也有一些其他的具体语法进行 条件过滤
, 具体有以下几个操作符可供选择:
关键词 | 说明 | 示例 |
---|---|---|
within(TypePattern) | TypePattern标示package或者类。TypePatter可以使用通配符 | 表示某个Package或者类中的所有JPoint。比如within(Test):Test类中(包括内部类)所有JPoint。图2所示的例子就是用这个方法。 |
withincode(Constructor Signature、Method Signature) | 表示某个构造函数或其他函数执行过程中涉及到的JPoint | withinCode(* TestDerived.testMethod(..)) 表示testMethod涉及的JPoint、withinCode( *.Test.new(..))表示Test构造函数涉及的JPoint |
cflow(pointcuts) | cflow是call flow的意思 cflow的条件是一个pointcut | cflow(call TestDerived.testMethod):表示调用TestDerived.testMethod函数时所包含的JPoint,包括testMethod的call这个JPoint本身 |
cflowbelow(pointcuts) | cflow是call flow的意思。 | cflowblow(call TestDerived.testMethod):表示调用TestDerived.testMethod函数时所包含的JPoint,不包括testMethod的call这个JPoint本身 |
this(Type) | JPoint的this对象是Type类型。(其实就是判断Type是不是某种类型,即是否满足instanceof Type的条件) | JPoint是代码段(不论是函数,异常处理,static block),从语法上说,它都属于一个类。如果这个类的类型是Type标示的类型,则和它相关的JPoint将全部被选中。this(TestDerived)将会选中这个testMethod JPoint |
target(Type) | JPoint的target对象是Type类型 | 和this相对的是target。不过target一般用在call的情况。call一个函数,这个函数可能定义在其他类。比如testMethod是TestDerived类定义的。那么target(TestDerived)就会搜索到调用testMethod的地方。但是不包括testMethod的execution JPoint |
args(TypeSignature) | 用来对JPoint的参数进行条件搜索的 | 比如args(int,..),表示第一个参数是int,后面参数个数和类型不限的JPoint。 |
### 1.3 Advice
具体可以理解为定义操作行为运行在切面的具体位置(前、后、包裹
),具体操作符如下:
关键词 | 说明 | 示例 |
---|---|---|
before() | before advice | 表示在JPoint执行之前,需要干的事情 |
after() | after advice | 表示JPoint自己执行完了后,需要干的事情。 |
返回值类型 around() | before和around是指JPoint执行前或执行后备触发,而around就替代了原JPoint | around是替代了原JPoint,如果要执行原JPoint的话,需要调用proceed |
after():returning(返回值类型)、after():throwing(异常类型) | returning和throwing后面都可以指定具体的类型,如果不指定的话则匹配的时候不限定类型 | 假设JPoint是一个函数调用的话,那么函数调用执行完有两种方式退出,一个是正常的return,另外一个是抛异常。注意,after()默认包括returning和throwing两种情况 |
上面的语法操作符看起来比较抽象,看起来也比较无聊。在 Coding World 中还是需要具体的 Code 来直观的说明一切。具体的代码可以参看 AspectJDemo
针对 Activity 的相关生命周期做切面处理
@PonitCut("execution(* android.app.Activity.onC*(..))")
public void setCut(){}
@Before("setCut()")
public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
String key = joinPoint.getSignature().toString();
Log.e(TAG, "onActivityMethodBefore: 切面的点执行了!" + key);
}
当然你也可以这样写:
@Before("execution(* android.app.Activity.onC*(..))")
public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
String key = joinPoint.getSignature().toString();
Log.e(TAG, "onActivityMethodBefore: 切面的点执行了!" + key);
}
运行程序我们可以看到如下 Log 日志;
当然针对于 Before 、After、Around 、excution 、call 等具体区别此处就不展开写了,毕竟网上的博客写的都差不多,复习此知识点时可浏览参考博客链接。
对申请手机权限的切面做处理,具体代码如下:
注解类
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SecurityCheckAnnotation {
public String declaredPermission();
}
切面处理下关代码:
/**
* 注意: 这里有一个需要注意的点:
* SecurityCheckAnnotation 相应的注解类如果和该类在同包,则可以直接写SecurityCheckAnnotation,
* 如果不在则写SecurityCheckAnnotation全路径
*/
@Pointcut("execution(@com.mk.aline.aspectjdemo.SecurityCheckAnnotation public * *..*.*(..)) && @annotation(ann)")
public void checkPermssion(SecurityCheckAnnotation ann) {
}
/**
* 接下来是advice,advice的真正功能由check函数来实现,这个check函数第二个参数就是我们想要
* 的注解。在实际运行过程中,AspectJ会把这个信息从JPoint中提出出来并传递给check函数。
*/
@After("checkPermssion(securityCheckAnnotation)")
public void check(JoinPoint joinPoint, SecurityCheckAnnotation securityCheckAnnotation) {
//从注解信息中获取声明的权限。
String neededPermission = securityCheckAnnotation.declaredPermission();
Log.e(TAG, joinPoint.toShortString());
Log.e(TAG, "\tneeded permission is " + neededPermission);
return;
}
业务类(进行权限申请)
@SecurityCheckAnnotation(declaredPermission = "android.permission.READ_PHONE_STATE")
public void checkPhoneState() {
//如果不使用AOP,就得自己来检查权限
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED) {
Log.e(TAG, "have no permission to read phone state");
return;
}
Log.e(TAG, "Read Phone State succeed");
return;
}
具体打印日志:
注意: 这里特别需要的一个注意点见上面的注释信息
熟悉了 AspectJ
在 Android
中基本使用,那么具体贴近业务怎样巧妙的使用 AspectJ
才能够达到事半功倍的效果,由于我司的项目中并没有使用到该项技术,下面我贴几个在网上可以看到了实际运用的的帖子,以供参考:
Android中的AOP编程之AspectJ实战实现数据埋点
归纳AOP在Android开发中的几种常见用法
Android基于AOP的非侵入式监控之——AspectJ实战
AOP实现Android集中式登录架构
以上内容为自己的学习笔记,自己会进一步研究 AspectJ 在 Android 项目中的使–用,笔记也会继续更新。
深入理解Android之AOP
看AspectJ在Android中的强势插入
Aspect Oriented Programming in Android
Android中的AOP编程之AspectJ实战实现数据埋点
Android AOP编程的四种策略探讨:Aspectj,cglib+dexmaker,Javassist,epic+dexposed