背景
58作为分类信息服务平台,涉及的业务面十分广泛。不同的部门负责不同的功能和业务,提供专业的服务。为了提升用户体验,通常各部门各业务,需要对业务功能进行针对性能的检测记录,或者异常问题的记录。
从技术方向上来说,通常可以采用侵入式编程,即各业务线在必要的时机自行处理,添加同一套功能。但作为58一个整体中的一部分,很容易出现混乱,行为不一致情况,且存在重复工作。因此,一个统一的通用处理方案是必须的。
以Android客户端为例,目前常见的处理方案为,提供一个统一的工具类SDK,各业务线,在关键地方进行调用
基本这种方式也满足需求,但是存在几点不足:
不可控:需要各业务线手动调用,存在调用重复,或者调用丢失等情况,不可靠
实现复杂:一般对于三方sdk,需要添加功能,大多数情况下无法直接调用,需要通过反射、代理等方式,实现,使用复杂
可读性差:从整体结构来说,破坏了原有功能模块的完整性。例如方法A,原本功能单一清晰,添加了各种功能模块后,使方法A功能变得复杂,改变了A原本单一的职责,使整体结构变的复杂,不利于维护
总的来说,常见的实现方式是一种侵入式实现,虽说能够达到效果,但是还有很多不足,可以优化。我们可以通过面向切片编程(即AOP编程)的方式实现我们的目标。
AOP介绍
AOP:面向切片编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
通过概念,我们可以得知AOP实现有2种实现方式,一个是在编译期,一个是在运行期。
对比下2种实现方式,首先从结果上看,都能满足需求。那从性能上看,一个是影响编译时速度,一个影响运行时速度。通常移动端对应用的启动速度,运行速度都有一定的要求,所以,通过预编译的方式实现功能的统一维护,性能最优。接下来我们主要分析一下,编译期的AOP在Android端的实现。
需要了解Android端预编译方式AOP,我们首先需要了解下,Android的打包流程,方便了解我们在什么时机进行代码的统一处理
Android打包一般通过gradle进行构建,所以我们可以通过gradle插件,在编译阶段添加我们的任务,进行文件的修改工作。
gradle插件
Android一般通过gradle进行代码构建,可以在构建过程中,添加自己的Task执行一些操作,不过无法多项目复用。也可以创建gradle插件,发布到maven仓库,项目中直接引用即可,可以复用。
Google官方提供了一个Transform api允许用户在构建过程中(.class到.dex过程中间),对.class文件进行修改。使用方可以通过gradle插件注册Transform对.class文件进行修改
Transform的调用是一个链式过程,允许注册多个transform,一个transform执行完,会执行下一个transform。流程如下:
gradle插件的定义十分简单,流程如下:
创建一个Android Library的module 该module的名字即moduleName
打开src/main目录,修改java文件名称为grovvy,删除其他文件
修改build.gradle文件 设置为插件
在src/main下创建创建resources文件夹,然后在resources文件夹下创建META-INF.gradle-pulgins文件夹,并创建配置文件.properties文件,并在properties文件中指定plugin,properties文件名字即apply所需的插件名
上传插件到maven仓库,可以通过在主项目的build.gradle设置maven插件依赖,并在module的build.gradle中apply插件
创建好插件后通过Transform,我们能对项目中的.class文件或者jar包中的.class文件进行修改。
字节码操作
字节码操作框架常见的有javassist ,asm等,基于性能,包大小等考虑,我们采用ASM框架操作class文件
ASM是一个java字节码操作框架,可以直接修改二进制class文件。具体使用文档参考《官方使用指南》,下载地址:https://asm.ow2.io/asm4-guide.pdf
整体执行流程如下
通过Gradle插件中的Transform 我们对class文件或者jar包中的class文件进行操作
过滤我们需要处理的class类,通过ASM进行读取,遍历节点,进行修改
实践与总结
总的来说,AOP的实践方式,给我们提供了新的解决问题的思路,以非侵入式方式,提供各模块或者各业务线,统一维护的一种实践方式。相比传统的侵入式,在应用性能,开发效率,代码的结构上都有所提升。
领取专属 10元无门槛券
私享最新 技术干货