在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 ...
随机推荐
- python07 函数式编程
1.作用域 1.1 pass关键字表示,暂时不写该方法 1.2表示返回值为方法 输出结果333 1.3函数作用域:和函数调用没关系,和声明的位置有关系, 结果为444 2.匿名函数 lanmbda ...
- Spring 框架XML文件的配置文件
<?xml version="1.0" encoding="UTF-8"?><beans xmlns:xsi="http://www ...
- idea执行mapreduce报错 Could not locate Hadoop executable: C:\hadoop-3.1.1\bin\winutils.exe
window执行mapreduce报错 Exception in thread "main" java.lang.RuntimeException: java.io.FileNot ...
- ABP框架系列之三十九:(NLayer-Architecture-多层架构)
Introduction Layering of an application's codebase is a widely accepted technique to help reduce com ...
- Codeforces Round #539 (Div. 2) D 思维
https://codeforces.com/contest/1113/problem/D 题意 将一个回文串切成一段一段,重新拼接,组成一个新的回文串,问最少切几刀 题解 首先无论奇偶串,最多只会切 ...
- winSCP无法连接虚拟机Linux解决
刚在虚拟机上装上Linux(Centos7)后使用winSCP建立文件共享发现连接超时,经过几个小时的查找发现Linux中没有eth0文件,这说明其网卡名不是eth0,在网上查过一些解决办法有的通过修 ...
- DSP builder安装指南(以9.1为例) 转自http://www.cnblogs.com/sleepy/archive/2011/06/28/2092362.html
DSP Builder在算法友好的开发环境中帮助设计人员生成DSP设计硬件表征,从而缩短了DSP设计周期.已有的MATLAB函数和Simulink模块可以和Altera DSP Builder模块以及 ...
- 六.使用python操作mysql数据库
数据库的安装和连接 pymysql的安装 pip install PyMySQL python连接数据库 import pymysql db = pymysql.connec ...
- shell 命令 zip unzip
工作当中,经常要用到zip压缩. zip 将文件夹打包: 如文件夹名 xxx zip -r xxx.zip xxx unzip unzip -o xxx.zip -o 覆盖原有的文件夹 查询更多参数: ...
- 单源最短路径算法——Bellman-ford算法和Dijkstra算法
BellMan-ford算法描述 1.初始化:将除源点外的所有顶点的最短距离估计值 dist[v] ← +∞, dist[s] ←0; 2.迭代求解:反复对边集E中的每条边进行松弛操作,使得顶点集V ...