编译插桩是什么
编译插桩就是在代码编译期间修改已有的代码或者生成新代码。Dagger、ButterKnife甚至是Kotlin语言,它们都用到了编译插桩的技术。
具体实现有如下两种方式:
- 在.java文件编译成.class文件时,APT、AndroidAnnotation等就是在此处触发代码生成。
- 在 .class 文件进一步优化成 .dex 文件时,也就是直接操作字节码文件,这种方式功能更加强大,应用场景也更多。但是门槛比较高,需要对字节码有一定的理解。具体处理如下图:
一般情况下,我们经常会使用编译插桩实现如下几种功能:
日志埋点;
性能监控;
动态权限控制;
业务逻辑跳转时,校验是否已经登录;
甚至是代码调试等。
插桩工具介绍
目前市面上主要流行两种实现编译插桩的方式:
AspectJ
AspectJ是老牌AOP(Aspect-OrientedProgramming)框架,,如果你做过 J2EE 开发可能对这个框架更加熟悉,经常会拿这个框架跟 Spring AOP 进行比较。其主要优势是成熟稳定,使用者也不需要对字节码文件有深入的理解。
ASM
目前另一种编译插桩的方式ASM越来越受到广大工程师的喜爱。通过ASM可以修改现有的字节码文件,也可以动态生成字节码文件,并且它是一款完全以字节码层面来操纵字节码并分析字节码的框架。
下面介绍使用 ASM 来实现简单的编译插桩效果,通过插桩实现课时开始讲的需求,在每一个 Activity 打开时输出相应的 log 日志。
使用 ASM
实现思路
过程主要包含两步:
- 找到项目中编译生成的所有.class文件
AndroidStudio使用Gradle编译项目中的.java文件,并且从Gradle1.5.0之后,我们可以自己定义Transform,来获取所有.class文件引用。
但是Transform的使用需要依赖GradlePlugin。因此我们第一步需要创建一个单独的GradlePlugin,并在 Gradle Plugin 中使用自定义 Transform 找出所有的 .class 文件。遍历到目标.class文件(Activity)之后,
2.通过ASM动态注入需要被插入的字节码
如果第一步进行顺利,我们可以找出所有的.class文件。接下来就需要过滤出目标 Activity 文件,并在目标 Activity 文件的 onCreate 方法中,通过 ASM 插入相应的 log 日志字节码。
具体实现
1、创建 ASMLifeCycleDemo 项目
创建主项目 ASMLifeCycleDemo,当前项目中只有一个 MainActivity;
2、创建自定义Gradle插件
2.1、首先在ASMLifeCycleDemo项目中创建一个新的module,并选择AndroidLibrary类型,命名为asm_lifecycle_plugin。将asm_lifecycle_pluginmodule中除了build.gradle和main文件夹之外的所有内容都删除。然后在 main 目录下分别创建 groovy 和 java 目录;
因为Gradle插件是使用groovy语言编写的,所以需要新建一个groovy目录,用来存放插件相关的.groovy类。但ASM是java层面的框架,所以在 java 目录里存放 ASM 相关的类。
2.2、然后,在groovy中创建目录danny.jiang.plugin,并在此目录中创建类LifeCyclePlugin.groovy文件。在LifeCyclePlugin中重写 apply 方法,实现插件逻辑,因为是 demo 演示,所以我只是简单的打印 log 日志。
1 | package com.hopechart.plugin |
可以看出LifeCyclePlugin实现了gradleapi中的Plugin接口。当我们在appmodule的build.gradle文件中使用此插件时,其LifeCyclePlugin的apply方法将会被自动调用。
2.3、接下来,将asm_lifecycle_pluginmodule的build.gradle中的内容全部删掉,改为如下内容:
1 | apply plugin: 'groovy' |
group和version都需要在appmodule引用此插件时使用。所有的插件都需要被部署到maven库中,我们可以选择部署到远程或者本地。这里只是演示,所以只是将插件部署到本地目录中。具体地址通过 repository 属性配置,如图所示我将其配置在项目根目录下的 asm_lifecycle_repo 目录下。
2.4、最后一步,创建properties文件。在plugin/src/main目录下新建目录resources/META-INF/gradle-plguins,然后在此目录下新建一个文件:danny.asm.lifecycle.properties,其中文件名 danny.asm.lifecycle 就是我们自定义插件的名称,稍后我们在 app module 中会使用到此名称。配置文件内容
1 | implementation-class=com.hopechart.plugin.LifeCyclePlugin |
在 .properties 文件中,需要指定我们自定义的插件类名 LifeCyclePlugin,
2.5、至此,自定义 Gradle 插件就已经写完,现在可以在 Android Studio 的右边栏找到 Gradle 中点击 uploadArchives,执行 plugin 的部署任务;
2.6、构建成功之后,在 Project 的根目录下将会出现一个 asm_lifecycle_repo目录,里面存放的就是我们的插件目标文件。
测试 asm_lifecycle_plugin
为了测试自定义的 Gradle 插件是否可用,可以在 app module 中的 build.gradle 中引用此插件。
1 | apply plugin: 'com.android.application' |
然后在命令行中使用 gradlew 执行构建命令,如果打印出我们自定义插件里的 log,则说明自定义 Gradle 插件可以使用