上篇文章裏講了 android gradle plugin 的總體流程,引入插件之後生成了不少 Task,這篇文章就談談生成的這些 Task 都有什麼用處,以及一些主要 Task 的實現java
1.項目添加 android gradle plugin 依賴android
compile 'com.android.tools.build:gradle:3.0.1'
複製代碼
經過這種方式,能夠直接依賴 plugin 的源碼,讀起來比較方便git
2.官方對照源碼地址 android gradle plugin 源碼地址github
你們能夠直接 clone EasyGradle 項目,把 app/build.gradle 裏的 implementation 'com.android.tools.build:gradle:3.0.1' 註釋打開就能夠了。json
在 Gradle的基本使用 和 Android Gradle Plugin 主要流程分析 裏,咱們知道了 gradle 中 task 的重要性,以及 android gradle plugin 的主要流程,這一篇就來分析一下 android gradle plugin 中一些重要的 task 是怎麼執行的。緩存
在介紹 Android Gradle Plugin Task 以前,咱們先看看一個 apk 的構建流程,先放一張官方流程圖: bash
官方介紹的流程以下:app
那麼以 Task 的維度來看 apk 的打包,是什麼流程呢?咱們先執行下面的命令,看一下打包一個 apk 須要哪些 taskide
首先咱們看一下 打包一個 apk 須要哪些 task。 在項目根目錄下執行命令工具
./gradlew android-gradle-plugin-source:assembleDebug --console=plain
複製代碼
看一下輸出結果
:android-gradle-plugin-source:preBuild UP-TO-DATE
:android-gradle-plugin-source:preDebugBuild
:android-gradle-plugin-source:compileDebugAidl
:android-gradle-plugin-source:compileDebugRenderscript
:android-gradle-plugin-source:checkDebugManifest
:android-gradle-plugin-source:generateDebugBuildConfig
:android-gradle-plugin-source:prepareLintJar UP-TO-DATE
:android-gradle-plugin-source:generateDebugResValues
:android-gradle-plugin-source:generateDebugResources
:android-gradle-plugin-source:mergeDebugResources
:android-gradle-plugin-source:createDebugCompatibleScreenManifests
:android-gradle-plugin-source:processDebugManifest
:android-gradle-plugin-source:splitsDiscoveryTaskDebug
:android-gradle-plugin-source:processDebugResources
:android-gradle-plugin-source:generateDebugSources
:android-gradle-plugin-source:javaPreCompileDebug
:android-gradle-plugin-source:compileDebugJavaWithJavac
:android-gradle-plugin-source:compileDebugNdk NO-SOURCE
:android-gradle-plugin-source:compileDebugSources
:android-gradle-plugin-source:mergeDebugShaders
:android-gradle-plugin-source:compileDebugShaders
:android-gradle-plugin-source:generateDebugAssets
:android-gradle-plugin-source:mergeDebugAssets
:android-gradle-plugin-source:transformClassesWithDexBuilderForDebug
:android-gradle-plugin-source:transformDexArchiveWithExternalLibsDexMergerForDebug
:android-gradle-plugin-source:transformDexArchiveWithDexMergerForDebug
:android-gradle-plugin-source:mergeDebugJniLibFolders
:android-gradle-plugin-source:transformNativeLibsWithMergeJniLibsForDebug
:android-gradle-plugin-source:transformNativeLibsWithStripDebugSymbolForDebug
:android-gradle-plugin-source:processDebugJavaRes NO-SOURCE
:android-gradle-plugin-source:transformResourcesWithMergeJavaResForDebug
:android-gradle-plugin-source:validateSigningDebug
:android-gradle-plugin-source:packageDebug
:android-gradle-plugin-source:assembleDebug
複製代碼
上面就是打包一個 apk 須要的 task
咱們先看看每一個 task 都是作什麼的,以及其對應的實現類。
先回憶一下,咱們在前面 android-gradle-plugin 主要流程分析裏說到過,task 的實現能夠在 TaskManager 裏找到,建立 task 的方法主要是兩個,TaskManager.createTasksBeforeEvaluate() 和 ApplicationTaskManager.createTasksForVariantScope(),因此這些 task 的實現,也在這兩個類裏找就能夠,下面列出了各個 task 的做用及實現類。
Task | 對應實現類 | 做用 |
---|---|---|
preBuild | 空 task,只作錨點使用 | |
preDebugBuild | 空 task,只作錨點使用,與 preBuild 區別是這個 task 是 variant 的錨點 | |
compileDebugAidl | AidlCompile | 處理 aidl |
compileDebugRenderscript | RenderscriptCompile | 處理 renderscript |
checkDebugManifest | CheckManifest | 檢測 manifest 是否存在 |
generateDebugBuildConfig | GenerateBuildConfig | 生成 BuildConfig.java |
prepareLintJar | PrepareLintJar | 拷貝 lint jar 包到指定位置 |
generateDebugResValues | GenerateResValues | 生成 resvalues,generated.xml |
generateDebugResources | 空 task,錨點 | |
mergeDebugResources | MergeResources | 合併資源文件 |
createDebugCompatibleScreenManifests | CompatibleScreensManifest | manifest 文件中生成 compatible-screens,指定屏幕適配 |
processDebugManifest | MergeManifests | 合併 manifest 文件 |
splitsDiscoveryTaskDebug | SplitsDiscovery | 生成 split-list.json,用於 apk 分包 |
processDebugResources | ProcessAndroidResources | aapt 打包資源 |
generateDebugSources | 空 task,錨點 | |
javaPreCompileDebug | JavaPreCompileTask | 生成 annotationProcessors.json 文件 |
compileDebugJavaWithJavac | AndroidJavaCompile | 編譯 java 文件 |
compileDebugNdk | NdkCompile | 編譯 ndk |
compileDebugSources | 空 task,錨點使用 | |
mergeDebugShaders | MergeSourceSetFolders | 合併 shader 文件 |
compileDebugShaders | ShaderCompile | 編譯 shaders |
generateDebugAssets | 空 task,錨點 | |
mergeDebugAssets | MergeSourceSetFolders | 合併 assets 文件 |
transformClassesWithDexBuilderForDebug | DexArchiveBuilderTransform | class 打包 dex |
transformDexArchiveWithExternalLibsDexMergerForDebug | ExternalLibsMergerTransform | 打包三方庫的 dex,在 dex 增量的時候就不須要再 merge 了,節省時間 |
transformDexArchiveWithDexMergerForDebug | DexMergerTransform | 打包最終的 dex |
mergeDebugJniLibFolders | MergeSouceSetFolders | 合併 jni lib 文件 |
transformNativeLibsWithMergeJniLibsForDebug | MergeJavaResourcesTransform | 合併 jnilibs |
transformNativeLibsWithStripDebugSymbolForDebug | StripDebugSymbolTransform | 去掉 native lib 裏的 debug 符號 |
processDebugJavaRes | ProcessJavaResConfigAction | 處理 java res |
transformResourcesWithMergeJavaResForDebug | MergeJavaResourcesTransform | 合併 java res |
validateSigningDebug | ValidateSigningTask | 驗證簽名 |
packageDebug | PackageApplication | 打包 apk |
assembleDebug | 空 task,錨點 |
在 gradle plugin 中的 Task 主要有三種,一種是普通的 task,一種是增量 task,一種是 transform,下面分別看下這三種 task 怎麼去讀。
咱們先看看下這個類,這個類表示的是增量 Task,什麼是增量呢?是相對於 全量來講的,全量咱們能夠理解爲調用 clean 之後第一次編譯的過程,這個就是全量編譯,以後修改了代碼或者資源文件,再次編譯,就是增量編譯。
其中比較重要的幾個方法以下:
public abstract class IncrementalTask extends BaseTask {
// ...
@Internal
protected boolean isIncremental() {
// 是否須要增量,默認是 false
return false;
}
// 須要子類實現,全量的時候執行的任務
protected abstract void doFullTaskAction() throws Exception;
// 增量的時候執行的任務,默認是什麼都不執行,參數是增量的時候修改過的文件
protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) throws Exception {
}
@TaskAction
void taskAction(IncrementalTaskInputs inputs) throws Exception {
// 判斷是不是增量
if(this.isIncremental() && inputs.isIncremental()) {
this.doIncrementalTaskAction(this.getChangedInputs(inputs));
} else {
this.getProject().getLogger().info("Unable do incremental execution: full task run");
this.doFullTaskAction();
}
}
// 獲取修改的文件
private Map<File, FileStatus> getChangedInputs(IncrementalTaskInputs inputs) {
Map<File, FileStatus> changedInputs = Maps.newHashMap();
inputs.outOfDate((change) -> {
FileStatus status = change.isAdded()?FileStatus.NEW:FileStatus.CHANGED;
changedInputs.put(change.getFile(), status);
});
inputs.removed((change) -> {
FileStatus var10000 = (FileStatus)changedInputs.put(change.getFile(), FileStatus.REMOVED);
});
return changedInputs;
}
}
複製代碼
簡單介紹了 IncrementalTask 以後,咱們這裏強調一下,如何去讀一個 增量 Task 的代碼,主要有四步:
上面每一個 task 已經簡單說明了具體作什麼以及對應的實現類,下面選了幾個比較重要的來分析一下其實現
爲何分析這幾個呢?這幾個表明了 gradle 自動生成代碼,資源的處理,以及 dex 的處理,算是 apk 打包過程當中比較重要的幾環。
generateDebugBuildConfig
processDebugManifest
mergeDebugResources
processDebugResources
transformClassesWithDexBuilderForDebug
transformDexArchiveWithExternalLibsDexMergerForDebug
transformDexArchiveWithDexMergerForDebug
分析過程主要下面幾個步驟,實現類,總體實現圖,調用鏈路(方便之後回看代碼),以及重要代碼分析
GenerateBuildConfig
GenerateBuildConfig.generate -> BuildConfigGenerator.generate -> JavaWriter
複製代碼
在 GenerateBuildConfig 中,主要生成代碼的步驟以下:
// GenerateBuildConfig.generate()
@TaskAction
void generate() throws IOException {
// ...
BuildConfigGenerator generator = new BuildConfigGenerator(
getSourceOutputDir(),
getBuildConfigPackageName());
// 添加默認的屬性,包括 DEBUG,APPLICATION_ID,FLAVOR,VERSION_CODE,VERSION_NAME
generator
.addField(
"boolean",
"DEBUG",
isDebuggable() ? "Boolean.parseBoolean(\"true\")" : "false")
.addField("String", "APPLICATION_ID", '"' + appPackageName.get() + '"')
.addField("String", "BUILD_TYPE", '"' + getBuildTypeName() + '"')
.addField("String", "FLAVOR", '"' + getFlavorName() + '"')
.addField("int", "VERSION_CODE", Integer.toString(getVersionCode()))
.addField(
"String", "VERSION_NAME", '"' + Strings.nullToEmpty(getVersionName()) + '"')
.addItems(getItems()); // 添加自定義屬性
List<String> flavors = getFlavorNamesWithDimensionNames();
int count = flavors.size();
if (count > 1) {
for (int i = 0; i < count; i += 2) {
generator.addField(
"String", "FLAVOR_" + flavors.get(i + 1), '"' + flavors.get(i) + '"');
}
}
// 內部調用 JavaWriter 生成 java 文件
generator.generate();
}
複製代碼
MergeResources
MergeResources.doFullTaskAction -> ResourceMerger.mergeData -> MergedResourceWriter.end -> QueueableAapt2.compile -> Aapt2QueuedResourceProcessor.compile -> AaptProcess.compile -> AaptV2CommandBuilder.makeCompile
複製代碼
MergeResources 這個類,繼承自 IncrementalTask,按照前面說的閱讀增量 Task 代碼的步驟,依次看三個方法的實現:isIncremental,doFullTaskAction,doIncrementalTaskAction
// 說明 Task 支持增量
protected boolean isIncremental() {
return true;
}
複製代碼
// MergeResources.doFullTaskAction()
List<ResourceSet> resourceSets = getConfiguredResourceSets(preprocessor);
複製代碼
// MergeResources.doFullTaskAction()
ResourceMerger merger = new ResourceMerger(minSdk);
複製代碼
// MergeResources.doFullTaskAction()
// makeAapt 中會判斷使用 aapt 仍是 aapt2,這裏以 aapt2 爲例,返回的是 QueueableAapt2 對象
QueueableResourceCompiler resourceCompiler =
makeAapt(
aaptGeneration,
getBuilder(),
fileCache,
crunchPng,
variantScope,
getAaptTempDir(),
mergingLog)
複製代碼
for (ResourceSet resourceSet : resourceSets) {
resourceSet.loadFromFiles(getILogger());
merger.addDataSet(resourceSet);
}
複製代碼
// MergeResources.doFullTaskAction()
merger.mergeData(writer, false /*doCleanUp*/);
複製代碼
// DataMerger.mergeData
consumer.start()
for item in sourceSets:
// item 包括了須要處理的資源,包括 xml 和 圖片資源,每個 item 對應的文件,會建立一個 CompileResourceRequest 對象,加入到 mCompileResourceRequests 裏
consumer.addItem(item)
consumer.end()
複製代碼
// MergedResourceWriter.end()
Future<File> result = this.mResourceCompiler.compile(new CompileResourceRequest(fileToCompile, request.getOutput(), request.getFolderName(), this.pseudoLocalesEnabled, this.crunchPng));
// AaptProcess.compile
public void compile(
@NonNull CompileResourceRequest request,
@NonNull Job<AaptProcess> job,
@Nullable ProcessOutputHandler processOutputHandler)
throws IOException {
// ...
// 使用 AaptV2CommandBuilder 生成 aapt2 命令
mWriter.write(joiner.join(AaptV2CommandBuilder.makeCompile(request)));
mWriter.flush(); // 輸出命令
}
複製代碼
這一步調用 aapt2 命令去處理資源,處理完之後 xxx.xml.flat 格式
ProcessAndroidResources
ProcessAndroidResources.doFullTaskAction -> ProcessAndroidResources.invokeAaptForSplit -> AndroidBuilder.processResources -> QueueAapt2.link -> Aapt2QueuedResourceProcessor.link -> AaptProcess.link -> AaptV2CommandBuilder.makeLink
複製代碼
ProcessAndroidResources 也是繼承自 IncrementalTask,可是沒有重寫 isIncremental,因此不是增量的 Task,直接看 doFullTaskAction 便可
List<ApkData> splitsToGenerate =
getApksToGenerate(outputScope, supportedAbis, buildTargetAbi, buildTargetDensity);
複製代碼
返回的是一個 ApkData 列表,ApkData 有三個子類,分別是 Main,Universal,FullSplit
咱們配置 以下:
android {
splits {
// Configures multiple APKs based on screen density.
density {
// Configures multiple APKs based on screen density.
enable true
// Specifies a list of screen densities Gradle should not create multiple APKs for.
exclude "ldpi", "xxhdpi", "xxxhdpi"
// Specifies a list of compatible screen size settings for the manifest.
compatibleScreens 'small', 'normal', 'large', 'xlarge'
}
}
}
複製代碼
這裏的 ApkData 會返回一個 Universal 和多個 FullSplit,Universal 表明的是主 apk,FullSplit 就是根據屏幕密度拆分的 apk。
若是咱們沒有配置 splits apk,那麼這裏只會返回一個 Main 的實例,標識完整的 apk。
2. 先處理 main 和 不依賴 density 的 ApkData 資源
// ProcessAndroidResources.doFullTaskAction
List<ApkData> apkDataList = new ArrayList<>(splitsToGenerate);
for (ApkData apkData : splitsToGenerate) {
if (apkData.requiresAapt()) {
// 這裏只處理 main 和不依賴 density 的資源
boolean codeGen =
(apkData.getType() == OutputFile.OutputType.MAIN
|| apkData.getFilter(OutputFile.FilterType.DENSITY) == null);
if (codeGen) {
apkDataList.remove(apkData);
invokeAaptForSplit(
manifestsOutputs,
libraryInfoList,
packageIdFileSet,
splitList,
featureResourcePackages,
apkData,
codeGen,
aapt);
break;
}
}
}
複製代碼
// ProcessAndroidResources.invokeAaptForSplit
void invokeAaptForSplit(...) {
// ...
String packageForR = null;
File srcOut = null;
File symbolOutputDir = null;
File proguardOutputFile = null;
File mainDexListProguardOutputFile = null;
// 若是傳了 generateCode 參數,會生成 R.java
if (generateCode) {
packageForR = originalApplicationId;
// we have to clean the source folder output in case the package name changed.
srcOut = getSourceOutputDir();
if (srcOut != null) {
FileUtils.cleanOutputDir(srcOut);
}
symbolOutputDir = textSymbolOutputDir.get();
proguardOutputFile = getProguardOutputFile();
mainDexListProguardOutputFile = getMainDexListProguardOutputFile();
}
// ...
getBuilder().processResources(aapt, config);
}
複製代碼
關於 aapt2 的 compile 和 link 參數,能夠在 developer.android.com/studio/comm… 這裏看
MergeManifests
MergeManifests.dofFullTaskAction -> AndroidBuilder.mergeManifestsForApplication -> Invoker.merge -> ManifestMerge2.merge
複製代碼
MergeManifests 也是繼承了 IncrementalTask,可是沒有實現 isIncremental,因此只看其 doFullTaskAction 便可。
這個 task 功能主要是合併 mainfest,包括 module 和 flavor 裏的,整個過程經過 MergingReport,ManifestMerger2 和 XmlDocument 進行。
這裏直接看 ManifestMerger2.merge() 的 merge 過程 。 主要有幾個步驟:
performSystemPropertiesInjection(mergingReportBuilder, xmlDocumentOptional.get());
// ManifestMerger2.performSystemPropertiesInjection
protected void performSystemPropertiesInjection(
@NonNull MergingReport.Builder mergingReport,
@NonNull XmlDocument xmlDocument) {
for (ManifestSystemProperty manifestSystemProperty : ManifestSystemProperty.values()) {
String propertyOverride = mSystemPropertyResolver.getValue(manifestSystemProperty);
if (propertyOverride != null) {
manifestSystemProperty.addTo(
mergingReport.getActionRecorder(), xmlDocument, propertyOverride);
}
}
}
複製代碼
for (File inputFile : mFlavorsAndBuildTypeFiles) {
LoadedManifestInfo overlayDocument = load(
new ManifestInfo(null, inputFile, XmlDocument.Type.OVERLAY,
Optional.of(mainPackageAttribute.get().getValue())),
selectors,
mergingReportBuilder);
// 檢查 package 定義
Optional<XmlAttribute> packageAttribute =
overlayDocument.getXmlDocument().getPackage();
if (loadedMainManifestInfo.getOriginalPackageName().isPresent() &&
packageAttribute.isPresent()
&& !loadedMainManifestInfo.getOriginalPackageName().get().equals(
packageAttribute.get().getValue())) {
// 若是 package 定義重複的話,會輸出下面信息,咱們平時應該或多或少見過相似的錯誤
String message = mMergeType == MergeType.APPLICATION
? String.format(
"Overlay manifest:package attribute declared at %1$s value=(%2$s)\n"
+ "\thas a different value=(%3$s) "
+ "declared in main manifest at %4$s\n"
+ "\tSuggestion: remove the overlay declaration at %5$s "
+ "\tand place it in the build.gradle:\n"
+ "\t\tflavorName {\n"
+ "\t\t\tapplicationId = \"%2$s\"\n"
+ "\t\t}",
packageAttribute.get().printPosition(),
packageAttribute.get().getValue(),
mainPackageAttribute.get().getValue(),
mainPackageAttribute.get().printPosition(),
packageAttribute.get().getSourceFile().print(true))
: String.format(
"Overlay manifest:package attribute declared at %1$s value=(%2$s)\n"
+ "\thas a different value=(%3$s) "
+ "declared in main manifest at %4$s",
packageAttribute.get().printPosition(),
packageAttribute.get().getValue(),
mainPackageAttribute.get().getValue(),
mainPackageAttribute.get().printPosition());
// ...
return mergingReportBuilder.build();
}
}
複製代碼
for (LoadedManifestInfo libraryDocument : loadedLibraryDocuments) {
mLogger.verbose("Merging library manifest " + libraryDocument.getLocation());
xmlDocumentOptional = merge(
xmlDocumentOptional, libraryDocument, mergingReportBuilder);
if (!xmlDocumentOptional.isPresent()) {
return mergingReportBuilder.build();
}
}
複製代碼
performPlaceHolderSubstitution(loadedMainManifestInfo, xmlDocumentOptional.get(), mergingReportBuilder, severity);
複製代碼
DexArchiveBuilderTransform
DexArchiveBuilderTransform.transform -> DexArchiveBuilderTransform.convertJarToDexArchive -> DexArchiveBuilderTransform.convertToDexArchive -> DexArchiveBuilderTransform.launchProcessing -> DxDexArchiveBuilder.convert
複製代碼
在 DexArchiveBuilderTransform 中,對 class 的處理分爲兩種方式,一種是對 目錄下的 class 進行處理,一種是對 .jar 裏的 class 進行處理。
爲何要分爲這兩種方式呢?.jar 中的 class 通常來講都是依賴庫,基本上不會改變,gradle 在這裏作了一個緩存,可是兩種方式最終都會調用到 convertToDexArchive,能夠說是異曲同工吧。
private List<File> convertJarToDexArchive(
@NonNull Context context,
@NonNull JarInput toConvert,
@NonNull TransformOutputProvider transformOutputProvider)
throws Exception {
File cachedVersion = cacheHandler.getCachedVersionIfPresent(toConvert);
if (cachedVersion == null) {
// 若是沒有緩存,調用 convertToDexArchive 去生成 dex
return convertToDexArchive(context, toConvert, transformOutputProvider, false);
} else {
// 若是有緩存,直接使用緩存的 jar
File outputFile = getPreDexJar(transformOutputProvider, toConvert, null);
Files.copy(
cachedVersion.toPath(),
outputFile.toPath(),
StandardCopyOption.REPLACE_EXISTING);
// no need to try to cache an already cached version.
return ImmutableList.of();
}
}
複製代碼
private static void launchProcessing(
@NonNull DexConversionParameters dexConversionParameters,
@NonNull OutputStream outStream,
@NonNull OutputStream errStream)
throws IOException, URISyntaxException {
// ...
boolean hasIncrementalInfo =
dexConversionParameters.isDirectoryBased() && dexConversionParameters.isIncremental;
// 判斷 class 是否新增或者修改過,若是新增或者修改過,就須要處理
Predicate<String> toProcess =
hasIncrementalInfo
? path -> {
Map<File, Status> changedFiles =
((DirectoryInput) dexConversionParameters.input)
.getChangedFiles();
File resolved = inputPath.resolve(path).toFile();
Status status = changedFiles.get(resolved);
return status == Status.ADDED || status == Status.CHANGED;
}
: path -> true;
bucketFilter = bucketFilter.and(toProcess);
try (ClassFileInput input = ClassFileInputs.fromPath(inputPath)) {
// 內部調用 dx 或者 d8 去打 dex
dexArchiveBuilder.convert(
input.entries(bucketFilter),
Paths.get(new URI(dexConversionParameters.output)),
dexConversionParameters.isDirectoryBased());
} catch (DexArchiveBuilderException ex) {
throw new DexArchiveBuilderException("Failed to process " + inputPath.toString(), ex);
}
}
複製代碼
在 launchProcessing 中,有下面幾個步驟:
ExternalLibsMergerTransform
這一步是處理依賴庫的 dex,把上一步生成的依賴庫的 dex merge 成一個 dex
// dx
ExternalLibsMergerTransform.transform -> DexMergerTransformCallable.call -> DxDexArchiveMerger.mergeDexArchives -> DxDexArchiveMerger.mergeMonoDex -> DexArchiveMergerCallable.call -> DexMerger.merge
複製代碼
// d8
ExternalLibsMergerTransform.transform -> DexMergerTransformCallable.call -> D8DexArchiveMerger.mergeDexArchives -> 調用 D8 命令
複製代碼
這裏邏輯比較簡單,就不具體分析了
DexMergerTransform
和上一步相似
// dx
DexMergerTransform.transform -> DexMergerTransform.handleLegacyAndMonoDex -> DexMergerTransformCallable.call -> DxDexArchiveMerger.mergeDexArchives -> DxDexArchiveMerger.mergeMonoDex -> DexArchiveMergerCallable.call -> DexMerger.merge
複製代碼
// d8
DexMergerTransform.transform -> DexMergerTransform.handleLegacyAndMonoDex -> DexMergerTransformCallable.call -> D8DexArchiveMerger.mergeDexArchives -> 調用 D8 命令
複製代碼