android build报错 项目飘红_用了这份 Android 工程师实操,我实现了3K 到 3W 的财富自由之路...
作者简介:姜新星,前360技术专家。本文选自:拉勾教育专栏《Android 工程师进阶 34 讲》你好,我是你的 Android 课老师阿星,本课时我们讲解如何编译插桩操纵字节码。 本课时就使用 ASM 来实现简单的编译插桩效果,通过插桩实现课时开始讲的需求,在每一个 Activity 打开时输出相应的 log 日志。实现思路过程主要包含两步:遍历项目中所有的 .class 文件如何找到项目中编
作者简介:姜新星,前360技术专家。本文选自:拉勾教育专栏《Android 工程师进阶 34 讲》
你好,我是你的 Android 课老师阿星,本课时我们讲解如何编译插桩操纵字节码。 本课时就使用 ASM 来实现简单的编译插桩效果,通过插桩实现课时开始讲的需求,在每一个 Activity 打开时输出相应的 log 日志。实现思路
过程主要包含两步:
- 遍历项目中所有的 .class 文件
如何找到项目中编译生成的所有 .class 文件,是我们需要解决的第一个问题。众所周知,Android Studio 使用 Gradle 编译项目中的 .java 文件,并且从 Gradle1.5.0 之后,我们可以自己定义 Transform,来获取所有 .class 文件引用。但是 Transform 的使用需要依赖 Gradle Plugin。因此我们第一步需要创建一个单独的 Gradle Plugin,并在 Gradle Plugin 中使用自定义 Transform 找出所有的 .class 文件。
- 遍历到目标 .class 文件 (Activity)之后,通过 ASM 动态注入需要被插入的字节码
如果第一步进行顺利,我们可以找出所有的 .class 文件。接下来就需要过滤出目标 Activity 文件,并在目标 Activity 文件的 onCreate 方法中,通过 ASM 插入相应的 log 日志字节码。
本文选自:拉勾教育专栏《Android 工程师进阶 34 讲》具体实现创建 ASMLifeCycleDemo 项目
创建主项目 ASMLifeCycleDemo,当前项目中只有一个 MainActivity,如下:
创建自定义 Gradle 插件
首先在 ASMLifeCycleDemo 项目中创建一个新的 module,并选择 Android Library 类型,命名为 asm_lifecycle_plugin。将 asm_lifecycle_plugin module 中除了 build.gradle 和 main 文件夹之外的所有内容都删除。然后在 main 目录下分别创建 groovy 和 java 目录,结构如下:
因为 Gradle 插件是使用 groovy 语言编写的,所以需要新建一个 groovy 目录,用来存放插件相关的.groovy类。
但 ASM 是 java 层面的框架,所以在 java 目录里存放 ASM 相关的类。 然后,在 groovy 中创建目录 danny.jiang.plugin,并在此目录中创建类 LifeCyclePlugin.groovy 文件。
在 LifeCyclePlugin 中重写 apply 方法,实现插件逻辑,因为是 demo 演示,所以我只是简单的打印 log 日志。 目录结构与代码如下:
以看出 LifeCyclePlugin 实现了 gradle api 中的 Plugin 接口。当我们在 app module 的 build.gradle 文件中使用此插件时,其 LifeCyclePlugin 的 apply 方法将会被自动调用。
接下来,将 asm_lifecycle_plugin module 的 build.gradle 中的内容全部删掉,改为如下内容:
group 和 version 都需要在 app module 引用此插件时使用。 所有的插件都需要被部署到 maven 库中,我们可以选择部署到远程或者本地。
这里只是演示,所以只是将插件部署到本地目录中。具体地址通过 repository 属性配置,如图所示我将其配置在项目根目录下的 asm_lifecycle_repo 目录下。
最后一步,创建 properties 文件。 在 plugin/src/main 目录下新建目录 resources/META-INF/gradle-plguins,然后在此目录下新建一个文件:danny.asm.lifecycle.properties,其中文件名 danny.asm.lifecycle 就是我们自定义插件的名称,稍后我们在 app module 中会使用到此名称。
在 .properties 文件中,需要指定我们自定义的插件类名 LifeCyclePlugin,如下所示:
至此,自定义 Gradle 插件就已经写完,现在可以在 Android Studio 的右边栏找到 Gradle 中点击 uploadArchives,执行 plugin 的部署任务:
可以看到,构建成功之后,在 Project 的根目录下将会出现一个 repo 目录,里面存放的就是我们的插件目标文件。测试 asm_lifecycle_plugin
为了测试自定义的 Gradle 插件是否可用,可以在 app module 中的 build.gradle 中引用此插件。
图中 ① 处就是在自定义 Gradle 插件中 properties 的文件名 (danny.asm.lifecycle)。图中 ② 处 dependencies 中的 classpath 是 group 值 + module 名 + version。
然后在命令行中使用 gradlew 执行构建命令,如果打印出我们自定义插件里的 log,则说明自定义 Gradle 插件可以使用:
其实现在已经有了一些比较成熟的三方 Gradle 插件,比如 hiBeaver。如果不喜欢从头创建 Gradle 插件,可以考虑尝试使用。自定义 Transform,实现遍历 .class 文件
自定义 Gradle 插件已经写好,接下来就需要实现遍历所有 .class 的逻辑。这部分功能主要依赖 Transform API。什么是 Transform ?
Transform 可以被看作是 Gradle 在编译项目时的一个 task,在 .class 文件转换成 .dex 的流程中会执行这些 task,对所有的 .class 文件(可包括第三方库的 .class)进行转换,转换的逻辑定义在 Transform 的 transform 方法中。实际上平时我们在 build.gradle 中常用的功能都是通过 Transform 实现的,比如混淆(proguard)、分包(multi-dex)、jar 包合并(jarMerge)。自定义 Transform
在 danny.jiang.plugin 目录中,新建 LifeCycleTransform.groovy,并继承 Transform 类。
可以看到,LifeCycleTransform 需要实现抽象类 Transform 中的抽象方法,具体有如下几个方法需要实现:
解释说明:Transform 主要作用是检索项目编译过程中的所有文件。通过这几个方法,我们可以对自定义 Transform 设置一些遍历规则,具体如下: getName:设置我们自定义的 Transform 对应的 Task 名称。
Gradle 在编译的时候,会将这个名称显示在控制台上。比如:Task :app:transformClassesWithXXXForDebug。 getInputType:在项目中会有各种各样格式的文件,通过 getInputType 可以设置 LifeCycleTransform 接收的文件类型,此方法返回的类型是 Set<QualifiedContent.ContentType> 集合。 ContentType 有以下 2 种取值。
- CLASSES:代表只检索 .class 文件;
- RESOURCES:代表检索 java 标准资源文件。
getScopes()这个方法规定自定义 Transform 检索的范围,具体有以下几种取值:
isIncremental() 表示当前 Transform 是否支持增量编译,我们不需要增量编译,所以直接返回 false 即可。 transform()在 自定义Transform 中最重要的方法就是 transform()。在这个方法中,可以获取到两个数据的流向。
- inputs:inputs 中是传过来的输入流,其中有两种格式,一种是 jar 包格式,一种是 directory(目录格式)。
- outputProvider:outputProvider 获取到输出目录,最后将修改的文件复制到输出目录,这一步必须做,否则编译会报错。
我们可以实现一个简易 LifeCycleTransform,功能是打印出所有 .class 文件。代码如下:
解释说明:
- 自定义的 Transform 名称为 LifeCycleTransform;
- 检索项目中 .class 类型的目录或者文件;
- 设置当前 Transform 检索范围为当前项目;
- 设置过滤文件为 .class 文件(去除文件夹类型),并打印文件名称。
将自定义的 LifeCycleTransform 注册到 Gradle 插件中
在 LifeCyclePlugin 中添加如下代码:
再次在命令行中执行 build 命令,可以看到 LifeCycleTransform 检索出的所有 .class 文件。
从图中可以看出,Gradle 编译时多了一个我们自定义的 LifeCycleTransform 类型的任务,并且将所有 .class 文件名打印出来,其中包含了我们需要的目标文件 MainActivity.class。使用 ASM,插入字节码到 Activity 文件
ASM 是一套开源框架,其中几个常用的 API 如下:
- ClassReader:负责解析 .class 文件中的字节码,并将所有字节码传递给 ClassWriter。
- ClassVisitor:负责访问 .class 文件中各个元素,还记得上一课时我们介绍的 .class 文件结构吗?ClassVisitor 就是用来解析这些文件结构的,当解析到某些特定结构时(比如类变量、方法),它会自动调用内部相应的 FieldVisitor 或者 MethodVisitor 的方法,进一步解析或者修改 .class 文件内容。
- ClassWriter:继承自 ClassVisitor,它是生成字节码的工具类,负责将修改后的字节码输出为 byte 数组。
添加 ASM 依赖
在 asm_lifecycle_plugin 的 build.gradle 中,添加对 ASM 的依赖,如下:
创建自定义 ASM Visitor 类
在 asm_lifecycle_plugin module 中的 src/main/java 目录下创建包 danny.jiang.asm,并分别创建 LifecycleClassVisitor.java 和 LifecycleMethodVisitor.java。代码如下: LifecycleClassVisitor.java
红框中,在 visitMethod 方法中,过滤出继承自 AppCompatActivity 的文件,并在 LifeCycleMethodVisitor.java 中对 onCreate 进行改造。 LifeCycleMethodVisitor.java
图中红框内是真正执行插入字节码的逻辑。可以看出 ASM 都是直接以字节码指令的方式进行操作的,所以如果想使用 ASM,需要程序员对字节码有一定的理解。如果对字节码不是很了解,也可以借助三方工具 ASM Bytecode Outline 来生成想要的字节码。修改 LifeCycleTransform 的 transform 方法,使用 ASM
各种 Visitor 都定义好之后,我们就可以修改 LifeCycleTransform 的 transform 方法,并将需要插桩的字节码插入到 MainActivity.class 文件中:
重新部署自定义 Gradle 插件,并运行主项目
上面几步如果一切执行顺利,那接下来就可以在点击 uploadArchives 重新部署 LifeCyclePlugin。
注意:重新部署时,需要先在 app module 的 build.gradle 中将插件依赖注释,否则报错。
部署成功之后,重新在 app 中依赖自定义插件并运行主项目,当 MainActivity 被打开时,会在 logcat 中看到如下日志:
后续如果我们有新的 Activity,比如新建一个 BActivity.java 如下:
并在 MainActivity 中设置点击事件跳转到 BActivity 中:
那么 Logcat 中的日志如下:
虽然我们在 MainActivity 和 BActivity 中并没有添加任何 log 日志逻辑,但是在编译期间,自定义的 LifeCyclePlugin 会自动为每一个 Activity 的 onCreate 方法中添加 log 日志逻辑。
读到这里你可能会有疑虑,如果在项目中打开了混淆,那注入的字节码还会正常 work 吗? 其实无需担心,因为混淆其实也是一个 Transform,叫作 ProguardTransform,它是在自定义的 Transform 之后执行。总结
本课时详细操作了一遍编译插桩的流程。期间涉及了几个知识点:
- Android APK 打包编译过程;
- 自定义 Gradle 插件;
- Transform API 的使用;
- ASM 的使用。
本课时作为一篇对编译插桩的入门指导,并没有对以上几个知识点做深入分析。你课后如果感兴趣,可以自行查阅相关资料。最后以一句话结束这一课时:
对技术的追求不仅仅要停留在会用 API,会写基本功能上,要想在技术上有更高的造诣,就需要深入到原理层面去认识代码运行的机制。
本文选自:拉勾教育专栏《Android 工程师进阶 34 讲》
好了,本课时的内容就讲完了,下一课时我将讲解通关类加载器 ClassLoader,记得按时来听课。
版权声明:本文版权归属拉勾教育及该专栏作者,任何媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表,违者必究。
更多推荐
所有评论(0)