谈谈对Android上AspectJ使用的想法

2020-06-16 16:33:03 浏览数 (1)

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 看个例子:

代码语言:javascript复制
@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'
代码语言:javascript复制
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的引入
代码语言:javascript复制
dependencies {
  implementation fileTree(dir: 'libs', include: ['*.jar'])
  api 'org.aspectj:aspectjrt:1.8.9'
              ...
}
  • 在主项目的build.gradle中添加:apply plugin: 'android-aspectjx'
代码语言:javascript复制
apply plugin: 'com.android.application'
apply plugin: 'android-aspectjx'
  • Aspect的实现类
代码语言:javascript复制
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   ",这是返回结果的后缀";
   }
  • 自定义的注解
代码语言:javascript复制
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

0 人点赞