在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 ...
随机推荐
- UVA 10534 Wavio Sequence
题目链接:https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=17&p ...
- SQLite3命令操作大全
SQLite3命令操作大全 SQLite库包含一个名字叫做sqlite3的命令行,它可以让用户手工输入并执行面向SQLite数据库的SQL命令.本文档提供一个样使用sqlite3的简要说明. 一.ql ...
- springboot 使用maven 打包 报 (请使用 -source 7 或更高版本以启用 diamond 运算符) 错误解决办法
在使用springboot maven 打包时 报如下错误 (请使用 -source 7 或更高版本以启用 diamond 运算符) pom.xml编译插件 配置如下: <plugin> ...
- hdu 1290_献给杭电五十周年校庆的礼物
Description 或许你曾经牢骚满腹或许你依然心怀忧伤或许你近在咫尺或许你我天各一方 对于每一个学子母校 永远航行在生命的海洋 今年是我们杭电建校五十周年,这是一个值得祝福的日子.我们该送给母校 ...
- Beta冲刺 (4/7)
Part.1 开篇 队名:彳艮彳亍团队 组长博客:戳我进入 作业博客:班级博客本次作业的链接 Part.2 成员汇报 组员1(组长)柯奇豪 过去两天完成了哪些任务 共享编辑文章的后端数据处理 展示Gi ...
- 第二周Access课总结
一.问;这节课你学到了什么知识? 答:回忆上周主要学了关于Access的基础知识和基本操作,一转眼,这周也学到了很多,主要学Access的数据类型的表的建立和管理相关的操作! 收获多少在于学了多少,正 ...
- 2-postman批量执行接口
1.postman环境设置与使用 1)点击设置,添加按钮 2)填写环境名称,参数 3)切换环境 4)使用环境变量,使用格式为:{{变量名}} 2.postman批量执行接口 1)选择要执行的文件夹,点 ...
- 第四篇:断路器(Hystrix)
一.断路器简介. 在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC),在Spring Cloud可以用RestTemplate+Ribbon和Feign来调用.为了保证 ...
- python实现线性排序-基数排序
基数排序算法是一种是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较. 由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于 ...
- Testing - 软件测试知识梳理 - 测试模型
珠玉在前,不再赘言. 软件测试模型 软件测试模型汇总