Android使用AspectJ实现AOP -- 上手实践篇

前言

 

1、aspect配置

刚接触aspectj时在网上查阅了不少资料,参考了一些网友的经验分享,结果很少有成功的,最后还是找到一篇可以正常编译运行的经验分享,这里我也稍作整理供大家参考,自己也留个笔记,方便以后翻阅查找。

拿自己的demo为例,可能跟大家的工程结构不一样,各位可以酌情参考,我是新建了一个Android Project,有一个默认的app module,然后aspectj类直接在app里创建的,大家也可以创建单独的android lib module,把aspectj文件在这里新建,然后在app module中引用,需要配置的脚本内容如下:

1.1 project根目录下的build.gradle

首先需要在工程根目录下的build.gradle的buildscript中添加aspectj相关依赖,如下:

buildscript {
    repositories {
        google()
        jcenter()
        
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.4.2'
        
        //☆☆☆ add for test AspectJ Begin 
        classpath 'org.aspectj:aspectjtools:1.9.2'
        classpath 'org.aspectj:aspectjweaver:1.9.2'
        //☆☆☆ End
    }
}

1.2 app module中的build.gradle 配置

这里需要配置两块内容,一个是依赖的sdk,另一个是aspectJ生效配置,具体如下:


//Part One
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    //aspectjrt的依赖
    implementation group: 'org.aspectj', name: 'aspectjrt', version: '1.9.2'
}


//Part Two
/*Aspectj配置*/
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

//log 打印工具和构建配置
final def log = project.logger
final def variants = project.android.applicationVariants

variants.all { variant ->
    // 通过debug判断是否需要打入aspectj,比如咱们切入的是打印执行时间工具代码,那如果release不需要打则return掉
    if (!variant.buildType.isDebuggable()) {
        log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
        return;
    }

    //执行到这里,使 aspectj 配置生效
    JavaCompile javaCompile = variant.javaCompile
    javaCompile.doLast {
        String[] args = ["-showWeaveInfo",
                         "-1.8",
                         "-inpath", javaCompile.destinationDir.toString(),
                         "-aspectpath", javaCompile.classpath.asPath,
                         "-d", javaCompile.destinationDir.toString(),
                         "-classpath", javaCompile.classpath.asPath,
                         "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
        log.debug "ajc args: " + Arrays.toString(args)


        //主要这两句使aspectj 生效
        MessageHandler handler = new MessageHandler(true);
        new Main().run(args, handler);


        //在编译时打印信息如警告、error 等等
        for (IMessage message : handler.getMessages(null, true)) {
            switch (message.getKind()) {
                case IMessage.ABORT:
                case IMessage.ERROR:
                case IMessage.FAIL:
                    log.error message.message, message.thrown
                    break;
                case IMessage.WARNING:
                    log.warn message.message, message.thrown
                    break;
                case IMessage.INFO:
                    log.info message.message, message.thrown
                    break;
                case IMessage.DEBUG:
                    log.debug message.message, message.thrown
                    break;
            }
        }
    }

经过以上几部配置,我们先sync一下工程,就可以编写自己的Aspect类了,在这个类里,你需要定义目标PointCut和Advice。

2、一个简单的Demo

    比如我们想在Activity的声明周期方法中开始添加一个打印方法名称的aspect,我们需要创建切面类,然后定义PointCut,如:

@Aspect
public class ActivityAspect {

    @Pointcut("execution(* com.example.htestproj.MainActivity.on**(..))")
    public void actlifecycle() {
    }

}

    这里我们使用两个注解,一个@Aspect,它用来描述当前类为aspect类,然后我新建了一个空实现的方法,用@Pointcut注解标注,其value内容为"execution(* com.example.htestproj.MainActivity.on**(..))", 该Pointcut表示是一个execution类型的JoinPoint,它筛选的对象为onXX开头的方法。

    关于signature的书写规则可以参考一下这篇文章:aspect signature规则,详见文中4.2.2部分;

    定义好Pointcut之后就可以创建advice了,通过设置advice我们可以指定代码注入的时机,是位于pointcut之前还是之后,或者两者都有,这里按照我之前的想法,想在各声明周期方法执行之前打印一下方法的名称,所以这里我需要用@Before类型的advice来标注一个方法,如:

@Before("actlifecycle()")
public void beforeLifeCycle(JoinPoint joinPoint) throws Throwable {
    MethodSignature ms = (MethodSignature)joinPoint.getSignature();
    String methodName = ms.getName();
    String cname = joinPoint.getThis().getClass().getSimpleName();
    Log.d("hgl_tag", "inject log  before "+methodName+" in class: " + cname);
}    

这里需要注意一点,即advice注解的value就是要关联的Pointcut对应的方法名,方法名后边的括号需要加上,不然不生效。通过代码可以看到,声明的方法需要包含一个JoinPoint参数,通过这个JP我们可以拿到方法的名字,以及当前class等信息,其他信息大家可以看一下源码了解一下都提供了获取哪些信息的方法。

测试的activity代码很简单:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }

    @Override
    protected void onResume() {
        super.onResume();
        isDebugModel(BuildConfig.BUILD_TYPE);
    }

    @Override
    protected void onPause() {
        super.onPause();
    }
}

打包运行APP,logcat可以看到如下信息:

2020-01-19 16:12:50.762 30627-30627/? D/hgl_tag: inject log  before onCreate in class: MainActivity
2020-01-19 16:12:50.900 30627-30627/? D/hgl_tag: inject log  before onResume in class: MainActivity

说明我们通过aspectj成功实现AOP,完成了面向切换的代码注入;

3、通过自定义注解实现AOP

在实际使用中,我们关注的切面可能比较分散,且没有统一的命名规则,这时候我们可以借助自定义注解来实现切面注入。来看一个具体例子:

3.1 定义注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OmegaTrack {
    String params();
}

3.2使用注解

     //在目标方法前添加注解,设置参数
     @OmegaTrack(params = "actvity_onPause")
    @Override
    protected void onPause() {
        super.onPause();
    }

3.3 声明pointcut

    @Pointcut("execution(@com.example.annotation.OmegaTrack * *(..))")
    public void omegaTrackData() {

    }
    
    
    /**
     * AOP Advice 通过自定义注解实现AOP埋点
     */
    @Around("omegaTrackData()")
    public Object trackMethod(ProceedingJoinPoint pdj) throws Throwable {
                Object result = null;

        MethodSignature method = (MethodSignature)pdj.getSignature();
        String methodName = method.getName();

        OmegaTrack omegaTrackAn = method.getMethod().getAnnotation(OmegaTrack.class);
        String params = omegaTrackAn.params();

        Log.d("hgl_tag", "omegatrack:" + methodName + ",参数为:" + Arrays.asList(pdj.getArgs()) + " omage params: " + params);
        trackOmegaEvent(params);
        
        try {
            result = pdj.proceed();
            Log.d("hgl_tag", "omegatrack: " + methodName + ",返回结果为:" + result);
        } catch (Throwable e) {
            /*异常通知方法*/
            Log.d("hgl_tag", "omegatrack: " + methodName + ",异常为:" + e);
            throw new RuntimeException();
        }

        return result;
    }
    

打包编译运行即可看到用@OmegaTrack标注的方法被注入了代码。

疑问?

最终被注入aspect注入代码的class文件在哪儿呢?经过一番搜索终于发现它的位置: /HTestProj/app/build/intermediates/transforms/dexBuilder/debug/0/com , 不过该目录下的文件是dex形式的需要做dex2jar的转化在查看。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值