从 1.5.0-beta1 开始,Gradle 插件包含一个 Transform API,允许第三方插件在将已编译的类文件转换为 dex 文件之前对其进行操作。(该 API 已存在于 1.4.0-beta2 中,但已在 1.5.0-beta1 中进行了彻底修改)
Transform API 的目标是简化注入自定义类的操作而不必处理任务,并为操作内容提供更大的灵活性。内部代码处理(jacoco,progard,multi-dex)已经在 1.5.0-beta1 中转移到了这一新机制。
Transform 的注册和使用非常简单,在我们自定义的 Gradle 插件中,只需创建一个实现 Transform 接口的类,然后将其注册到 android.registerTransform(theTransform) 或 android.registerTransform(theTransform, dependencies) 中即可。
Transform 是一个链式结构,每个 Transform 都是一个 Gradle 的 Task,Android 编译器通过 TaskManager 将每个 Transform 串联起来。
Gradle Transform 是 Android 官方提供给开发者在项目构建阶段,即由 .class 到 .dex 转换期间修改 .class 文件的一套 API。目前比较经典的应用是字节码插桩、代码注入技术。
Transform.java 是一个抽象类,我们在使用是,需要实现它,它的主要方法有哪些呢?
用于指定 Transform 的名字,对应了该 Transform 的 Task 的名称。
该方法指明是否支持增量编译,增量编译用于加快编译速度。
指定 Transform 要处理的数据类型,可以作为输入过滤的一种手段。
在 TransformManager 中定义了很多类型:
用于指定 Transform 的作用域。同样在 TransformManager 中定义了很多类型。
常见的作用域有:
PROJECT:只处理当前项目。
SUB_PROJECT:只处理子项目。
PROJECT_LOCAL_DEPS:只处理当前项目的本地依赖,例如:jar、aar。
SUB_PROJECT_LOCAL_DEPS:只处理子项目的本地依赖,例如:jar、aar。
EXTERNAL_LIBRARIES:只处理外部依赖库。
PROVIDED_ONLY:只处理本地或远程以 provided 形式引入的依赖库。
TESTED_CODE:测试代码。
SCOPE_FULL_PROJECT:即代表所有 Project。
确定了 ContentType 和 Scope 后就确定了该自定义 Transform 需要处理的资源流。
例如,上面提到的常用输入类型(CONTENT_CLASS)和常用作用域(SCOPE_FULL_PROJECT)表示的就是 所有项目中 java 编译成的 class 组成的资源流。
transform 方法来处理中间转换过程,主要逻辑在该方法中实现。
它的定义:
public void transform(@NonNull TransformInvocation transformInvocation)
该方法的参数是 TransformInvocation。
我们可以在 transform 方法中,实现对字节码的修改、处理等操作。
我们可以通过 TransformInvocation 来获取输入,同时也获得了输出的功能。
TransformInvocation 接口定义如下:
public interface TransformInvocation {
@NonNull Context getContext();
//TransformInput 是输入文件的抽象,包括 jar 和目录格式。
@NonNull Collection<TransformInput> getInputs();
@NonNull Collection<TransformInput> getReferencedInputs();
@NonNull Collection<SecondaryInput> getSecondaryInputs();
// Transform 的输出,通过它可以获取输出路径。
@Nullable TransformOutputProvider getOutputProvider();
boolean isIncremental();
}
TransformInvocation 的使用,我们举例说明一下:
@Override
void transform(@NonNull TransformInvocation transformInvocation) {
def startTime = System.currentTimeMillis()
//如果是非增量编译,则删除之前的输出
if (!transformInvocation.isIncremental) {
transformInvocation.outputProvider.deleteAll()
}
//TransformInvocation 来获取输入
Collection<TransformInput> inputs = transformInvocation.inputs
//TransformInvocation 来获取输出
TransformOutputProvider outputProvider = transformInvocation.outputProvider
//遍历inputs
inputs.each { TransformInput input ->
//遍历directoryInputs
input.directoryInputs.each { DirectoryInput directoryInput ->
handleDirectoryInput(directoryInput, outputProvider)
}
//遍历jarInputs
input.jarInputs.each { JarInput jarInput ->
handleJarInputs(jarInput, outputProvider)
}
}
def cost = (System.currentTimeMillis() - startTime) / 1000
}
TransformInput 是指这些输入文件的抽象。它包括两部分:
是指以源码方式参与项目编译的所有目录结构及其目录下 的源码文件。
是指以 jar 包方式参与项目编译的所有本地 jar 包和远程 jar 包。
是 Transform 的输出的抽象,通过它可以获取输出路径。TransformOutputProvider 通过调用 getContentLocation 来获取输出目录:
@NonNull
File getContentLocation(
@NonNull String name,
@NonNull Set<QualifiedContent.ContentType> types,
@NonNull Set<? super QualifiedContent.Scope> scopes,
@NonNull Format format);
Transform 的注册和使用非常易懂, 在我们自定义的 plugin 内, 我们可以通过 android.registerTransform(theTransform) 或者 android.registerTransform(theTransform, dependencies) 就可以完成注册。
使用 Transform API 主要是写一个类继承 Transform,并把该 Transform 注入到打包过程中。
注入 Transform 很简单,先获取 com.android.build.gradle.AppExtension 对象,然后调用它的registerTransform() 方法。
这个方法实际上是属于 BaseExtension 的,AppExtension 继承自 BaseExtension:
#com.android.build.gradle.BaseExtension
public void registerTransform(@NonNull Transform transform, Object... dependencies) {
transforms.add(transform);
transformDependencies.add(Arrays.asList(dependencies));
}
注册我们自定义的 Transform:
void apply(Project project) {
AppExtension android = project.extensions.getByType(AppExtension)
android.registerTransform(new MethodTimeTransform(project))
}
通过获取 module 的 Project 的 AppExtension,通过它的 registerTransform 方法完成 Transform 的注册。
这里注册之后,会在编译过程中的 TransformManager#addTransform 中生成一个 task,然后在执行这个 task 的时候会执行到我们自定义的 Transform 的 transform 方法。这个 task 的执行时机就是 .class 文件转换成 .dex文 件的时候。
我们来看完整的实例代码。
自定义的 Transform 类,AsmTransform:
class AsmTransform extends Transform{
private Project mProject;
AsmTransform(Project project){
mProject = project
}
@Override
String getName() {
return "budaye_transform"
}
@Override
Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS
}
@Override
Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT
}
@Override
boolean isIncremental() {
return true
}
@Override
void transform(@NonNull TransformInvocation transformInvocation) {
def startTime = System.currentTimeMillis()
//如果是非增量编译,则删除之前的输出
if (!transformInvocation.isIncremental) {
transformInvocation.outputProvider.deleteAll()
}
Collection<TransformInput> inputs = transformInvocation.inputs
TransformOutputProvider outputProvider = transformInvocation.outputProvider
//遍历inputs
inputs.each { TransformInput input ->
//遍历directoryInputs
input.directoryInputs.each { DirectoryInput directoryInput ->
handleDirectoryInput(directoryInput, outputProvider)
}
//遍历jarInputs
input.jarInputs.each { JarInput jarInput ->
handleJarInputs(jarInput, outputProvider)
}
}
def cost = (System.currentTimeMillis() - startTime) / 1000
}
/**
* 处理文件目录下的class文件
*/
static void handleDirectoryInput(DirectoryInput directoryInput, TransformOutputProvider outputProvider) {
//是否是目录
if (directoryInput.file.isDirectory()) {
//列出目录所有文件(包含子文件夹,子文件夹内文件)
directoryInput.file.eachFileRecurse { File file ->
def name = file.name
if (checkClassFile(name)) {
println '----------- deal with "class" file <' + name + '> -----------'
ClassReader classReader = new ClassReader(file.bytes)
ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
ClassVisitor cv = new LifecycleClassVisitor(classWriter)
classReader.accept(cv, EXPAND_FRAMES)
byte[] code = classWriter.toByteArray()
FileOutputStream fos = new FileOutputStream(
file.parentFile.absolutePath + File.separator + name)
fos.write(code)
fos.close()
}
}
}
//处理完输入文件之后,要把输出给下一个任务
def dest = outputProvider.getContentLocation(directoryInput.name,
directoryInput.contentTypes, directoryInput.scopes,
Format.DIRECTORY)
FileUtils.copyDirectory(directoryInput.file, dest)
}
/**
* 处理Jar中的class文件
*/
static void handleJarInputs(JarInput jarInput, TransformOutputProvider outputProvider) {
if (jarInput.file.getAbsolutePath().endsWith(".jar")) {
//重名名输出文件,因为可能同名,会覆盖
def jarName = jarInput.name
def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
if (jarName.endsWith(".jar")) {
jarName = jarName.substring(0, jarName.length() - 4)
}
JarFile jarFile = new JarFile(jarInput.file)
Enumeration enumeration = jarFile.entries()
File tmpFile = new File(jarInput.file.getParent() + File.separator + "classes_temp.jar")
//避免上次的缓存被重复插入
if (tmpFile.exists()) {
tmpFile.delete()
}
JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(tmpFile))
//用于保存
while (enumeration.hasMoreElements()) {
JarEntry jarEntry = (JarEntry) enumeration.nextElement()
String entryName = jarEntry.getName()
ZipEntry zipEntry = new ZipEntry(entryName)
InputStream inputStream = jarFile.getInputStream(jarEntry)
//插桩class
if (checkClassFile(entryName)) {
//class文件处理
println '----------- deal with "jar" class file <' + entryName + '> -----------'
jarOutputStream.putNextEntry(zipEntry)
ClassReader classReader = new ClassReader(IOUtils.toByteArray(inputStream))
ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
ClassVisitor cv = new LifecycleClassVisitor(classWriter)
classReader.accept(cv, EXPAND_FRAMES)
byte[] code = classWriter.toByteArray()
jarOutputStream.write(code)
} else {
jarOutputStream.putNextEntry(zipEntry)
jarOutputStream.write(IOUtils.toByteArray(inputStream))
}
jarOutputStream.closeEntry()
}
//结束
jarOutputStream.close()
jarFile.close()
def dest = outputProvider.getContentLocation(jarName + md5Name,
jarInput.contentTypes, jarInput.scopes, Format.JAR)
FileUtils.copyFile(tmpFile, dest)
tmpFile.delete()
}
}
/**
* 检查class文件是否需要处理
* @param fileName
* @return
*/
static boolean checkClassFile(String name) {
//只处理需要的class文件
return (name.endsWith(".class") && !name.startsWith("R\$")
&& !"R.class".equals(name) && !"BuildConfig.class".equals(name)
&& "android/support/v4/app/FragmentActivity.class".equals(name))
}
}
AsmTransform 的注册:
@Override
void apply(Project project) {
if (project.plugins.hasPlugin(AppPlugin)){
//registerTransform
def android = project.extensions.getByType(AppExtension)
android.registerTransform(new AsmTransform(project))
}
}
到了这里,就实现了整个 Transform 的定义和注册过程了。
**PS:更多性能优化相关文章,请查看 --> 《Android 性能优化》
**PS:更多性能优化相关文章,请查看 --> 《Android 性能优化》
**PS:更多性能优化相关文章,请查看 --> 《Android 性能优化》