阅读 778

AOP(面向切面编程)预研报告

1. 什么是AOP(面向切面编程)

  • OOP(面向对象)
    我们需要完成一个任务的时候一般都想着把一些操作封装成一个类,所有的变量和操作都封装到一个类里面,那么这个类就是我们的对象,我们要实现某个特定的功能,首先也想着在这个对象里面去实现

缺点
比如我们想实现某些不是常用的功能,我们需要去在需要的对象中去一一实现这些功能,并且我们要不断去维护这些功能,一旦多了我们就会很累的。
比如Android中一些按键统计、生命周期统计,特定统计都是比较琐碎的事情,要利用面向对象的思想去实现都不是很完美,这就要求去一一实现,显得很琐碎

  • AOP(面向切面)
    我在使用的时候是关注具体的方法和功能切入点,不需要知道也不用关心所在什么类或者是什么对象,我们只关注功能的实现,具体对象是谁,不关心

2.AOP的实现方式

从广义上来讲,AOP技术可以是任何能实现代码织入的技术或框架,对代码的改动最终都会体现在字节码上,而这类技术也可以叫做字节码增强,通用名词理解即可。

  • AOP关注的方法功能点,事先不知道所在对象是谁,当然程序的运行都是需要拿到对象在运行的
  • 要在知道方法功能点的前提下拿到对象并执行,这就需要用到Java的动态代理
  • 动态代理可以在不修改对象的情况下,改变对象的逻辑

java的动态代理:
有两个重要的类或接口
一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),我们可以自己写代码去定义自己的动态代理,去实现AOP,但是太麻烦了,所以需要一个第三方工具

举一个静态代理的例子,快速理解代理

public interface HelloInterface {
    void sayHello();
}
public class Hello implements HelloInterface{
    @Override
    public void sayHello() {
        System.out.println("Hello zhanghao!");
    }
}
public class HelloProxy implements HelloInterface{
    private HelloInterface helloInterface = new Hello();
    @Override
    public void sayHello() {
        System.out.println("Before invoke sayHello" );
        helloInterface.sayHello();
        System.out.println("After invoke sayHello");
    }
}
//调用
HelloProxy helloProxy = new HelloProxy();
helloProxy.sayHello();
结果:  
Before invoke sayHello  
Hello zhanghao!  
After invoke sayHello
复制代码

3. 实现AOP的方式对比

  • 首先,从织入的时机的角度看,可以分为源码阶段、class阶段、dex阶段、运行时织入
    • 对于前三项,源码阶段、class阶段、dex织入,由于他们都发生在class加载到虚拟机前,我们统称为静态织入
    • 而在运行阶段发生的改动,我们统称为动态织入
织入时机 技术框架
静态织入 APT,AspectJ、ASM、Javassit
动态织入 java动态代理,cglib、Javassit
  • 静态织入发生在编译器,因此几乎不会对运行时的效率产生影响;
  • 动态织入发生在运行期,可直接将字节码写入内存,并通过反射完成类的加载,所以效率相对较低,但更灵活。

动态织入的前提是类还未被加载,你不能将一个已经加载的类经过修改再次加载,这是ClassLoader的限制。但是可以通过另一个ClassLoader进行加载,虚拟机允许两个相同类名的class被不同的ClassLoader加载,在运行时也会被认为是两个不同的类,因此需要注意不能相互赋值, 不然会抛出ClassCastException。

java动态代理、cglib只会创建新的代理类而不是对原有类的字节码直接修改,Javassit可修改原有字节码。

其实利用反射或者hook技术同样可以实现代码行为的改变,但由于这类技术并没有真正的改变原有的字节码,所以暂不在谈论范围内,比如xposed,dexposed。

AOP方式对比选择

First Header
Hook时机
Android中应用场景 优点 缺点
Dexposed 运行时动态hook 滑动流畅度监控,事件执行监控,热修复 可以动态监控和系统通信的各种方法。 不支持5.0以上手机
Xposed 运行时动态hook 同Dexposed 可以动态监控和系统通信的各种方法。 不支持5.0以上手机,必须root
Java Proxy 运行时动态hook hook和系统通信接口例如:插件sdk Java 原生API,没有兼容性问题 只能hook 有Interface的类
AspactJ 编译时修改代码 统计方法执行时长,方法前后注入逻辑 Sprint开源的AOP框架,功能强大。注解很多。基本包括所有的编译时注入方式 需要引入118K的jar
ASM 编译时修改代码 同上 字节码操作库 需要自己写注解和编译脚本。字节码插入编写比较费劲
Javassit 编译时修改代码 同上 基于java反射的字节码操作类库。对比ASM,编写简单 对比ASM,修改类时,执行时间长
  • 主流使用的都是AspectJ
    • 功能强大
    • 支持编译期和加载时代码注入
    • 易于使用
  • 缺陷
    • 官方不支持Kotlin, 我们项目基本都是Kotlin编写

不过沪江科技开源了自己优化了的AspectJ, 支持Kotlin, 2.8K赞

gradle_plugin_android_aspectjx

  1. 目前的开源库中还没有发现可应用于Android平台的比较好的AOP框架或者工具,虽然xposed,dexposed非常强大,但基于严重的碎片化现状,兼容问题永远是一座无法逾越的大山。
  2. 目前其他的AspectJ相关插件和框架都不支持AAR或者JAR切入的,对于目前在Android圈很火爆的Kotlin更加无能为力。

4. 引入AOP优势和劣势

  • 优势
    • 可以实现无痕埋点
    • AOP天然的面向操作,因此同类型的操作可以快速且无侵入的添加进任何对象
    • 完全解耦, 抽取出来的操作, 跟对象再无关联
  • 劣势
    • 如上表所说, 引入的jar包相对较大
    • 使用沪江科技开源的AOP工具是否持续稳定

不错的讲解

  1. Android AspectJ详解
    • 详解AspectJ
  2. Android AOP 总结
    • AOP方式对比选择
  3. 谈谈Android AOP技术方案
    • AOP方式对比选择
    • 提到不支持Java8
  4. 深入理解Android之AOP
    • 大神详解
  5. Android Aspectj 从入门到实战
    • 点赞

5. 最终结论

暂时不建议使用

  • AOP的最终目的是脱离对象, 对功能点进行抽离
  • 这种抽离暂时还不是我们APP的痛点
  • 但为了让AOP适应我们APP, 我们需要有一些妥协, 如下
  1. 原生AspectJ引入顺畅, 但不支持Kotlin, 我们App几乎全部使用Kotlin编写
  2. 以AspectJ方式支持AOP的暂时只发现沪江科技的aspectjx
  3. 沪江科技的aspectjx虽然支持Kotlin, 但维护人员似乎只有一个, 在我引入的过程中出现过几个,已经在issue里提到过很多次的问题, 但作者并没有理会, 难免让人怀疑维护者对这个项目的热情, 是否会影响到我们后期的使用