Android App 的打包流程,可以参考下图: 通过下图可知,我们只要在图中红圈处拦截,就可以拿到所有的 .class 文件,然后遍历 .class 文件中的所有方法, 再根据条件找到目标方法,最后进行修改并保存,就可以插入埋点代码了。
Google 从 Android Gradle 1.5.0 开始,提供了 Trans- form API,允许第三方的插件(Plugin)在 Android App 打包成 .dex 文件之前的编译过程中操作 .class 文件。我们只要实现一套 Transform,去遍历所有 .class 文件的所有方法,然后进行修改,最后再对原文件进行替换,即可达到插入代码的目的。
Gradle Transform 是 Android 官方提供给开发者在项目构建阶段,即由 .class 到 .dex 转换期间修改 .class 文件的一套 API。目前比较经典的应用是字节码插桩、代码注入技术。
概括来说,Transform 就是把输入的 .class 文件转变成目标字节码文件。
我们先了解一下 Transform 的两个概念:
TransformInput 是指这些输入文件的抽象。它包括两部分:
1)DirectoryInput 集合
是指以源码方式参与项目编译的所有目录结构及其目录下 的源码文件。
2)JarInput 集合
是指以 jar 包方式参与项目编译的所有本地 jar 包和远程 jar 包。
是指 Transform 的输出,通过它可以获取输出路径。
我们下面了解一下 Transform.java 的定义。 Transform.java 是一个抽象类,它的定义如下: 它定义了几个抽象方法如下:
我们下面实现一个 Gradle Transform 的实例,该实例其实没有什么特定功能,仅仅是把所有的输入文件原封不动的拷贝到 输出目录。 通过 Transform 提供的 API 可以遍历所有文件,包括目录和 jar 包。但是要实现 Transform 的遍历 .class 文件的操作,需 要通过 Gradle 插件来实现。 完整的项目源码后续会 release 给大家。
ASM 是一个功能比较齐全的 Java 字节码操作与分析框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接 产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类的行为。Java class 被存储在严格格式定义 的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素,包括类名称、方法、属性以及 Java 字节码(指令)。 ASM 从类文件中读入这些信息后,能够改变类行为、分析类的信息,甚至能够根据具体的要求生成新的类。 我们下面简单的介绍一个 ASM 框架中几个核心的类:
ClassVisitor 定义了一系列的 API,它按照一定的标准次序来遍历类中的成员。
在 ClassVisitor 中,我们可以根据实际的需求进行条件判断,只要满足我们特定条件的类,我们才会去修改它的方法。比如, 我们要自动采集 Button 的点击事件,那么只有实现了 View$OnClickListener 接口的类,我们才会去遍历它的方法并找到 onClick(view) 方法,然后进行修改操作。 我们下面重点介绍 ClassVisitor 中的 visit 方法和 visitMethod 方法。
该方法是当扫描类时第一个拜访的方法。 方法定义如下:
各参数解释如下:
表示 JDK 的版本,比如 51,代表 JDK 版本 1.7。 各个 JDK 版本对应的数值如下:
类的修饰符。修饰符在 ASM 中是以“ACC_”开头的常量。可以作用到类级别上 的修饰符有:
类的名称。通常我们会使用完整的包名 + 类名来表示类,比如:a.b.c.MyClass, 但是在字节码中是以路径的形式表示,即:a/b/c/MyClass。值得注意的是,虽 然是路径表示法但是不需要写明类的“.class”扩展名。
表示泛型信息,如果类并未定义任何泛型该参数为空。
表 示 所 继 承 的 父 类。由 于 Java 的 类 是 单 根 结 构,即 所 有 类 都 继 承 自 java.lang.Object。因此可以简单的理解为任何类都会具有一个父类。虽然在编 写 Java 程序时我们没有去写 extends 关键字去明确继承的父类,但是 JDK 在编 译时总会为我们加上“extends Object”。
表示类实现的接口,在 Java 中,类是可以实现多个不同的接口,因此该参数是 一个数组。
该方法是当扫描器扫描到类的方法时进行调用。 方法定义如下:
各参数解释如下:
表示方法的修饰符。 可以作用到方法级别上的修饰符有:
表示方法名。
表示方法签名,方法签名的格式如下:“( 参数列表 ) 返回值类型”。在 ASM 中不 同的类型对应不同的代码:
下面举几个方法参数列表对应的方法签名示例:
表示泛型相关的信息。
表示将会抛出的异常,如果方法不会抛出异常,该参数为空。
我们自定义一个 Gradle Plugin ,可以注册一个 Transform 对象,然后在 transform 方法里,分别遍历目录和 jar 包,然 后我们就可以遍历所有的
.class 文件。然后再利用 ASM 的相关 API,加载相应的
.class 文件、解析
.class 文件,就可以找 到满足一定特定条件的
.class 文件和相关方法,最后去修改相应的方法以动态插入埋点字节码,从而达到自动埋点的效果。
完整的项目源码后续会 release 给大家。
• 暂时没有什么发现缺点
• 字节码语法
• Gradle Plugin
• Transform API
• ASM
[1] https://www.jianshu.com/p/9039a3e46dbc
[2] http://tools.android.com/tech-docs/new-build-system/transform-api
[3]https://blog.csdn.net/tscyds/article/details/78082861
[4]https://blog.csdn.net/Neacy_Zz/article/details/78546237
[5] http://asm.ow2.org/
[6]https://blog.csdn.net/byeweiyang/article/details/80127789
注:该内容来自神策数据用户行为洞察研究院出品的《Android 全埋点解决方案》白皮书,查看完整白皮书可点击 《Android 全埋点解决方案》
更多白皮书、报告、干货和案例,可以关注“神策数据”和“用户行为洞察研究院”公众号了解~