谈谈对Android上AspectJ使用的想法

AOP是什么

概念:AOP是Aspect Oriented Programming的缩写,即『面向切面编程』;切面编程,就是在你项目原有的功能基础上,通过AOP去添加新的功能,这些功能是建立在原有功能的基础上的,而且原有的功能并不知道你已经添加了新的功能;AOP就是在某一个类或方法执行前后打个标记,声明在执行到这里之前要先执行什么,执行完这里之后要接着执行什么。插入了新的执行方法。

AOP和OOP的不同

OOP,即『面向对象编程』,它提倡的是将功能模块化,对象化,而AOP的思想,则不太一样,它提倡的是针对同一类问题的统一处理,当然,我们在实际编程过程中,不可能单纯的安装AOP或者OOP的思想来编程,很多时候,可能会混合多种编程思想,大家也不必要纠结该使用哪种思想,取百家之长,才是正道。

AOP的使用场景

主要用于不想侵入原有代码的场景中,例如SDK需要无侵入的在宿主中插入一些代码,做日志埋点、性能监控、数据校验、持久化、动态权限控制、甚至是代码调试等等。

什么是AspectJ

AspectJ实际上是对AOP编程思想的一个实践,当然,除了AspectJ以外,还有很多其它的AOP实现,例如ASMDex,但目前最好、最方便的,依然是AspectJ。

原理图

图片来自https://www.jianshu.com/p/0fa8073fd144

工作原理.png

首先谈谈AspectJ相关的几个概念点

  • Join Points(连接点)
    Join Points可以看作是程序运行时的一个执行点,比如:一个函数的调用可以看作是个Join Points,如Log.d()这个函数,d()可以看作是个Join Points,而调运d()的函数也可以认为是一个Join Points;设置一个变量,或者读取一个变量也可以是个Join Points;for循环也可以看作是Join Points。可以是函数,构造方法等


    Join Points.png
  • Pointcuts(切入点)
    一个程序会有多个Join Points,即使同一个函数,也还分为call和execution类型的Join Points,但并不是所有的Join Points都是我们关心的,Pointcuts就是提供一种使得开发者能够选择自己需要的JoinPoints的方法;或者说是程序中可能作为代码注入目标的特定的点,例如一个方法调用或者方法入口。


    image.png

    以上的 Signature 都是由一段表达式组成,且每个关键词之间都有“空格”,下面是对关键词的解释:


    image.png
  • Advice
    Advice,也就是具体的插入点。典型的 Advice 类型有 before、after 和 around,分别表示在目标方法执行之前、执行后和完全替代目标方法执行的代码。


    image.png

    看个例子:

@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 onActivityMethodBefore:实际切入的代码。

应用

在Android项目中使用AspectJ

在android中配置aspectj是特别麻烦的,目前市场上流行的一款在Android使用的插件 gradle_plugin_android_aspectjx

如何在Android studio配置gradle_plugin_android_aspectjx的插件

  • 项目根目录的build.gradle中增加依赖:classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
