一、Transform介绍
从 1.5.0-beta1 开始,Gradle 插件包含一个 Transform API,允许 3rd 方插件在将编译的类文件转换为 dex 文件之前对其进行操作。(API 在 1.4.0-beta2 中存在,但在 1.5.0-beta1 中已完全修改)此 API 的目标是简化注入自定义类操作而无需处理任务,并为操作的内容提供更大的灵活性。内部代码处理(jacoco、progard、multi-dex)在 1.5.0-beta1 中已经全部转移到这个新机制。
简单来说,TransformAPI可以让我们在编译打包安卓项目时,在源码编译为class字节码后,处理成dex文件前,对字节码做一些操作。
二、Transform方法
实现自定义的Transform一般要复写如下几个方法,下面对每个方法做一下详细解释:
- getName()
getName()方法用于指明自定义的Transform的名称,在gradle执行该任务时,会将该Transform的名称再加上前后缀,如上面图中所示的,最后的task名称是transformClassesWithXXXForXXX这种格式。如下:
> Task :app:transformClassesWithMyTransformForDebug
MyTransform就是我们自定义的Transform。
getInputTypes()
用于指明 Transform 的输入类型,可以作为输入过滤的手段。在 TransformManager 类中定义了很多类型,我们常用的就是TransformManager.CONTENT_CLASS
getScopes()
用于指明 Transform 的作用域。同样,在 TransformManager 类中定义了几种范围,常用的是 SCOPE_FULL_PROJECT ,代表所有 Project 。确定了 ContentType 和 Scope 后就确定了该自定义 Transform 需要处理的资源流。比如 CONTENT_CLASS 和 SCOPE_FULL_PROJECT 表示了所有项目中 java 编译成的 class 组成的资源流。
isIncremental()
指明该 Transform 是否支持增量编译。需要注意的是,即使返回了 true ,在某些情况下运行时,它还是会返回 false 的。transform(TransformInvocation transformInvocation)
一般在这个方法中对字节码做一些处理。
完整的自定类Transform如下:
package com.example.test_plugin;
import com.android.build.api.transform.DirectoryInput;
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.TransformInvocation;
import com.android.build.api.transform.TransformOutputProvider;
import com.android.build.gradle.internal.pipeline.TransformManager;
import java.io.IOException;
import java.util.Collection;
import java.util.Set;
public class MyTransform extends Transform {
public MyTransform() {
System.out.println("Hello MyTransform..1111.");
}
@Override
public String getName() {
return "MyTransform";
}
@Override
public Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS;
}
@Override
public Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT;
}
@Override
public boolean isIncremental() {
return false;
}
@Override
public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation);
System.out.println("Hello MyTransform..2222222222222222.");
//是否支持增量编译
boolean incremental = transformInvocation.isIncremental();
//获取输出
TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();
//如果不支持增量编译,需要把之前生成的都删除,不复用缓存
if (!incremental) {
outputProvider.deleteAll();
}
for (TransformInput input : transformInvocation.getInputs()) {
//处理jar
Collection<JarInput> jarInputs = input.getJarInputs();
if (jarInputs!= null && jarInputs.size()>0){
for (JarInput jarInput : jarInputs) {
System.out.println("-----------name = "+jarInput.getName());
}
}
}
for (TransformInput input : transformInvocation.getInputs()) {
//处理jar
Collection<DirectoryInput> directoryInputs = input.getDirectoryInputs();
if (directoryInputs!= null && directoryInputs.size()>0){
for (DirectoryInput directoryInput :
directoryInputs) {
String name = directoryInput.getName();
System.out.println("-----------name = "+name);
}
}
}
}
}
三、使用Transform
将插件发布到本地仓库中后,我们在另外一个工程集成插件。
setting.gradle中添加本地仓库依赖
pluginManagement {
repositories {
//加上mavenLocal()会在我们user目录中的.m2文件夹下加载插件
mavenLocal()
gradlePluginPortal()
google()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "AGPTest2"
include ':app'
接下来在project的build.gradle中添加自定义插件的classpath,buildscript 一定要放在文件的最上面,即plugins{}的上面。
buildscript {
dependencies {
// 自定义的插件
classpath "com.example.test_plugin:MyPlugin:1.0.0"
}
}
plugins {
id 'com.android.application' version '7.2.2' apply false
id 'com.android.library' version '7.2.2' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}
然后点击sync同步工程,build打印如下:
> Configure project :app
//这个是自定义plugin中apply方法中的打印
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
//这个是MyTransform构造函数中加的打印
Hello MyTransform..1111.
> Task :prepareKotlinBuildScriptModel UP-TO-DATE
BUILD SUCCESSFUL in 5s
同时也可以看到一个打印
API 'android.registerTransform' is obsolete.
It will be removed in version 8.0 of the Android Gradle plugin.
The Transform API is removed to improve build performance. Projects that use the
Transform API force the Android Gradle plugin to use a less optimized flow for the
build that can result in large regressions in build times. It’s also difficult to
use the Transform API and combine it with other Gradle features; the replacement
APIs aim to make it easier to extend the build without introducing performance or
correctness issues.
There is no single replacement for the Transform API—there are new, targeted
APIs for each use case. All the replacement APIs are in the
`androidComponents {}` block.
For more information, see https://developer.android.com/studio/releases/gradle-plugin-api-updates#transform-api.
To determine what is calling android.registerTransform, use -Pandroid.debug.obsoleteApi=true on the command line to display more information.
Affected Modules: app
网上查了下gradle插件7.0以后的版本已经将Transform类废弃了,且在gradle插件8.0后的版本中删除。也就是说我们要是使用8.0的插件就要重新去适配了。
下面我们点击makeproject编译一下,看下日志打印
> Task :app:transformClassesWithMyTransformForDebug
Hello MyTransform..2222222222222222.
-----------name = e7ec8f42bf9ecfd490b4d2f6650f62c44d97ce8b
-----------name = 5e85c75d151005536ab1b95f769308b4176c0370
-----------name = ceeec43640bb729acc5da4ae8aa5484027d464e9
-----------name = 4586c2585f55ebd93f1225d84efe4c9b0c641ede
-----------name = e4f913597bef93f7aab8d178381df67b837d6e5f
-----------name = 29a643378b50632bde83cd9ad75cc7d0b0b6c827
-----------name = 5e7b19787e83c522b7fa80f4369c441caebe416a
-----------name = d66cab69d9f8f6517cd4b37c637a9c071e4d47f8
-----------name = 16ae442b038162ee3afda0d5a5342317cca2f2f8
-----------name = eb3cd54684b5f9b0deb4a49f940d719ebcf782af
-----------name = cd052027d5bcbe254b57571f0d0a9c5d96221c5a
-----------name = 5ee16216ca36e0d891ba50bfa4d53c0dcc34f9d4
-----------name = 88f9b54a2e1a5559f78d14a0440ef949a7f49174
-----------name = adeeb9135b1f0ca214043160d4c76b7f8f83d621
-----------name = c3ccb50a3a7db3fa1a78d74fb63ebc4c0d99657b
-----------name = 48a1b65441cb63987d2678214c4a39022407f480
-----------name = 40915a26fb6370c09465623a422a0669a36a3c2b
-----------name = 13a00d5ddee0a60336a86b30f5d9df15cbcfd367
-----------name = d07572def3a6da380429006e45e7f3567c2f71d4
-----------name = a3ea4023c9ab427f538e16e3b293b47a2653b3b4
-----------name = 8d0e0767185c160d8dba2702cc48a15aa2b0c162
-----------name = 29c3923b3f9177532529fcfb63c121b526c10daa
-----------name = f4d1a6d741eb208668817d980477083eced0f516
-----------name = 5e74fcb46599fa0a2b5f446d5fedf15e8c785a80
-----------name = bdf81e81e6085244c3f7c00a31d814fe58819faf
-----------name = 0c39cb4bff00f99545a55c689c057da46cfa8473
-----------name = 0ebd3d833a3291efe364da0fb08ecc34e294826e
-----------name = 3c2ade5802777fc6c5ad08f1f675003f73c9a1eb
-----------name = b84f24406382ded9b327796147be46005af17e51
-----------name = e26ece7351729f1e089c009f8f39d7908017a07e
-----------name = 91464501c2b25aaaceb76f0ca98a7cbe30912de0
-----------name = 1e0ab6a8775dddbf0075405823728830af4d3bb8
-----------name = bbfb7a466dfddd80f750335c9b2b63f9bc8e319c
-----------name = d9c50cfdd3fd04d3f5ccd4b5be8259713463f54d
-----------name = 597321e777dc86031329325e6644efa5e006a447
-----------name = d6b2a06a050f3fe1bd5e7d22eab82822cc41a5f9
-----------name = ab2dabf3b2c8afab1e2e25bfff6db7ca0640dc11
-----------name = 5a3370db105b6d5b65e161490af592b37c383076
-----------name = 4dc558cf34ce4e5f42bfdad7d68b09b1dc4faaf7
-----------name = a9db3a407db96c9052dd2b9b2d50c286a7a04c74
-----------name = 62af1ffde5bad5bf3c26d0372c8b7d8b3de6e31a
-----------name = dd2f57f1ac126f50f918bbdd3452bb8db05a0269
-----------name = 159d7220100c5bb15845f5eeebcd88df83776dd1
-----------name = 6b8af78f2ead300fb55abe5499f044cbfb0e89f1
-----------name = 68e7df6c8e6a81d5e23caa53db7f446d47beb246
-----------name = 27918f1d3618ed8cd80d47ecf93ca58f2be0a357
-----------name = 2bda8da70e0b8dacf32533ea74b0861cfc96ac07
-----------name = ec9549ae6717cf79f7abc0c074a969a53a0fce14
-----------name = cf9a6d0dbd42d1cc0c99c7f0da512b3b51d34a78
-----------name = 2bcb6cb60b5fd3fd390a75c4dd9b0603a47e2e09
-----------name = 1b5cc4d85fdc7e38ca388a0de3cfb1cb4b09879f
-----------name = c004cca63a24f3abe43c4f247b827eee30d2bbd1
-----------name = 738bde333103bb986360d94ae0b316c67161c49c
-----------name = c53b27a931aec69a656098a7015674c2784a3fcf
-----------name = 186c6b371c294a35776381254c1039e97e5c0860
-----------name = 85ff3092a968484cde3ad1c61b84113bfce18f37
-----------name = c5e5d904920026492d32b34d17fa9f8d0928ee31
可以看到transform方法中的加的日志成功打印了。
Transform为我们提供了在.class生成dex之前加载和修改class文件的入口,那么如何去修改class字节码,我们在下一篇ASM文章中介绍。