在Android项目中使用AspectJ的方法

 更新时间:2018年04月20日 10:21:34   作者:Cavalier-  
这篇文章主要介绍了在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{
}

这里会分成好几个部分,我们依次来看:

  1. @Before: Advice, 也就是具体的插入点
  2. execution:处理Join Point的类型,例如call、execution
  3. (* android.app.Activity.on**(..)): 这个是最重要的表达式,第一个*表示返回值,*表示返回值为任意类型,后面这个就是典型的包名路径,其中可以包含 *来进行通配,几个 *没有区别。同时这里可以通过&&、||、!来进行条件组合。()代表这个方法的参数,你可以指定类型,例如android.os.Bundle,或者 (..) 这样来代表任意类型、任意个数的参数。
  4. 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*(..))")

整个表达式可以分为五个部分:
1.execution()是表达式主体
2.第一个*号代表返回类型,*号代表所有的类型。
3.包名 表示需要拦截的包名,这里使用*.代表匹配所有的包名。
4.第二个*号表示类名,后面跟.MainActivity是指具体的类名叫MainActivity。
5.*(..) 最后这个星号表示方法名,+.代表具体的函数名,*号通配符,包括括弧号里面表示方法的参数,两个dot代表任意参数。

遇到的错误

1.以下错误可以使用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 用Time和Calendar获取系统当前时间源码分享(年月日时分秒周几)

    Android 用Time和Calendar获取系统当前时间源码分享(年月日时分秒周几)

    这篇文章主要介绍了Android 用Time和Calendar获取系统当前时间源码分享,包括年月日时分秒周几的源码,非常不错,具有参考借鉴价值,需要的朋友参考下
    2017-01-01
  • Android Studio安装配置方法图文详细教程

    Android Studio安装配置方法图文详细教程

    这篇文章主要为大家介绍了Android Studio下载和配置图文详细教程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-07-07
  • 关于Kotlin的自动类型转换详解

    关于Kotlin的自动类型转换详解

    这篇文章主要给大家介绍了关于Kotlin的自动类型转换的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • Android 文件存储与SharedPreferences存储方式详解用法

    Android 文件存储与SharedPreferences存储方式详解用法

    SharedPreferences是安卓平台上一个轻量级的存储类,用来保存应用的一些常用配置,比如Activity状态,Activity暂停时,将此activity的状态保存到SharedPereferences中;当Activity重载,系统回调方法onSaveInstanceState时,再从SharedPreferences中将值取出
    2021-10-10
  • Android开源组件小结

    Android开源组件小结

    Android自带的组件比较丑陋(个人感觉),自己写组件比较复杂,而且必须熟悉android应用层开发的一些机制,如绘制、回调,所以非迫不得已的情况下还是不要自己写组件,因为怕考虑不周全导致譬如性能或异常方面的问题,你自己写也会耗费不少时间
    2013-02-02
  • Android开发中通过手机号+短信验证码登录的实例代码

    Android开发中通过手机号+短信验证码登录的实例代码

    最近在开发一个android的项目,需要通过获取手机验证码来完成登录功能,接下来通过实例代码给大家分享手机号+短信验证码登录的实现方法,需要的的朋友参考下吧
    2017-05-05
  • Android网易有道词典案例源码分享

    Android网易有道词典案例源码分享

    这篇文章主要为大家分享了Android网易有道词典案例源码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-05-05
  • Android制作漂亮自适布局键盘的方法

    Android制作漂亮自适布局键盘的方法

    最近做了个自定义键盘,但面对不同分辨率的机型其中数字键盘不能根据界面大小自已铺满,但又不能每种机型都做一套吧,所以要做成自适应,那这里主讲思路,感兴趣的朋友一起学习吧
    2015-12-12
  • Android架构组件Room指南

    Android架构组件Room指南

    Room是Google推出的Android架构组件库中的数据持久化组件库, 也可以说是在SQLite上实现的一套ORM解决方案。下面通过本文给大家介绍Android架构组件Room指南,需要的朋友参考下吧
    2017-12-12
  • Android入门之实现自定义Adapter

    Android入门之实现自定义Adapter

    这篇文章主要为大家详细介绍了Android如何实现自定义Adapter,文中的示例代码讲解详细,对我们学习Android有一定的帮助,需要的可以参考一下
    2022-11-11

最新评论