一、原理
Google从Android Gradle 1.5.0开始,提供了Transform API。通过TransformAPI,允许第三方以插件(Plugin)的形式,在Android应用程序打包成.dex文件之前的编译过程中操作.class文件。我们只要实现一套Transform,去遍历所有.class文件的所有方法,然后进行修改(在特定listener的回调方法中插入埋点代码),最后再对源文件进行替换,即可达到插入代码的目的。
二、Gradle Transform
它是在.class转化为.dex期间用来修改.class文件的一套标准API。比较经典的应用是做字节码插桩、代码注入等。主要功能就是把输入的.class转化成目标字节码文件。
-
TransformInput 输入文件的一个抽象包含两个部分
- DirectoryInput 集合 是指以源码方式参与项目编译的所有目录结构以及目录下的源文件
- JarInput 集合 是指以jar包(包含aar)参与编译的所有本地jar以及远程jar包
TransformOutoutProvider Transform的输出,通过他可以获取输出路径等。
三、写一个Tranform ,把所有文件拷贝到输出目录
1.创建一个项目
2.创建一个 Android Library module,名称叫做plugin
3.清空plugin/build.gradle里面所有的内容,改成如下所示
apply plugin: 'groovy'
apply plugin: 'maven'
dependencies {
implementation gradleApi()
implementation localGroovy()
implementation 'com.android.tools.build:gradle:3.1.3'
}
repositories {
jcenter()
}
uploadArchives{
repositories.mavenDeployer{
//本地仓库路径,放到项目根目录下的repo的文件为列
repository(url:uri('../repo'))
//groupId 自行定义
pom.groupId = 'com.sensorsdata'
//artifactId
pom.artifactId = 'autotrack.android'
//插件版本号
pom.version = '1.0.2'
}
}
4.删除plugin/src/main目录下所有的文件
5.新建groovy目录
插件是groovy语言开发的,需要放到groovy目录下。在groovy目录下创建一个package,比如:com.sensorsdata.analytics.android.plugin。
6.在com.sensorsdata.analytics.android.plugin下创建SensorsAnalyticsTransform.groovy类。
package com.sensorsdata.analytics.android.plugin;
import com.android.build.api.transform.Context
import com.android.build.api.transform.DirectoryInput
import com.android.build.api.transform.Format
import com.android.build.api.transform.JarInput
import com.android.build.api.transform.QualifiedContent
import com.android.build.api.transform.Transform
import com.android.build.api.transform.TransformException
import com.android.build.api.transform.TransformInput
import com.android.build.api.transform.TransformOutputProvider
import com.android.build.gradle.internal.pipeline.TransformManager
import com.android.utils.FileUtils
import org.apache.commons.codec.digest.DigestUtils
import org.gradle.api.Project
class SensorsAnalyticsTransform extends Transform{
private static Project project
public SensorsAnalyticsTransform(Project project){
this.project = project
}
//这个是任务Task的名称
@Override
String getName() {
return "SensorsAnalyticsAutoTrack"
}
/**
* 需要处理的数据类型,有两种枚举类型
*TransformManager.CONTENT_CLASS 代表处理java的class文件
* TransformManager.CONTENT_RESOURCES 代表java的资源文件
* @return
*/
@Override
Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS
}
/**
*
* PROJECT 只处理当前项目
* SUB——POROJECT 只处理子项目
* PROJECT-LOCAL-DEPS 只处理项目中的本地依赖 例如 jar,aar
* SUB_PROJECTS_LOCAL_DECPS 只处理子项目的本地依赖
* EXTERNAL_LIBRARIES 只处理外部项目
* PROVIDED_ONLY 只处理本地或以provided形式引入的依赖库
* TESTED_CODE 测试代码
* TransformManager.SCOPE_FULL_PROJECT
* @return
*/
@Override
Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT
}
//是否增量构建
@Override
boolean isIncremental() {
return false
}
static void printCopyRight(){
println()
println("#########################################")
println("######### ###########")
println("######### ###########")
println("######### AZY ###########")
println("######### ###########")
println("######### ###########")
println("#########################################")
println()
}
@Override
void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {
printCopyRight()
//Transform的Inputs有两种类型,一种是目录,一种是jar包,要分开遍历
inputs.each {TransformInput input ->
//遍历目录
input.directoryInputs.each { DirectoryInput directoryInput ->
//获取output目录
def dest = outputProvider.getContentLocation(directoryInput.name,directoryInput.contentTypes,directoryInput.scopes,
Format.DIRECTORY)
//将input目录复制到output目录
FileUtils.copyDirectory(directoryInput.file,dest)
}
//遍历jar
input.jarInputs.each { JarInput jarInput ->
//重命名输出文件
def jarName = jarInput.name
def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
if(jarName.endsWith(".jar")){
jarName = jarName.substring(0,jarName.length() - 4)
}
File copyJarFile = jarInput.file
//生成输出路径
def dest = outputProvider.getContentLocation(jarName+md5Name,
jarInput.contentTypes,jarInput.scopes,Format.JAR)
//将input的目录复制到output指定目录
FileUtils.copyFile(copyJarFile,dest)
}
}
}
}
注意:自定义Transform即使什么都不做,也需要把输入文件拷贝到目标目录下,否则下一个Task就没有TransformInput了。如果我们什么都不做,那么打包的时候apk中缺少.class文件。
7.新建SensorsAnalyticsPlugin类,用来注册SensorsAnalyticsTransform
package com.sensorsdata.analytics.android.plugin
import com.android.build.gradle.AppExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
public class SensorsAnalyticsPlugin implements Plugin<Project>{
@Override
void apply(Project project) {
AppExtension appExtension = project.extensions.findByType(AppExtension.class)
appExtension.registerTransform(new SensorsAnalyticsTransform(project))
}
}
8.创建properties文件
在plugin/src/main目录下新建目录resources/META-INF/gradle-plugins(这是三个文件夹),然后此目录下创建一个文件:com.sensorsdata.android.properties,com.sensorsdata.android是用来指定我们的插件名称的。也就是apply plugin 'com.sensorsdata.android'。文件具体类容如下(一般是包名+类名)
implementation-class=com.sensorsdata.analytics.android.plugin.SensorsAnalyticsPlugin
9.执行plugin的uploadArchives任务构建plugin,如下图
执行成功之后可以在项目跟路径下查看repo文件夹
10.修改根目录下的build.gradle文件,添加对应的插件(添加备注“//新增”两处)
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
//新增
maven{
url uri('repo')
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.2'
//新增
classpath 'com.sensorsdata:autotrack.android:1.0.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
11.在app/build.gradle文件中声明使用插件
12.通过执行命令:./gradlew assembleDebug
如果编译没有出错,可以看到Transform中的日志打印,也可以查看对一个的输入文件。
四、遇到的问题
1.把plugin 写成pulgin
2.把resources/META-INF/gradle-plugins 三个文件夹,写出了一个文件夹resources.META-INF.gradle-plugins 导致app中 apply 插件时候找不到对应的插件。花了我4.5个小时找这个问题。
./gradlew compileDebug --stacktrace. 查看具体报错信息
最后附上源码地址 http://github.com/yangzai100/AutoTrackTransformProject