Gradle Transform API 的基本使用
文章來源自作者的Android進階計劃( ofollow,noindex">https://github.com/SusionSuc/AdvancedAndroid )
在前面學習 WMRouter
和 ARouter
時都涉及到了 Transform API
。他們都利用 Transform
在編譯生成class檔案之後和生成dex檔案之前來做一些事情,本文就具體瞭解一下 Gradle Transform API
。
什麼是Transform
我們編譯Android專案時,如果我們想拿到編譯時產生的Class檔案,並在生成Dex之前做一些處理,我們可以通過編寫一個 Transform
來接收這些輸入(編譯產生的Class檔案),並向已經產生的輸入中新增一些東西。
我們可以通過Gradle外掛來註冊我們編寫的 Transform
。註冊後的 Transform
會被Gradle包裝成一個 Gradle Task
,這個TransForm Task會在 java compile Task
執行完畢後執行。
對於編寫 Transform
的API, 我們可以通過引入下面這個依賴來使用:
compile 'com.android.tools.build:gradle:2.3.3'//版本應該在 2.x以上
先大致看一下 Transform
的執行流程圖:

GradleTransform處理流程.png
Transform的使用場景
一般我們使用 Transform
會有下面兩種場景
- 我們需要對編譯class檔案做自定義的處理。
- 我們需要讀取編譯產生的class檔案,做一些其他事情,但是不需要修改它。
接下來我們就來看一下這些 Transform API
吧 :
Transform API學習
我們編寫一個自定義的transform需要繼承 Transform
,它是一個抽象類, 我們這裡先看一下 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
的名字。
輸入的型別
Set<ContentType> getInputTypes()
是指明你自定義的這個 Transform
處理的輸入型別,輸入型別共有以下幾種:
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檔案來自於jar或者資料夾。資源就是標準的java資源。
輸入檔案所屬的範圍 Scope
getScopes()
用來指明自定的 Transform
的輸入檔案所屬的範圍, 這是因為gradle是支援多工程編譯的。總共有以下幾種:
/** * 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), //只是當前工程的程式碼 /** 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); }
對於 getScopes()
的返回,其實 TransformManager
已經為我們定義了一些,比如:
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);
如果一個Transform不想處理任何輸入,只是想檢視輸入的內容,那麼只需在 getScopes()
返回一個空集合,在 getReferencedScopes()
返回想要接收的範圍。
public Set<? super Scope> getReferencedScopes() { return ImmutableSet.of(); }
transform()
它是 Transform
的關鍵方法:
public void transform(@NonNull TransformInvocation transformInvocation) {}
它是一個空實現, input
的內容將會打包成一個 TransformInvocation
物件,因為我們要想使用 input
,我們需要詳細瞭解一下 TransformInvocation
引數。
TransformInvocation
我們看一下這個類相關的API:
public interface TransformInvocation { Collection<TransformInput> getInputs(); // 輸入作為 TransformInput 返回 TransformOutputProvider getOutputProvider(); //TransformOutputProvider 可以用來建立輸出內容 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 { //根據 name、ContentType、QualifiedContent.Scope返回對應的檔案( jar / directory) File getContentLocation(String name, Set<QualifiedContent.ContentType> types, Set<? super QualifiedContent.Scope> scopes, Format format); }
即我們可以通過 TransformInvocation
來獲取輸入,同時也獲得了輸出的功能。舉個例子,
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的輸入,然後遍歷每一個jar做一些自定義的處理。
我們在做完自定義的處理後,如果想自己輸出一些東西怎麼辦? 比如一個class檔案,就可以通過 TransformOutputProvider
來完成。比如下面這段程式碼:
File dest = invocation.getOutputProvider().getContentLocation("susion", TransformManager.CONTENT_CLASS, ImmutableSet.of(QualifiedContent.Scope.PROJECT), Format.DIRECTORY;
這段程式碼就是在本工程( ImmutableSet.of(QualifiedContent.Scope.PROJECT)
)下產生一個目錄( Format.DIRECTORY
), 目錄的名字是( susion
),裡面的內容是 TransformManager.CONTENT_CLASS
。
建立這個資料夾後,我們就可以向其中寫入一些內容,比如class檔案。
註冊Transform
我們在瞭解 transform api
後,我們可以編寫一個自定義的 Transform
。但是我們編寫的這個 Transform
,如何在構建過程中生效呢?我們需要註冊它
在自定義外掛中註冊它,然後在 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()); } }
其實如果你包含了你編寫的transform庫,我們也可以直接在 build.gradle
中註冊:
//在build.gradle中也是可以直接編寫 groovy程式碼的。 project.extensions.findByType(BaseExtension.class).registerTransform(new MyCustomTransform());
End
好,有關transform的基本使用都已經瞭解完畢。