当前位置:   article > 正文

【AspectJX】Android 中快速集成使用一款 AOP 框架并附加数据埋点解决方案实现

aspectjx

背景


主要是记录学习 AOP 编程思想。项目中数据埋点统一方案有使用到,也是一次加深学习理解的过程。

什么是 AOP


AOPAspect-Oriented Programming 缩写,即面向切面编程。提倡针对同一类问题的统一处理方法。

AOP 这种编程思想有哪些作用呢?一般来说,主要用于不想侵入原有代码的场景中,例如SDK 需要无侵入的在宿主中插入一些代码,做日志埋点、性能监控、动态权限控制、甚至是代码调试等。

AspectJX


一个基于 AspectJ 并在此基础上扩展出来可应用于 Android 开发平台的 AOP 框架,可作用于java 源码,class 文件及 jar 包,同时支持 kotlin 的应用。

  • 目前 AspectJX 仅支持 annotation 的方式

【 Github AspectJX 】点击了解更多

问:编译时会出现 can’t determine superclass of missing type** 及其他编译错误怎么办?

答:大部分情况下把出现问题相关的 class 文件或者库(jar 文件)过滤掉就可以搞定了

集成使用

具体配置


  • 在项目根目录的 build.gradle 里依赖 AspectJX。如下:
dependencies {

        classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 在项目中 app 模块的 build.gradle 里应用插件。如下:
apply plugin: 'android-aspectjx'
  • 1
  • AspectJX配置

AspectJX 默认会处理所有的二进制代码文件和库,为了提升编译效率及规避部分第三方库出现的编译兼容性问题,AspectJX 提供 include, exclude 命令来过滤需要处理的文件及排除某些文件(包括 class 文件及 jar 文件)。

配置在对应模块的 gradle 文件下,如下:

android{
	aspectjx{
		// 配置规则
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5

示例配置如下:

aspectjx {
//排除所有package路径中包含`android.support`的class文件及库(jar文件)
	exclude 'android.support'
}
  • 1
  • 2
  • 3
  • 4
aspectjx {
//忽略所有的class文件及jar文件,相当于AspectJX不生效
	exclude '*'
}
  • 1
  • 2
  • 3
  • 4

更多详细规则使用自行查阅 Github AspectJX 框架相关知识了解。

  • 模块的 gradle 中添加 dependencies 依赖。如下:
dependencies {
	implementation 'org.aspectj:aspectjrt:1.9.5'
}
  • 1
  • 2
  • 3
  • 配置编译选项支持 java 8
android{
	compileOptions {
        targetCompatibility 1.8
        sourceCompatibility 1.8
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这样可以保证编译时不会报错异常,建议加上。到这里就完成了所有的配置,然后就可以愉快的 coding

Demo 中使用


  • MainActivity.java 中新增一个方法 void doAspectJX(String s),并在 onCreate 中调用,保证程序运行时能执行到这个方法。如下:
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        doAspectJX("");
    }

    private void doAspectJX(String s){

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 创建一个 MyAspectJX.java 自定义类,然后在合适的时机切入。如下:
@Aspect
public class MyAspectJX {

    @After("execution(* *..MainActivity.doAspectJX*(..))")
    public void test(JoinPoint point){
        try {
        
            MethodSignature methodSignature = (MethodSignature) point.getSignature();
            String methodName = methodSignature.getName();
            System.out.println(":> methodName: " + methodName);

        }catch (Exception e){
            e.printStackTrace();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

几点说明:

  • 切入类需要使用 @Aspect 注解进行标识
  • 根据自定义的切入规则,在程序运行过程中,满足规则时进行切入

示例中规则为:@After("execution(* *..MainActivity.doAspectJX*(..))")

表示:当程序执行完 MainActivity 类中的 doAspectJX() 这个方法之后进行切入。

更多规则同学们自行学习使用。

  • 切入验证

我们切入后,打印切入的方法名称。运行程序后打印结果如下:

在这里插入图片描述
上图所示,说明成功切入了,打印方法名称 doAspectJX

到这里就完成了从集成到简单应用的一个整体流程。

拓展实现:数据埋点解决方案

准备工作


上面也谈到了 AOP 编程思想可用于埋点功能的实现,下面我们一起实现一下。

  • 首先新建一个接口,命名为 Behavior.java 。如下:
public interface Behavior {

    Map<String, Object> params();

}
  • 1
  • 2
  • 3
  • 4
  • 5

这个接口,可以供需要埋点的类实现,然后通过实现 param() 方法,把需要埋点的参数和值保存到 map 中返回即可。

  • 自定义 DataPoint.java 注解。如下:
/**
 * 自定义 DataPoint 注解
 * 切入规则使用:仅标记方法
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataPoint {
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

使用注解来标记方法,可以很方便的指定切入规则,在合适时机进行切入获取数据,然后就可以愉快的上报数据了。

完整代码实现


  • MainActivity.java 代码如下:
// 实现 Behavior 接口
public class MainActivity extends AppCompatActivity implements Behavior{

    Map<String, Object> map = new HashMap<>();

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

    @DataPoint
    private void doAspectJX(String s){
        System.out.println(":> doAspectJX 执行");
        map.put("name", "imxiaoqi");
    }

    @Override
    public Map<String, Object> params() {
        return map;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 切入类 MyAspectJX.java 代码如下:
@Aspect
public class MyAspectJX {

//    @After("execution(* *..MainActivity+.on*(..))")
//    @After("execution(* *..*.doAspectJX(..))")
    @After("execution(@DataPoint * *..*.*(..))")
    public void test(JoinPoint point){
        try {

            // 获取被切入的类对象
            // 示例中 MainActivity 实现了 Behavior 接口,故可以类型强转为 Behavior
            Behavior behavior = (Behavior) point.getThis();
            // 获取数据,存于 Map 中
            Map<String, Object> map = behavior.params();
            System.out.println(":> 成功切入,获取 name: " + map.get("name"));

        }catch (Exception e){
            e.printStackTrace();
        }
    }


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

@After("execution(@DataPoint * *..*.*(..))") 表示:任意类型 任意包下的任意类里的任意方法,并且方法添加了 DataPoint 注解,会在方法执行完后立即切入。

这样就会只在添加了注解的方法执行完后进行切入,可以对程序的整体性能影响降到最低。

  • 运行结果

在这里插入图片描述

上图所示,切入成功,数据也正常获取。

然后就可以愉快的将该数据埋点解决方案应用于项目中去了。

参考文章


AspectJ在Android 中的使用攻略

Android中使用AspectJ


技术永不眠!我们下期见!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/266725
推荐阅读
相关标签
  

闽ICP备14008679号