在Android项目中使用AspectJ
版权声明:本文为博主原创文章,未经博主允许不得转载。
什么是AOP
AOP是 Aspect Oriented Programming 的缩写,即面向切面编程,和平常遇到的面向对象OOP编程不一样的是,OOP是将功能模块化对象化,AOP是针对同一类的问题统一化处理。例如做日志埋点,性能监控,动态权限控制等。
AspectJ
AspectJ实际上是对AOP编程的实践,目前还有很多的AOP实现,如ASMDex,但笔者选用的是AspectJ。
在Android项目中使用AspectJ
如果使用原生AspectJ在项目中配置会非常麻烦,在GitHub上有个开源的SDK gradle_plugin_android_aspectjx基于gradle配置即可。
接入说明
请自行查看开源项目中的接入配置过程
AspectJ 之 Join Points介绍
Join Points在AspectJ中是关键的概念。Join Points可以看做是程序运行时的一个执行点,比如:一个函数的调用可以看做是个Join Points,相当于代码切入点。但在AspectJ中,只有下面几种执行点是认为是Join Points:
Join Points | 说明 | 实例 |
---|---|---|
method call | 函数调用 | 比如调用Log.e(),这是一个个Join Point |
method execution | 函数执行 | 比如Log.e()的执行内部,是一处Join Points。注意这里是函数内部 |
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 中,对应catch内的执行 |
advice execution | 这个是AspectJ 的内容 | - |
Pointcuts 介绍
一个程序会有多个Join Points,即使同一个函数,也还分为call 和 execution 类型的Join Points,但并不是所有的Join Points 都是我们关心的,Pointcuts 就是提供一种使得开发者能够值选择所需的JoinPoints的方法。
Advice
Advice就是我们插入的代码可以以何种方式插入,有Before 还有 After、Around。
下面看个例子:
@Before(“execution(* android.app.Activity.on**(..)))”)
public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable{
}
这里会分成好几个部分,我们依次来看:
- @Before: Advice, 也就是具体的插入点
- execution:处理Join Point的类型,例如call、execution
(* android.app.Activity.on**(..))
: 这个是最重要的表达式,第一个*
表示返回值,*
表示返回值为任意类型,后面这个就是典型的包名路径,其中可以包含*
来进行通配,几个*
没有区别。同时这里可以通过&&、||、!
来进行条件组合。()代表这个方法的参数,你可以指定类型,例如android.os.Bundle,或者 (..) 这样来代表任意类型、任意个数的参数。- public void onActivityMehodBefore: 实际切入的代码。
Before 和 After 其实还是很好理解的,也就是在Pointcuts之前和之后,插入代码,那么Android呢,从字面含义上来讲,也就是在方法前后各插入代码,他包含了 Before和 After 的全部功能,代码如下:
@(“execution(* com.xys.aspectjxdemo.MainActivity.testAOP()))”)
public void onActivityMethodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
String key = proceedingJoinPoint.getSignature().toString();
Log.d(TAG,”onActivityMethodAroundFirst:”+key);
proceedingJoinPoint.proceed();
Log.d(TAG,”onActivityMethodAroundSecond:”+key);
}
以上代码中,proceedingJoinPoint.proceed()代表执行原始的方法,在这之前、之后,都可以进行各种逻辑处理。
自定义Pointcuts
自定义Pointcuts可以让我们更加精准的切入一个或多个指定的切入点。
首先我们要定义一个注解类
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface DebugTrace {
}
在需要插入代码的地方加入这个注解,例如在MainActivity中加入:
public class MainActivity extends AppCompatActivity{
final String TAG = MainActivity.class.getSimpleName();
@Override
protedcted void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
logTest();
}
@DebugTrace
public void logTest(){
Log.e(TAG,”log test");
}
}
最后创建切入代码
@Pointcut(“execution(@com.kun.aspectjtest.aspect.DebugTrace * *..*.*(..))”)
public void DebugTraceMethod(){}
@Before(“DebugTraceMethod()”)
public void beforeDebugTraceMethod(JoinPoint joinPoint) throws Throwable{
String key = joinPoint.getSignature().toString();
Log.e(TAG, “beforeDebugTraceMethod:”+key);
}
Call
在AspectJ的切入点表达式中,我们前面都是使用的execution,实际上还有一种类型—call,那么这两种语法有什么区别呢?对call来说:
Call (Before)
Pointcut{
Pointcut Method
}
Call (After)
对Execution来说:
Pointcut{
execution (Before)
Pointcut Method
execution (After)
}
Withincode
这个语法通常来进行一些切入点条件的过滤,作更加精确的切入控制,如下:
public class MainActivity extends AppCompatActivity{
final String TAG = MainActivity.class.getSimpleName();
@Orveride
protected void onCreate(Bundle savedInstanceState){
super.onCreate(saveInstanceState);
setContentView(R.layout.activity_main);
aspectJ1();
aspectJ2();
aspectJ3();
}
public void aspectJTest(){
Log.e(TAG,”execute aspectJTest");
}
public void aspectJ1(){
aspectJTest();
}
public void aspectJ2(){
aspectJTest();
}
public void aspectJ3(){
aspectJTest();
}
}
aspectJ1(),aspectJ2(),aspectJ3()都调用了aspectJTest方法,但只想在aspectJ2调用aspectJTest时插入代码,这个时候就需要使用到Pointcut和withcode组合的方式,来精确定位切入点。
@Pointcut(“(call(* *..aspectJTest()))&&withincode(* *..aspectJ2())”)
public void invokeAspectJTestInAspectJ2(){
}
@Before(“invokeAspectJTestInAspectJ2()”)
public void beforeInvokeaspectJTestInAspectJ2(JoinPoint joinPoint) throws Throwable{
Log.e(TAG,”method:”+getMethodName(joinPoint).getName());
}
private MethodSignature getMethodName(JoinPoint joinPoint){
if(joinPoint == null) return null;
return (MethodSignature) joinPoint.getSignature();
}
execution 语法
execution()是最常用的切点函数,其语法如下所示:
例如下面这段语法:
@Around(“execution(* *..MainActivity+.on*(..))")
整个表达式可以分为五个部分:
- execution()是表达式主体
- 第一个
*
号代表返回类型,*号代表所有的类型。 - 包名 表示需要拦截的包名,这里使用*.代表匹配所有的包名。
- 第二个*号表示类名,后面跟.MainActivity是指具体的类名叫MainActivity。
*(..)
最后这个星号表示方法名,+.代表具体的函数名,*
号通配符,包括括弧号里面表示方法的参数,两个dot代表任意参数。
遇到的错误
- 以下错误可以使用gradle2.2.3解决,由于目前还不适配gradle3.0导致的
Error:Execution failed for task ':app:transformClassesWithDexBuilderForDebug'.
> Unexpected scopes found in folder '/Users/ram/WorkSpace/AndroidWorkSpace/MyDemo/app/build/intermediates/transforms/AspectTransform/debug'. Required: PROJECT, SUB_PROJECTS, EXTERNAL_LIBRARIES. Found: EXTERNAL_LIBRARIES, PROJECT, PROJECT_LOCAL_DEPS, SUB_PROJECTS, SUB_PROJECTS_LOCAL_DEPS
在Android项目中使用AspectJ的更多相关文章
- android项目中配置NDK自动编译生成so文件
1 下载ndk开发包 2 在android 项目中配置编译器(以HelloJni项目为例) 2.1 创建builer (a)Project->Properties->Builder ...
- Android 项目中文件夹的说明与作用(转)
(转自:http://blog.csdn.net/goodshot/article/details/11529731) Android 项目中文件夹的作用 1. src:存放所有的*.java源程序. ...
- android XMl 解析神奇xstream 一: 解析android项目中 asset 文件夹 下的 aa.xml 文件
简介 XStream 是一个开源项目,一套简单实用的类库,用于序列化对象与 XML 对象之间的相互转换. 将 XML 文件内容解析为一个对象或将一个对象序列化为 XML 文件. 1.下载工具 xstr ...
- 对Android项目中的文件夹进行解释
对Android项目中的文件夹进行解释: · src:里面存放的是Activity程序,或者是以后的其他组件,在此文件夹之中建立类的时候一定要注意,包名称不能是一级. · gen:此文件夹中的内容是自 ...
- android项目中如何加载已有so库 <转>
1,在项目根目录下建立文件夹libs/armeabi文件夹 2,将so库放入 libs/armeabi文件夹 注意事项: 1,如果采用静态注册的方式请注意C文件中严格按照命名规则 Java_packa ...
- 在Android项目中启用Java 8的部分特性--Lambda & Method References
Android N发布时同时发布了一个新的编译工具Jack(AS2.1+支持),基于Jack我们可以使用Java 8 的部分特性,在低版本机器上能使用的更少,同时Jack也有诸多不完善,工具链的改变难 ...
- Android项目中如何用好构建神器Gradle?(转)
最近在忙团队并行开发的事情,主要是将各个团队的代码分库,一方面可以降低耦合,为后面模块插件化做铺垫,另一方面采用二进制编译,可以加快编译速度.分库遇到了一些问题,很多都要通过Gradle脚本解决,所以 ...
- android项目中刷新activity界面
android项目中在sqlite数据库插入/更新/删除数据后: 1. 刷新当前activity界面数据(手动刷新): 在activity类下新增一个refresh()方法: /** * 刷新, 这样 ...
- 举例android项目中的string.xml出现这个The character reference must end with the ';' delimiter.错误提示的原因及解决办法
今天在一个android项目中的string.xml中写这样一个字符串时出现了下面这个错误提示: The reference to entity "说明" must end wit ...
随机推荐
- vuex 中五大核心以及map函数的应用
什么是vuex? 我理解的vuex就是数据和状态的管理 如果在模块化构建系统中,请确保在开头调用了 Vue.use(Vuex) 五大核心: const store = new Vuex.Store({ ...
- KD-树(下)
来自于https://zhuanlan.zhihu.com/p/23966698 构造 kd 树的例子 上面抽象的定义和算法确实是很不好理解,举一个例子会清楚很多.首先随机在 中随机生成 13 个点作 ...
- 【NIFI】 Apache NiFI 安装及简单的使用
NiFI介绍 NiFi(NiagaraFiles)是为了实现系统间数据流的自动化而构建的.虽然术语“数据流”用于各种上下文,但我们在此处使用它来表示系统之间的自动和管理信息流 官网地址:http:// ...
- vue子传父多个值
子组件:this.$emit("methdosName",data1,data2,data3) 父组件: <child @methodsName="xxx(argu ...
- MySQL缓存参数优化(转)
MySQL 数据库性能优化之缓存参数优化 数据库属于 IO 密集型的应用程序,其主要职责就是数据的管理及存储工作.而我们知道,从内存中读取一个数据库的时间是微秒级别,而从一块普通硬盘上读取一个IO是在 ...
- PLL与PHY的连接:通道绑定或者不绑定
用到的术语: clock skew的产生 延时与时钟线的长度及被时钟线驱动的时序单元的负载电容.个数有关 由于时钟线长度及负载不同,导致时钟信号到达相邻两个时序单元的时间不同 于是产生所谓的clock ...
- C++基础笔记(int转string)
int a = 23; stringstream ss; ss << a; string s1 = ss.str(); 头文件需添加#include "sstream"
- php上传图片预览,放大,裁剪
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- delegate的Invoke和BeginInvoke方法
C#中的控件和delegate委托方法都有Invoke和BeginInvoke方法,控件的这两个方法网上讲得很多, 这里就不多说了,下面讲一下delegate的Invoke和BeginInvoke方法 ...
- 如何设置 sass 全局变量,js如何使用 sass 变量
关键词:sass全局变量 js引用sass变量 1 如何在样式中使用 scss 的声明的全局变量 假设我们有一个全局的 scss 变量文件/styles/_vars.sass,如下: $red: re ...