AOP学习总结-简介
AOP 全名 Aspect Oriented Programming,意思是面向切面编程。
AOP 跟 OOP 一样,是一种编程思想。
如果 OOP 是纵向思想的话,那么 AOP 就是横向思想。可以看下图的差别:
15572175991582.jpg
OOP 与 AOP 的区别
面向目标不同:简单来说 OOP 是面向名词领域,AOP 面向动词领域。
思想结构不同:OOP 是纵向结构,AOP 是横向结构。
注重方面不同:OOP 注重业务逻辑单元的划分,AOP 偏重业务处理过程中的某个步骤或阶段。
为什么要 AOP
在 Android 的架构演进中,从 MVC 到 MVP 到 MVVM 再到 模块化 和 组件化,可以看到每种架构它们想解决的共同问题是如何更加精细地分离业务逻辑,并且处理好它们直接的关系,尽可能的将关注点集中在某一个模块或者组件中,所以可以这样理解:
最佳的系统架构由模块化的关注面领域组成,每个关注面均用纯 Java 对象(当然还可以用其他,这里强调的是用面向对象的思想)实现。不同的领域之间用最不具有侵害性的「方面」或「类方面」工具整合起来(比如路由等)。
但是有一个问题需要我们反思:
虽然我们进行了模块化或者组件化,但是有很多模块没有做到恰当地切分关注面,往往在业务逻辑中耦合了业务埋点、权限申请、登陆状态的判断、对不可预知异常 try-catch 和一些持久化操作等等。
是否有一种方法可以把这些更好的解偶出来呢,用 AOP 是一种不错的选择,「切分关注面」就是 AOP 的思想,它可以被看成是 OOP 的一种补充。
AOP 的作用
AOP 的作用有很多,举例有 参数判空和校验,权限验证,无埋点上报,性能监控,日志记录,统一缓存代理,
热修改,异常捕获,事件防抖,事务处理等等。
目前使用到 AOP 思想的比较有名的框架有:DataBinding,Dagger2,ButterKnfie,EventBus3,Room,DBFlow,AndroidAnnotation,FragmentRigger 等等。
可见 AOP 的作用是挺大的。
AOP 的实现方式
动态织入 Hook 方式
在运行期,目标类加载后,为接口动态生成代理类,将切面植入到代理类中。相对于静态AOP更加灵活。但切入的关注点需要实现接口。
实现方式有:Dexposed,Xposed,epic
动态字节码生成
原理是在运行期间目标字节码加载后,通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。由于是通过子类来代理父类,因此不能代理被 final 字段修饰的方法。
实现方式有:Cglib + Dexmaker
静态织入方式
- 在编译期织入,切面直接以字节码的形式编译到目标字节码文件中,这要求使用特殊的 Java 编译器。
- 在类装载期织入,这要求使用特殊的类装载器。
实现方式有:APT,AspectJ,ASM,Javassist,DexMaker,ASMDEX
通过一张图理解他们的关系:
15572191917505.jpg
他们的区别就是插入的时机不同。
常用的 AOP 实现方法有 APT,AspectJ,Javassist。其他相对来说比较不常用。
APT
说到 APT ,那就离不开注解,Android 解析注解主要有两种实现。
第一种是运行期通过反射去解析当前类,注入相应要运行的方法。这种方法最大的缺点就是性能问题,因为用了反射。大量使用会导致 app 性能下降,比如早期的 butterknife 框架。
第二种是通过编译器生成类的代理类,在运行期直接调用代理类的代理方法。这种方法的优点就是没有性能问题,缺点可能就是如果使用很多会生成比较多的代理类,带上控制得当的话应该没什么问题,比如现在的 butterknife,EventBus3,Room 框架等。
APT 就是指第二种方法。
在编写 APT 代理类时,往往有自动生成源代码的需要,我们不可能自己手动拼接字符串,这时候可以用 J 神的 JavaPoet 框架去解决。
AspectJ
AspectJ 是目前来说比较好的 AOP 框架了,它完全兼容 Java,而且使用起来比较简单。
但在 Android 上集成比较麻烦,这时候可以通过一些大神们提供的插件解决:
- J 神的 Hugo
- gradle_plugin_android_aspectjx
Javassist
使用 Javassist 的代表框架有 热修复框架HotFix 、Savior(InstantRun)
Javassist 是一个编辑字节码的框架,作用是修改编译后的 class 字节码
实现 Javassist 的方式可以通过自定义 gradle 插件,利用 Gradle Transfrom 这个 api 去实现。原因是 Transform 更为方便,我们不再需要插入到某个Task前面。Tranfrom 有自己的执行时机,一经注册便会自动添加到 Task 执行序列中,且正好是 class 被打包成 dex 之前。