Soinice 2019-10-28
【Android 修炼手册】Gradle 篇 -- Android Gradle Plugin 主要 Task 分析
1.项目添加 android gradle plugin 依赖
compile ‘com.android.tools.build:gradle:3.0.1‘
通过这种方式,可以直接依赖 plugin 的源码,读起来比较方便
2.官方对照源码地址 android gradle plugin 源码地址
大家可以直接 clone EasyGradle 项目,把 app/build.gradle 里的 implementation ‘com.android.tools.build:gradle:3.0.1‘ 注释打开就可以了。
在 Gradle的基本使用 和 Android Gradle Plugin 主要流程分析 里,我们知道了 gradle 中 task 的重要性,以及 android gradle plugin 的主要流程,这一篇就来分析一下 android gradle plugin 中一些重要的 task 是怎么执行的。
在介绍 Android Gradle Plugin Task 之前,我们先看看一个 apk 的构建流程,先放一张官方流程图:

官方介绍的流程如下:
那么以 Task 的维度来看 apk 的打包,是什么流程呢?我们先执行下面的命令,看一下打包一个 apk 需要哪些 task
首先我们看一下 打包一个 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);
}
}
}4.合并 flavor,buildType 中的 manifest
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 命令