相信做过 Android 开发的工程师大多都遇到过这种需求:
记录每一个页面的打开和关闭事件,并通过各种 DataTracking 的框架上传到服务器,用来日后做数据分析。
面对这样的需求,一般人都会想到,这其实就是在每一个 Activity 的 onCreate 和 onDestroy 方法中,分别添加页面打开和页面关闭的逻辑。常见的做法有以下两种:
- 修改项目中现有的每一个 Activity,这样显然不够高大上,并且如果项目以后需要添加新的页面,这套逻辑需要重新拷贝一遍,非常容易遗漏。
- 将项目中所有的 Activity 继承自 BaseActivity,将页面打开和关闭的逻辑添加在 BaseActivity中,这种方案看起来比第 1 种方案高级得多,并且后续项目中有新的 Activity,直接继承 BaseActivity 即可。但是这种方案对第三方依赖库中的界面则无能为力,因为我们没有第三方依赖库的源码。
就是在这种环境下,一种更加优雅更加完整的方案应运而生:编译插桩。
编译插桩是什么
顾名思义,所谓编译插桩就是在代码编译期间修改已有的代码或者生成新代码。实际上,我们项目中经常用到的 Dagger、ButterKnife 甚至是 Kotlin 语言,它们都用到了编译插桩的技术。
理解编译插桩之前,需要先回顾一下 Android 项目中 .java 文件的编译过程:
从上图可以看出,我们可以在 1、2 两处对代码进行改造。
- 在 .java 文件编译成 .class 文件时,APT、AndroidAnnotation 等就是在此处触发代码生成。
- 在 .class 文件进一步优化成 .dex 文件时,也就是直接操作字节码文件,也是本课时主要介绍的内容。这种方式功能更加强大,应用场景也更多。但是门槛比较高,需要对字节码有一定的理解。
本课时主要介绍第 2 种实现方式,用一张图来描述如下过程,其中红色虚框包含了本课时要讲的所有内容。
一般情况下,我们经常会使用编译插桩实现如下几种功能:
- 日志埋点;
- 性能监控;
- 动态权限控制;
- 业务逻辑跳转时,校验是否已经登录;
- 甚至是代码调试等。