推薦先看幾篇文章:
1.Android 熱修復使用Gradle Plugin1.5改造Nuwa插件
2.GradleTransformAPI的基本使用
3.Transform官方文檔
4.一起玩轉Android項目中的字節碼(強烈推薦)
5.Android編譯流程和Gradle使用
引用說明
本文章主要作個人總結記錄,感謝CSDNAndroid高級架構的資料和文章,如有侵權,請聯系刪除。
知識點
1. 打包過程
要了解Transform,首先我們需要知道Gradle構建一個安卓應用,會經過哪些步驟。
1.png
上圖是谷歌官網給出的一個典型的apk構建的過程,比較概括。主要包括兩個過程,首先是編譯過程,編譯的內容包括本工程的文件以及依賴的各種庫文件,編譯的輸出包括dex文件和編譯后的資源文件。然后是打包過程。配合Keystore對第一步的輸出進行簽名對齊,生成最終的apk文件。
2.png
上面這張圖對上面的步驟以及每步用到的工具進行了細分,概括如下:
- Java編譯器對工程本身的java代碼進行編譯,這些java代碼有三個來源:app的源代碼,由資源文件生成的R文件(aapt工具),以及有aidl文件生成的java接口文件(aidl工具)。產出為.class文件。
- .class文件和依賴的三方庫文件通過dex工具生成Delvik虛擬機可執行的.dex文件,可能有一個或多個,包含了所有的class信息,包括項目自身的class和依賴的class。產出為.dex文件。
- apkbuilder工具將.dex文件和編譯后的資源文件生成未經簽名對齊的apk文件。這里編譯后的資源文件包括兩部分,一是由aapt編譯產生的編譯后的資源文件,二是依賴的三方庫里的資源文件。產出為未經簽名的.apk文件。
- 分別由Jarsigner和zipalign對apk文件進行簽名和對齊,生成最終的apk文件。
2. 什么是Transform
簡單介紹:
從android-build-tool:gradle:1.5開始,gradle插件包含了一個叫Transform的API,這個API允許第三方插件在class文件轉為為dex文件前操作編譯好的class文件,這個API的目標就是簡化class文件的自定義的操作而不用對Task進行處理。
作用域:
Transform是作用在.class編譯后,打包成.dex前,可以對.class和resource進行再處理的部分。
為了印證,我們隨便建立一個項目Build的一次。
可以很清楚的看到,原生就帶了一系列Transform供使用。那么這些Transform是怎么組織在一起的呢,我們用一張圖表示:
image
每個Transform其實都是一個gradle task,Android編譯器中的TaskManager將每個Transform串連起來,第一個Transform接收來自javac編譯的結果,以及已經拉取到在本地的第三方依賴(jar. aar),還有resource資源,注意,這里的resource并非android項目中的res資源,而是asset目錄下的資源。 這些編譯的中間產物,在Transform組成的鏈條上流動,每個Transform節點可以對class進行處理再傳遞給下一個Transform。我們常見的混淆,Desugar等邏輯,它們的實現如今都是封裝在一個個Transform中,而我們自定義的Transform,會插入到這個Transform鏈條的最前面。
但其實,上面這幅圖,只是展示Transform的其中一種情況。而Transform其實可以有兩種輸入,一種是消費型的,當前Transform需要將消費型型輸出給下一個Transform,另一種是引用型的,當前Transform可以讀取這些輸入,而不需要輸出給下一個Transform,比如Instant Run就是通過這種方式,檢查兩次編譯之間的diff的。
最終,我們定義的Transform會被轉化成一個個TransformTask,在Gradle編譯時調用。
TransformManager.class
/**
* Adds a Transform.
*
* <p>This makes the current transform consumes whatever Streams are currently available and
* creates new ones for the transform output.
*
* <p>his also creates a {@link TransformTask} to run the transform and wire it up with the
* dependencies of the consumed streams.
*
* @param taskFactory the task factory
* @param scope the current scope
* @param transform the transform to add
* @param callback a callback that is run when the task is actually configured
* @param <T> the type of the transform
* @return {@code Optional<AndroidTask<Transform>>} containing the AndroidTask for the given
* transform task if it was able to create it
*/
@NonNull
public <T extends Transform> Optional<TransformTask> addTransform(
@NonNull TaskFactory taskFactory,
@NonNull TransformVariantScope scope,
@NonNull T transform,
@Nullable TransformTask.ConfigActionCallback<T> callback) {
if (!validateTransform(transform)) {
// validate either throws an exception, or records the problem during sync
// so it's safe to just return null here.
return Optional.empty();
}
List<TransformStream> inputStreams = Lists.newArrayList();
String taskName = scope.getTaskName(getTaskNamePrefix(transform));
// get referenced-only streams
List<TransformStream> referencedStreams = grabReferencedStreams(transform);
// find input streams, and compute output streams for the transform.
IntermediateStream outputStream = findTransformStreams(
transform,
scope,
inputStreams,
taskName,
scope.getGlobalScope().getBuildDir());
if (inputStreams.isEmpty() && referencedStreams.isEmpty()) {
// didn't find any match. Means there is a broken order somewhere in the streams.
issueReporter.reportError(
Type.GENERIC,
new EvalIssueException(
String.format(
"Unable to add Transform '%s' on variant '%s': requested streams not available: %s+%s / %s",
transform.getName(),
scope.getFullVariantName(),
transform.getScopes(),
transform.getReferencedScopes(),
transform.getInputTypes())));
return Optional.empty();
}
//noinspection PointlessBooleanExpression
if (DEBUG && logger.isEnabled(LogLevel.DEBUG)) {
logger.debug("ADDED TRANSFORM(" + scope.getFullVariantName() + "):");
logger.debug("\tName: " + transform.getName());
logger.debug("\tTask: " + taskName);
for (TransformStream sd : inputStreams) {
logger.debug("\tInputStream: " + sd);
}
for (TransformStream sd : referencedStreams) {
logger.debug("\tRef'edStream: " + sd);
}
if (outputStream != null) {
logger.debug("\tOutputStream: " + outputStream);
}
}
transforms.add(transform);
// create the task...
TransformTask task =
taskFactory.create(
new TransformTask.ConfigAction<>(
scope.getFullVariantName(),
taskName,
transform,
inputStreams,
referencedStreams,
outputStream,
recorder,
callback));
return Optional.ofNullable(task);
}
3.Transform解讀
我們首先先定義一個自定義的Transform,需要實現如下方法。
class AspectJTransform extends Transform {
final String NAME = "AjcTransform"
@Override
String getName() {
return NAME
}
@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(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation)
}
首先我們一項項分析:
(1)Name
@Override
String getName() {
return NAME
}
Name顧名思義,就是我們的Transform名稱,再回到我們剛剛Build的流程里:
這個最終的名字是如何構成的呢?好像跟我們這邊的定義的名字有區別。
在gradle plugin的源碼中有一個叫TransformManager的類,這個類管理著所有的Transform的子類,里面有一個方法叫getTaskNamePrefix,在這個方法中就是獲得Task的前綴,以transform開頭,之后拼接ContentType,這個ContentType代表著這個Transform的輸入文件的類型,類型主要有兩種,一種是Classes,另一種是Resources,ContentType之間使用And連接,拼接完成后加上With,之后緊跟的就是這個Transform的Name,name在getName()方法中重寫返回即可。代碼如下:
@NonNull
static String getTaskNamePrefix(@NonNull Transform transform) {
StringBuilder sb = new StringBuilder(100);
sb.append("transform");
sb.append(
transform
.getInputTypes()
.stream()
.map(
inputType ->
CaseFormat.UPPER_UNDERSCORE.to(
CaseFormat.UPPER_CAMEL, inputType.name()))
.sorted() // Keep the order stable.
.collect(Collectors.joining("And")));
sb.append("With");
StringHelper.appendCapitalized(sb, transform.getName());
sb.append("For");
return sb.toString();
}
(2)getInputTypes()
先來看代碼注釋,注釋寫的很清晰了,必須是CLASSES(0x01),RESOURCES(0x02)之一,相當于Transform需要處理的類型。
/**
* Returns the type(s) of data that is consumed by the Transform. This may be more than
* one type.
*
* <strong>This must be of type {@link QualifiedContent.DefaultContentType}</strong>
*/
@NonNull
public abstract Set<ContentType> getInputTypes();
----------------------------------
/**
* The type of of the content.
*/
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);
private final int value;
DefaultContentType(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
}
其實還有一些另外的,還沒研究
(3)getScopes()
先來看源碼注釋,這個的作用相當于用來Transform表明作用域
/**
* Returns the scope(s) of the Transform. This indicates which scopes the transform consumes.
*/
@NonNull
public abstract Set<Scope> getScopes();
開發一共可以選如下幾種:
/**
* The scope of the content.
*
* <p>
* 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 (module) content */
PROJECT(0x01),
/** Only the sub-projects (other modules) */
SUB_PROJECTS(0x04),
/** 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),
/**
* Only the project's local dependencies (local jars)
*
* @deprecated local dependencies are now processed as {@link #EXTERNAL_LIBRARIES}
*/
@Deprecated
PROJECT_LOCAL_DEPS(0x02),
/**
* Only the sub-projects's local dependencies (local jars).
*
* @deprecated local dependencies are now processed as {@link #EXTERNAL_LIBRARIES}
*/
@Deprecated
SUB_PROJECTS_LOCAL_DEPS(0x08);
一般來說如果是要處理所有class字節碼,Scope我們一般使用TransformManager.SCOPE_FULL_PROJECT。即
public static final Set<Scope> SCOPE_FULL_PROJECT =
Sets.immutableEnumSet(
Scope.PROJECT,
Scope.SUB_PROJECTS,
Scope.EXTERNAL_LIBRARIES);
還有一些其他的可以參考下。
(4)isIncremental()
增量編譯開關。
/**
* Returns whether the Transform can perform incremental work.
*
* <p>If it does, then the TransformInput may contain a list of changed/removed/added files, unless
* something else triggers a non incremental run.
*/
public abstract boolean isIncremental();
當我們開啟增量編譯的時候,相當input包含了changed/removed/added三種狀態,實際上還有notchanged。需要做的操作如下:
- NOTCHANGED: 當前文件不需處理,甚至復制操作都不用;
- ADDED、CHANGED: 正常處理,輸出給下一個任務;
- REMOVED: 移除outputProvider獲取路徑對應的文件。
(5)transform()
先來看一下源碼注釋,它是Transform處理文件的核心代碼:
/**
* Executes the Transform.
*
* <p>The inputs are packaged as an instance of {@link TransformInvocation}
* <ul>
* <li>The <var>inputs</var> collection of {@link TransformInput}. These are the inputs
* that are consumed by this Transform. A transformed version of these inputs must
* be written into the output. What is received is controlled through
* {@link #getInputTypes()}, and {@link #getScopes()}.</li>
* <li>The <var>referencedInputs</var> collection of {@link TransformInput}. This is
* for reference only and should be not be transformed. What is received is controlled
* through {@link #getReferencedScopes()}.</li>
* </ul>
*
* A transform that does not want to consume anything but instead just wants to see the content
* of some inputs should return an empty set in {@link #getScopes()}, and what it wants to
* see in {@link #getReferencedScopes()}.
*
* <p>Even though a transform's {@link Transform#isIncremental()} returns true, this method may
* be receive <code>false</code> in <var>isIncremental</var>. This can be due to
* <ul>
* <li>a change in secondary files ({@link #getSecondaryFiles()},
* {@link #getSecondaryFileOutputs()}, {@link #getSecondaryDirectoryOutputs()})</li>
* <li>a change to a non file input ({@link #getParameterInputs()})</li>
* <li>an unexpected change to the output files/directories. This should not happen unless
* tasks are improperly configured and clobber each other's output.</li>
* <li>a file deletion that the transform mechanism could not match to a previous input.
* This should not happen in most case, except in some cases where dependencies have
* changed.</li>
* </ul>
* In such an event, when <var>isIncremental</var> is false, the inputs will not have any
* incremental change information:
* <ul>
* <li>{@link JarInput#getStatus()} will return {@link Status#NOTCHANGED} even though
* the file may be added/changed.</li>
* <li>{@link DirectoryInput#getChangedFiles()} will return an empty map even though
* some files may be added/changed.</li>
* </ul>
*
* @param transformInvocation the invocation object containing the transform inputs.
* @throws IOException if an IO error occurs.
* @throws InterruptedException
* @throws TransformException Generic exception encapsulating the cause.
*/
public void transform(@NonNull TransformInvocation transformInvocation)
throws TransformException, InterruptedException, IOException {
// Just delegate to old method, for code that uses the old API.
//noinspection deprecation
transform(transformInvocation.getContext(), transformInvocation.getInputs(),
transformInvocation.getReferencedInputs(),
transformInvocation.getOutputProvider(),
transformInvocation.isIncremental());
}
大致意思如下,具體大家一定要仔細看注釋:
- 如果拿取了getInputs()的輸入進行消費,則transform后必須再輸出給下一級
- 如果拿取了getReferencedInputs()的輸入,則不應該被transform。
- 是否增量編譯要以transformInvocation.isIncremental()為準。
(6)getSecondaryFiles()
上面transform函數這里還提到了一個東西叫
secondary files ({@link #getSecondaryFiles()}
網上Transform的講解對它的提及比較少,先看看注釋,它和我們之前介紹的一樣,有一系列API:
/**
* Returns a list of additional file(s) that this Transform needs to run. Preferably, use
* {@link #getSecondaryFiles()} API which allow eah secondary file to indicate if changes
* can be handled incrementally or not. This API will treat all additional file change as
* a non incremental event.
*
* <p>Changes to files returned in this list will trigger a new execution of the Transform
* even if the qualified-content inputs haven't been touched.
*
* <p>Any changes to these files will trigger a non incremental execution.
*
* <p>The default implementation returns an empty collection.
*
* @deprecated replaced by {@link #getSecondaryFiles()}
*/
@Deprecated
@NonNull
public Collection<File> getSecondaryFileInputs() {
return ImmutableList.of();
}
/**
* Returns a list of additional file(s) that this Transform needs to run.
*
* <p>Changes to files returned in this list will trigger a new execution of the Transform
* even if the qualified-content inputs haven't been touched.
*
* <p>Each secondary input has the ability to be declared as necessitating a non incremental
* execution in case of change. This Transform can therefore declare which secondary file
* changes it supports in incremental mode.
*
* <p>The default implementation returns an empty collection.
*/
@NonNull
public Collection<SecondaryFile> getSecondaryFiles() {
return ImmutableList.of();
}
/**
* Returns a list of additional (out of streams) file(s) that this Transform creates.
*
* <p>These File instances can only represent files, not directories. For directories, use
* {@link #getSecondaryDirectoryOutputs()}
*
*
* <p>Changes to files returned in this list will trigger a new execution of the Transform
* even if the qualified-content inputs haven't been touched.
*
* <p>Changes to these output files force a non incremental execution.
*
* <p>The default implementation returns an empty collection.
*/
@NonNull
public Collection<File> getSecondaryFileOutputs() {
return ImmutableList.of();
}
/**
* Returns a list of additional (out of streams) directory(ies) that this Transform creates.
*
* <p>These File instances can only represent directories. For files, use
* {@link #getSecondaryFileOutputs()}
*
* <p>Changes to directories returned in this list will trigger a new execution of the Transform
* even if the qualified-content inputs haven't been touched.
*
* <p>Changes to these output directories force a non incremental execution.
*
* <p>The default implementation returns an empty collection.
*/
@NonNull
public Collection<File> getSecondaryDirectoryOutputs() {
return ImmutableList.of();
}
跟據字面意思理解,除了主輸入/輸出流之外,Transform還可以額外定義另外的流供下個使用,不過我們平時用到的不多,可以根據系統自帶的Transform源碼看看它輸出了啥,比如ProguardTransform:
public class ProGuardTransform extends BaseProguardAction {
......
private final ImmutableList<File> secondaryFileOutputs;
......
public ProGuardTransform(@NonNull VariantScope variantScope) {
......
secondaryFileOutputs = ImmutableList.of(printMapping, printSeeds, printUsage);
}
@NonNull
@Override
public Collection<SecondaryFile> getSecondaryFiles() {
final List<SecondaryFile> files = Lists.newArrayList();
if (testedMappingFile != null && testedMappingFile.isFile()) {
files.add(SecondaryFile.nonIncremental(testedMappingFile));
} else if (testMappingConfiguration != null) {
files.add(SecondaryFile.nonIncremental(testMappingConfiguration));
}
// the config files
files.add(SecondaryFile.nonIncremental(getAllConfigurationFiles()));
return files;
}
@NonNull
@Override
public Collection<File> getSecondaryFileOutputs() {
return secondaryFileOutputs;
}
可以看到,它實際上是對mapping文件額外的配置,相當于如注釋一樣,是相對于主流額外新一個流,實際開發中我們用的較少。
(7)isCacheable()
按照慣例,先看注釋:
/**
* Returns if this transform's outputs should be cached. Please read {@link
* org.gradle.api.tasks.CacheableTask} Javadoc if you would like to make your transform
* cacheable.
*/
public boolean isCacheable() {
return false;
}
如果我們的transform需要被緩存,則為true。
它被TransformTask所用到:
@CacheableTask
public class TransformTask extends StreamBasedTask implements Context {
@Override
public void execute(@NonNull TransformTask task) {
task.transform = transform;
task.consumedInputStreams = consumedInputStreams;
task.referencedInputStreams = referencedInputStreams;
task.outputStream = outputStream;
task.setVariantName(variantName);
task.recorder = recorder;
if (configActionCallback != null) {
configActionCallback.callback(transform, task);
}
//這一句,如果設置為true,則緩存輸出
task.getOutputs().cacheIf(t -> transform.isCacheable());
task.registerConsumedAndReferencedStreamInputs();
}
4.Transform編寫模板
(1)無增量編譯:
class AspectJTransform extends Transform {
final String NAME = "AjcTransform"
@Override
String getName() {
return NAME
}
@Override
Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS
}
@Override
Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT
}
@Override
boolean isIncremental() {
return false
}
@Override
void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation)
//OutputProvider管理輸出路徑,如果消費型輸入為空,你會發現OutputProvider == null
TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();
transformInvocation.inputs.each { TransformInput input ->
input.jarInputs.each { JarInput jarInput ->
//處理Jar
processJarInput(jarInput, outputProvider)
}
input.directoryInputs.each { DirectoryInput directoryInput ->
//處理源碼文件
processDirectoryInputs(directoryInput, outputProvider)
}
}
}
void processJarInput(JarInput jarInput, TransformOutputProvider outputProvider) {
File dest = outputProvider.getContentLocation(
jarInput.getFile().getAbsolutePath(),
jarInput.getContentTypes(),
jarInput.getScopes(),
Format.JAR)
//TODO do some transform
//將修改過的字節碼copy到dest,就可以實現編譯期間干預字節碼的目的了
FileUtils.copyFiley(jarInput.getFile(), dest)
}
void processDirectoryInputs(DirectoryInput directoryInput, TransformOutputProvider outputProvider) {
File dest = outputProvider.getContentLocation(directoryInput.getName(),
directoryInput.getContentTypes(), directoryInput.getScopes(),
Format.DIRECTORY)
//建立文件夾
FileUtils.forceMkdir(dest)
//TODO do some transform
//將修改過的字節碼copy到dest,就可以實現編譯期間干預字節碼的目的了
FileUtils.copyDirectory(directoryInput.getFile(), dest)
}
}
這里只是實現了簡單的拷貝,具體怎么處理可以根據需求出發
(2)帶增量編譯(推薦):
class AspectJTransform extends Transform {
final String NAME = "AjcTransform"
@Override
String getName() {
return NAME
}
@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(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation)
boolean isIncremental = transformInvocation.isIncremental()
//OutputProvider管理輸出路徑,如果消費型輸入為空,你會發現OutputProvider == null
TransformOutputProvider outputProvider = transformInvocation.getOutputProvider()
if (!isIncremental) {
//不需要增量編譯,先清除全部
outputProvider.deleteAll()
}
transformInvocation.getInputs().each { TransformInput input ->
input.jarInputs.each { JarInput jarInput ->
//處理Jar
processJarInputWithIncremental(jarInput, outputProvider, isIncremental)
}
input.directoryInputs.each { DirectoryInput directoryInput ->
//處理文件
processDirectoryInputWithIncremental(directoryInput, outputProvider, isIncremental)
}
}
}
void processJarInputWithIncremental(JarInput jarInput, TransformOutputProvider outputProvider, boolean isIncremental) {
File dest = outputProvider.getContentLocation(
jarInput.getFile().getAbsolutePath(),
jarInput.getContentTypes(),
jarInput.getScopes(),
Format.JAR)
if (isIncremental) {
//處理增量編譯
processJarInputWhenIncremental(jarInput, dest)
} else {
//不處理增量編譯
processJarInput(jarInput, dest)
}
}
void processJarInput(JarInput jarInput, File dest) {
transformJarInput(jarInput, dest)
}
void processJarInputWhenIncremental(JarInput jarInput, File dest) {
switch (jarInput.status) {
case Status.NOTCHANGED:
break
case Status.ADDED:
case Status.CHANGED:
//處理有變化的
transformJarInputWhenIncremental(jarInput.getFile(), dest, jarInput.status)
break
case Status.REMOVED:
//移除Removed
if (dest.exists()) {
FileUtils.forceDelete(dest)
}
break
}
}
void transformJarInputWhenIncremental(JarInput jarInput, File dest, Status status) {
if (status == Status.CHANGED) {
//Changed的狀態需要先刪除之前的
if (dest.exists()) {
FileUtils.forceDelete(dest)
}
}
//真正transform的地方
transformJarInput(jarInput, dest)
}
void transformJarInput(JarInput jarInput, File dest) {
//TODO do some transform
//將修改過的字節碼copy到dest,就可以實現編譯期間干預字節碼的目的了
FileUtils.copyFile(jarInput.getFile(), dest)
}
void processDirectoryInputWithIncremental(DirectoryInput directoryInput, TransformOutputProvider outputProvider, boolean isIncremental) {
File dest = outputProvider.getContentLocation(
directoryInput.getFile().getAbsolutePath(),
directoryInput.getContentTypes(),
directoryInput.getScopes(),
Format.DIRECTORY)
if (isIncremental) {
//處理增量編譯
processDirectoryInputWhenIncremental(directoryInput, dest)
} else {
processDirectoryInput(directoryInput, dest)
}
}
void processDirectoryInputWhenIncremental(DirectoryInput directoryInput, File dest) {
FileUtils.forceMkdir(dest)
String srcDirPath = directoryInput.getFile().getAbsolutePath()
String destDirPath = dest.getAbsolutePath()
Map<File, Status> fileStatusMap = directoryInput.getChangedFiles()
fileStatusMap.each { Map.Entry<File, Status> entry ->
File inputFile = entry.getKey()
Status status = entry.getValue()
String destFilePath = inputFile.getAbsolutePath().replace(srcDirPath, destDirPath)
File destFile = new File(destFilePath)
switch (status) {
case Status.NOTCHANGED:
break
case Status.REMOVED:
if (destFile.exists()) {
FileUtils.forceDelete(destFile)
}
break
case Status.ADDED:
case Status.CHANGED:
FileUtils.touch(destFile)
transformSingleFile(inputFile, destFile, srcDirPath)
break
}
}
}
void processDirectoryInput(DirectoryInput directoryInput, File dest) {
transformDirectoryInput(directoryInput, dest)
}
void transformDirectoryInput(DirectoryInput directoryInput, File dest) {
//TODO do some transform
//將修改過的字節碼copy到dest,就可以實現編譯期間干預字節碼的目的了
FileUtils.copyDirectory(directoryInput.getFile(), dest)
}
void transformSingleFile(File inputFile, File destFile, String srcDirPath) {
FileUtils.copyFile(inputFile, destFile)
}
}
5.Transform注冊和使用
定義一個插件
class AspectJWeaverPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
boolean hasApp = project.getPlugins().hasPlugin(AppPlugin.class)
if (hasApp) {
def appExtension = project.getExtensions().getByType(AppExtension.class)
appExtension.registerTransform(new AspectJTransform(), Collections.EMPTY_LIST)
}
}
}
6.Transform優化
一般就三種:
- 增量編譯
- 并發編譯
- include... exclude...縮小transform范圍
這里講一下并發編譯,簡單實現如下:
WaitableExecutor waitableExecutor = WaitableExecutor.useGlobalSharedThreadPool()
......
transformInvocation.getInputs().each { TransformInput input ->
input.jarInputs.each { JarInput jarInput ->
//多線程處理Jar
waitableExecutor.execute(new Callable<Object>() {
@Override
Object call() throws Exception {
processJarInputWithIncremental(jarInput, outputProvider, isIncremental)
return null
}
})
}
input.directoryInputs.each { DirectoryInput directoryInput ->
//多線程處理文件
waitableExecutor.execute(new Callable<Object>() {
@Override
Object call() throws Exception {
processDirectoryInputWithIncremental(directoryInput, outputProvider, isIncremental)
return null
}
})
}
}
//等待所有任務結束
waitableExecutor.waitForTasksWithQuickFail(true)
7.Transform用途
結合ASM、AspectJ、javassit等字節碼處理框架進行AOP編程,具體后面會講到