buildscript {
    
    repositories {
        google()
        jcenter(){
            url 'http://jcenter.bintray.com/'
        }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.2'
        classpath 'com.jakewharton:butterknife-gradle-plugin:8.8.1'  //添加这一行
        classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter(){
            url 'http://jcenter.bintray.com/'
        }
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
  • 在主项目或者库的build.gradle中增加AspectJ的依赖:api 'org.aspectj:aspectjrt:1.8.9' //相关api的引入
dependencies {
  implementation fileTree(dir: 'libs', include: ['*.jar'])
  api 'org.aspectj:aspectjrt:1.8.9'
              ...
}
  • 在主项目的build.gradle中添加:apply plugin: 'android-aspectjx'
apply plugin: 'com.android.application'
apply plugin: 'android-aspectjx'
  • Aspect的实现类
package com.jason.aspectj;

import android.content.Context;
import android.util.Log;
import android.widget.Toast;

import com.jason.aspectj.tools.StopWatch;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

/**
* Description:AspectActivityTest
*
* @author Chenby
* @create 2019/10/10 14: 25
*/
@Aspect
public class AspectActivityTest {

 /**
  * 重要的概念知识
  */
 //Join Points,简称JPoints,是AspectJ的核心思想之一,它就像一把刀,把程序的整个执行过程切成了一段段不同的部分
 //Advice,Advice其实是最好理解的,也就是我们具体插入的代码,以及如何插入这些代码
 //@Before:Advice,也就是具体的插入点, 常见的有:Before,After,Around
 //execution:处理Join Point的类型,例如call、execution
 //execution是在被切入的方法中,call是在调用被切入的方法前或者后

 //Call(Before)
 //Pointcut{
 //    Pointcut Method
 //}
 //Call(After)

 //Pointcut{
 //  execution(Before)
 //    Pointcut Method
 //  execution(After)
 //}

 //(* android.app.Activity.on**(..)):这个是最重要的表达式,第一个『*』表示返回值,『*』表示返回值为任意类型,后面这个就是典型的包名路径,
 // 其中可以包含『*』来进行通配,几个『*』没区别。同时,这里可以通过『&&、||、!』来进行条件组合。()代表这个方法的参数,你可以指定类型,
 // 例如android.os.Bundle,或者(..)这样来代表任意类型、任意个数的参数。


 private static final String TAG = "Chenby";

 /**
  * advice 采用before的例子, Joint Point的类型是execution  Before 表示在方法之后插入代码
  *
  * @throws Throwable
  */
 @Before("execution(* android.app.Activity.on**(..))")
 public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
   String key = joinPoint.getSignature().toString();
   Log.d(TAG, "onActivityMethodBefore: " + key);
 }

 /**
  * advice 采用after的例子, Joint Point的类型是execution  After 表示在方法之后插入代码
  *
  * @throws Throwable
  */
 @After("execution(* android.app.Activity+.on*(android.os.Bundle))")
 public void onActivityMethodAfter(JoinPoint joinPoint) throws Throwable {
   String key = joinPoint.getSignature().toString();
   Log.d(TAG, "onActivityMethodAfter: " + key);
 }

 /**
  * advice 采用 Around的例子, Joint Point的类型是execution, Around表示在方法前后各插入代码
  *
  * @throws Throwable
  */
 @Around("execution(* com.cby.mvvmdemo.ui.mine.MinePageFragment.testAOP(*))")
 public void onFragmentMethodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
   String key = proceedingJoinPoint.getSignature().toString();
   Log.d(TAG, "onFragmentMethodAround: " + key);
   //表示原有方法的执行
   proceedingJoinPoint.proceed();
   Log.d(TAG, "onFragmentMethodAround: " + key);
 }

 @Around("execution(* com.cby.mvvmdemo.ui.mine.MinePageFragment.testAOP(android.content.Context))")
 public void onMethodAroundToast(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
   Context mContext = null;
   //proceedingJoinPoint.getThis()可以获取到调用该方法的对象
   if (proceedingJoinPoint.getThis() instanceof Context) {
     mContext = (Context) proceedingJoinPoint.getThis();
   } else {
     //proceedingJoinPoint.getArgs()可以获取到方法的所有参数
     for (Object context : proceedingJoinPoint.getArgs()) {
       if (context instanceof Context) {
         mContext = (Context) context;
         break;
       }
     }
   }
   if (mContext == null) {
     return;
   }
   proceedingJoinPoint.proceed();
   Toast.makeText(mContext.getApplicationContext(), "Test show toast for aop!", Toast.LENGTH_SHORT)
       .show();
 }

 private static final String POINTCUT_METHOD =
     "execution(@com.jason.aspectj.custom.DebugTool * *(..))";

 private static final String POINTCUT_CONSTRUCTOR =
     "execution(@com.jason.aspectj.custom.DebugTool *.new(..))";

 @Pointcut(POINTCUT_METHOD)
 public void methodAnnotatedWithDebugTrace(){}

 @Pointcut(POINTCUT_CONSTRUCTOR)
 public void constructorAnnotatedDebugTrace() {}

 @Around("methodAnnotatedWithDebugTrace() || constructorAnnotatedDebugTrace()")
 public Object onDebugToolMethodAround(ProceedingJoinPoint joinPoint) throws Throwable {

   MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
   String className = methodSignature.getDeclaringType().getSimpleName();
   String methodName = methodSignature.getName();

   final StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   Object result = joinPoint.proceed();
   stopWatch.stop();

   Log.i(className, buildLogMessage(methodName, stopWatch.getTotalTimeMillis()));

   return result;
 }

 /**
  * Create a log message.
  *
  * @param methodName A string with the method name.
  * @param methodDuration Duration of the method in milliseconds.
  * @return A string representing message.
  */
 private static String buildLogMessage(String methodName, long methodDuration) {
   StringBuilder message = new StringBuilder();
   message.append("Chenby --> ");
   message.append(methodName);
   message.append(" --> ");
   message.append("[");
   message.append(methodDuration);
   message.append("ms");
   message.append("]");

   return message.toString();
 }

 @AfterReturning(value = "execution(* com.cby.mvvmdemo.z_test.activities.AopTestActivity.AfterReturning*(..))", returning = "num")
 public void testAspectAfterReturning(int num) {
   Log.e(TAG, "AfterReturning-num:" + num);
 }
}
  /**
   * proceed(ibject[] args)的使用
   */
  @Around("execution(* com.jason.aspectj*.many*(..))")
  public Object process(ProceedingJoinPoint point) throws Throwable {
       System.out.println("@Around:执行目标方法之前...");
       //访问目标方法的参数:
       Object[] args = point.getArgs();
       if (args != null && args.length > 0 && args[0].getClass() == String.class) {
           args[0] = "改变后的参数1";
       }
       //用改变后的参数执行目标方法
       Object returnValue = point.proceed(args);
       System.out.println("@Around:执行目标方法之后...");
       System.out.println("@Around:被织入的目标对象为:" + point.getTarget());
       return "原返回值:" + returnValue + ",这是返回结果的后缀";
   }
   
  • 自定义的注解
package com.jason.aspectj.custom;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Description:DebugTool :自定义AOP的注解
*
* @author Chenby
* @create 2019/10/10 16: 31
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface DebugTool {
}

结语

以上是借鉴了网上的文章和自己的一些理解关于AOP的AspectJ的编程的想法,如有错误欢迎评论留言指出

参考文献

https://www.jianshu.com/p/0fa8073fd144
https://blog.csdn.net/eclipsexys/article/details/54425414
https://www.cnblogs.com/yxx123/p/6665736.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342