文章來(lái)源自作者的Android進(jìn)階計(jì)劃(https://github.com/SusionSuc/AdvancedAndroid)
在前面學(xué)習(xí)WMRouter
和ARouter
時(shí)都涉及到了Transform API
。他們都利用Transform
在編譯生成class文件之后和生成dex文件之前來(lái)做一些事情修档,本文就具體了解一下Gradle Transform API
卧惜。
什么是Transform
我們編譯Android項(xiàng)目時(shí)拥刻,如果我們想拿到編譯時(shí)產(chǎn)生的Class文件齿椅,并在生成Dex之前做一些處理灸蟆,我們可以通過(guò)編寫(xiě)一個(gè)Transform
來(lái)接收這些輸入(編譯產(chǎn)生的Class文件),并向已經(jīng)產(chǎn)生的輸入中添加一些東西曼验。
我們可以通過(guò)Gradle插件來(lái)注冊(cè)我們編寫(xiě)的Transform
卡骂。注冊(cè)后的Transform
會(huì)被Gradle包裝成一個(gè)Gradle Task
,這個(gè)TransForm Task會(huì)在java compile Task
執(zhí)行完畢后運(yùn)行煮甥。
對(duì)于編寫(xiě)Transform
的API, 我們可以通過(guò)引入下面這個(gè)依賴(lài)來(lái)使用:
compile 'com.android.tools.build:gradle:2.3.3' //版本應(yīng)該在 2.x以上
先大致看一下Transform
的執(zhí)行流程圖:
Transform的使用場(chǎng)景
一般我們使用Transform
會(huì)有下面兩種場(chǎng)景
- 我們需要對(duì)編譯class文件做自定義的處理盗温。
- 我們需要讀取編譯產(chǎn)生的class文件,做一些其他事情成肘,但是不需要修改它卖局。
接下來(lái)我們就來(lái)看一下這些Transform API
吧 :
Transform API學(xué)習(xí)
我們編寫(xiě)一個(gè)自定義的transform需要繼承Transform
,它是一個(gè)抽象類(lèi), 我們這里先看一下Transform
的抽象方法:
public abstract class Transform {
public abstract String getName();
public abstract Set<ContentType> getInputTypes();
public abstract Set<? super Scope> getScopes();
public abstract boolean isIncremental(); // 是否支持增量編譯
}
getName()
就是指定自定義的Transform
的名字。
輸入的類(lèi)型
Set<ContentType> getInputTypes()
是指明你自定義的這個(gè)Transform
處理的輸入類(lèi)型双霍,輸入類(lèi)型共有以下幾種:
enum DefaultContentType implements ContentType {
/**
* The content is compiled Java code. This can be in a Jar file or in a folder. If
* in a folder, it is expected to in sub-folders matching package names.
*/
CLASSES(0x01),
/**
* The content is standard Java resources.
*/
RESOURCES(0x02);
}
即分為class文件或者java資源砚偶。class文件來(lái)自于jar或者文件夾批销。資源就是標(biāo)準(zhǔn)的java資源。
輸入文件所屬的范圍 Scope
getScopes()
用來(lái)指明自定的Transform
的輸入文件所屬的范圍, 這是因?yàn)間radle是支持多工程編譯的蟹演》缱辏總共有以下幾種:
/**
* This indicates what the content represents, so that Transforms can apply to only part(s)
* of the classes or resources that the build manipulates.
*/
enum Scope implements ScopeType {
/** Only the project content */
PROJECT(0x01), //只是當(dāng)前工程的代碼
/** Only the project's local dependencies (local jars) */
PROJECT_LOCAL_DEPS(0x02), // 工程的本地jar
/** Only the sub-projects. */
SUB_PROJECTS(0x04), // 只包含子工工程
/** Only the sub-projects's local dependencies (local jars). */
SUB_PROJECTS_LOCAL_DEPS(0x08),
/** Only the external libraries */
EXTERNAL_LIBRARIES(0x10),
/** Code that is being tested by the current variant, including dependencies */
TESTED_CODE(0x20),
/** Local or remote dependencies that are provided-only */
PROVIDED_ONLY(0x40);
}
對(duì)于getScopes()
的返回顷蟀,其實(shí)TransformManager
已經(jīng)為我們定義了一些酒请,比如:
public static final Set<Scope> SCOPE_FULL_PROJECT = Sets.immutableEnumSet(
Scope.PROJECT, Scope.PROJECT_LOCAL_DEPS, Scope.SUB_PROJECTS, Scope.SUB_PROJECTS_LOCAL_DEPS, Scope.EXTERNAL_LIBRARIES);
如果一個(gè)Transform不想處理任何輸入,只是想查看輸入的內(nèi)容,那么只需在getScopes()
返回一個(gè)空集合鸣个,在getReferencedScopes()
返回想要接收的范圍羞反。
public Set<? super Scope> getReferencedScopes() {
return ImmutableSet.of();
}
transform()
它是Transform
的關(guān)鍵方法:
public void transform(@NonNull TransformInvocation transformInvocation) {}
它是一個(gè)空實(shí)現(xiàn),input
的內(nèi)容將會(huì)打包成一個(gè)TransformInvocation
對(duì)象囤萤,因?yàn)槲覀円胧褂?code>input,我們需要詳細(xì)了解一下TransformInvocation
參數(shù)昼窗。
TransformInvocation
我們看一下這個(gè)類(lèi)相關(guān)的API:
public interface TransformInvocation {
Collection<TransformInput> getInputs(); // 輸入作為 TransformInput 返回
TransformOutputProvider getOutputProvider(); //TransformOutputProvider 可以用來(lái)創(chuàng)建輸出內(nèi)容
boolean isIncremental();
}
public interface TransformInput {
Collection<JarInput> getJarInputs();
Collection<DirectoryInput> getDirectoryInputs();
}
public interface JarInput extends QualifiedContent {
File getFile(); //jar文件
Set<ContentType> getContentTypes(); // 是class還是resource
Set<? super Scope> getScopes(); //屬于Scope:
}
DirectoryInput與JarInput定義基本相同。
public interface TransformOutputProvider {
//根據(jù) name涛舍、ContentType澄惊、QualifiedContent.Scope返回對(duì)應(yīng)的文件( jar / directory)
File getContentLocation(String name, Set<QualifiedContent.ContentType> types, Set<? super QualifiedContent.Scope> scopes, Format format);
}
即我們可以通過(guò)TransformInvocation
來(lái)獲取輸入,同時(shí)也獲得了輸出的功能。舉個(gè)例子富雅,
public void transform(TransformInvocation invocation) {
for (TransformInput input : invocation.getInputs()) {
input.getJarInputs().parallelStream().forEach(jarInput -> {
File src = jarInput.getFile();
JarFile jarFile = new JarFile(file);
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
//處理
}
}
}
上面這段代碼就是獲取jar的輸入掸驱,然后遍歷每一個(gè)jar做一些自定義的處理。
我們?cè)谧鐾曜远x的處理后没佑,如果想自己輸出一些東西怎么辦毕贼? 比如一個(gè)class文件,就可以通過(guò)TransformOutputProvider
來(lái)完成。比如下面這段代碼:
File dest = invocation.getOutputProvider().getContentLocation("susion", TransformManager.CONTENT_CLASS, ImmutableSet.of(QualifiedContent.Scope.PROJECT), Format.DIRECTORY;
這段代碼就是在本工程(ImmutableSet.of(QualifiedContent.Scope.PROJECT)
)下產(chǎn)生一個(gè)目錄(Format.DIRECTORY
), 目錄的名字是(susion
),里面的內(nèi)容是TransformManager.CONTENT_CLASS
蛤奢。
創(chuàng)建這個(gè)文件夾后鬼癣,我們就可以向其中寫(xiě)入一些內(nèi)容,比如class文件啤贩。
注冊(cè)Transform
我們?cè)诹私?code>transform api后待秃,我們可以編寫(xiě)一個(gè)自定義的Transform
。但是我們編寫(xiě)的這個(gè)Transform
,如何在構(gòu)建過(guò)程中生效呢痹屹?我們需要注冊(cè)它
在自定義插件中注冊(cè)它,然后在build.gradle
中apply
就可以了锥余。
//MyCustomPlgin.groovy
public class MyCustomPlgin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getExtensions().findByType(BaseExtension.class)
.registerTransform(new MyCustomTransform());
}
}
其實(shí)如果你包含了你編寫(xiě)的transform庫(kù),我們也可以直接在build.gradle
中注冊(cè):
//在build.gradle中也是可以直接編寫(xiě) groovy代碼的痢掠。
project.extensions.findByType(BaseExtension.class).registerTransform(new MyCustomTransform());
End
好驱犹,有關(guān)transform的基本使用都已經(jīng)了解完畢。