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


    背景


    主要是记录学习 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


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

  • 相关阅读:
    zabbix
    C语言:打印几行机列的**符号
    暴力递归转动态规划(九)
    PostgreSQL自定义文本检索分词规则
    RabbitMQ 知识点解读
    FPGA序列脉冲波形发生器
    Prometheus(三)node-exporter
    EN 16069建筑物用隔热产品.工厂制造的聚乙烯泡沫(PEF)产品—CE认证
    supervisor守护python进程报FATAL错 spawn error
    【从入门到起飞】JavaSE—方法引用
  • 原文地址:https://blog.csdn.net/csdnzouqi/article/details/126262237