热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:Gradle之AndroidGradlePlugin主要Task分析

【Android修炼手册】Gradle篇--AndroidGradlePlugin主要Task分析



【Android 修炼手册】Gradle 篇 -- Android Gradle Plugin 主要 Task 分析



预备知识



  1. 理解 gradle 的基本开发

  2. 了解 gradle task 和 plugin 使用及开发

  3. 了解 android gradle plugin 的使用


看完本文可以达到什么程度



  1. 了解 android gradle plugin 中各个 task 作用

  2. 了解 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 打包流程

在介绍 Android Gradle Plugin Task 之前,我们先看看一个 apk 的构建流程,先放一张官方流程图:




官方介绍的流程如下:


  1. 编译器将您的源代码转换成 DEX(Dalvik Executable) 文件(其中包括 Android 设备上运行的字节码),将所有其他内容转换成已编译资源。

  2. APK 打包器将 DEX 文件和已编译资源合并成单个 APK。 不过,必须先签署 APK,才能将应用安装并部署到 Android 设备上。

  3. APK 打包器使用调试或发布密钥库签署您的 APK:

  4. 在生成最终 APK 之前,打包器会使用 zipalign 工具对应用进行优化,减少其在设备上运行时占用的内存。

那么以 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 对应实现类

我们先看看每个 task 都是做什么的,以及其对应的实现类。
先回忆一下,我们在前面 android-gradle-plugin 主要流程分析里说到过,task 的实现可以在 TaskManager 里找到,创建 task 的方法主要是两个,TaskManager.createTasksBeforeEvaluate() 和 ApplicationTaskManager.createTasksForVariantScope(),所以这些 task 的实现,也在这两个类里找就可以,下面列出了各个 task 的作用及实现类。


















































































































































































Task对应实现类作用
preBuild 空 task,只做锚点使用
preDebugBuild 空 task,只做锚点使用,与 preBuild 区别是这个 task 是 variant 的锚点
compileDebugAidlAidlCompile处理 aidl
compileDebugRenderscriptRenderscriptCompile处理 renderscript
checkDebugManifestCheckManifest检测 manifest 是否存在
generateDebugBuildConfigGenerateBuildConfig生成 BuildConfig.java
prepareLintJarPrepareLintJar拷贝 lint jar 包到指定位置
generateDebugResValuesGenerateResValues生成 resvalues,generated.xml
generateDebugResources 空 task,锚点
mergeDebugResourcesMergeResources合并资源文件
createDebugCompatibleScreenManifestsCompatibleScreensManifestmanifest 文件中生成 compatible-screens,指定屏幕适配
processDebugManifestMergeManifests合并 manifest 文件
splitsDiscoveryTaskDebugSplitsDiscovery生成 split-list.json,用于 apk 分包
processDebugResourcesProcessAndroidResourcesaapt 打包资源
generateDebugSources 空 task,锚点
javaPreCompileDebugJavaPreCompileTask生成 annotationProcessors.json 文件
compileDebugJavaWithJavacAndroidJavaCompile编译 java 文件
compileDebugNdkNdkCompile编译 ndk
compileDebugSources 空 task,锚点使用
mergeDebugShadersMergeSourceSetFolders合并 shader 文件
compileDebugShadersShaderCompile编译 shaders
generateDebugAssets 空 task,锚点
mergeDebugAssetsMergeSourceSetFolders合并 assets 文件
transformClassesWithDexBuilderForDebugDexArchiveBuilderTransformclass 打包 dex
transformDexArchiveWithExternalLibsDexMergerForDebugExternalLibsMergerTransform打包三方库的 dex,在 dex 增量的时候就不需要再 merge 了,节省时间
transformDexArchiveWithDexMergerForDebugDexMergerTransform打包最终的 dex
mergeDebugJniLibFoldersMergeSouceSetFolders合并 jni lib 文件
transformNativeLibsWithMergeJniLibsForDebugMergeJavaResourcesTransform合并 jnilibs
transformNativeLibsWithStripDebugSymbolForDebugStripDebugSymbolTransform去掉 native lib 里的 debug 符号
processDebugJavaResProcessJavaResConfigAction处理 java res
transformResourcesWithMergeJavaResForDebugMergeJavaResourcesTransform合并 java res
validateSigningDebugValidateSigningTask验证签名
packageDebugPackageApplication打包 apk
assembleDebug 空 task,锚点

三、如何去读 Task 的代码

在 gradle plugin 中的 Task 主要有三种,一种是普通的 task,一种是增量 task,一种是 transform,下面分别看下这三种 task 怎么去读。

如何读 Task 的代码



  1. 看 Task 继承的父类,一般来说,会继承 DefaultTask,IncrementalTask

  2. 看 @TaskAction 注解的方法,此方法就是这个 Task 做的事情


如何读 IncrementalTask

我们先看看下这个类,这个类表示的是增量 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 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 getChangedInputs(IncrementalTaskInputs inputs) {
Map
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 的代码,主要有四步:


  1. 首先这个 Task 要继承 IncrementalTask,

  2. 其次看 isIncremental 方法,如果返回 true,说明支持增量,返回 false 则不支持

  3. 然后看 doFullTaskAction 方法,是全量的时候执行的操作

  4. 最后看 doIncrementalTaskAction 方法,这里是增量的时候执行的操作


如何读 Transform



  1. 继承自 Transform

  2. 看其 transform 方法的实现


四、重点 Task 实现分析

上面每个 task 已经简单说明了具体做什么以及对应的实现类,下面选了几个比较重要的来分析一下其实现
为什么分析这几个呢?这几个代表了 gradle 自动生成代码,资源的处理,以及 dex 的处理,算是 apk 打包过程中比较重要的几环。
generateDebugBuildConfig
processDebugManifest
mergeDebugResources
processDebugResources
transformClassesWithDexBuilderForDebug
transformDexArchiveWithExternalLibsDexMergerForDebug
transformDexArchiveWithDexMergerForDebug

分析过程主要下面几个步骤,实现类,整体实现图,调用链路(方便以后回看代码),以及重要代码分析

4.1 generateDebugBuildConfig


4.1.1 实现类

GenerateBuildConfig

4.1.2 整体实现图


 

 

4.1.3 代码调用链路

GenerateBuildConfig.generate -> BuildConfigGenerator.generate -> JavaWriter




4.1.4 主要代码分析

在 GenerateBuildConfig 中,主要生成代码的步骤如下:


  1. 生成 BuildConfigGenerator

  2. 添加默认的属性,包括 DEBUG,APPLICATION_ID,FLAVOR,VERSION_CODE,VERSION_NAME

  3. 添加自定义属性

  4. 调用 JavaWriter 生成 BuildConfig.java 文件


// 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
flavors = getFlavorNamesWithDimensionNames();
int count = flavors.size();
if (count > 1) {
for (int i = 0; i ) {
generator.addField(
"String", "FLAVOR_" + flavors.get(i + 1), \'"\' + flavors.get(i) + \'"\');
}
}
// 内部调用 JavaWriter 生成 java 文件
generator.generate();
}



4.2 mergeDebugResources


4.2.1 实现类

MergeResources

4.2.2 整体实现图

 

 

4.2.3 调用链路






MergeResources.doFullTaskAction -> ResourceMerger.mergeData -> MergedResourceWriter.end -> QueueableAapt2.compile -> Aapt2QueuedResourceProcessor.compile -> AaptProcess.compile -> AaptV2CommandBuilder.makeCompile




4.2.4 主要代码分析

MergeResources 这个类,继承自 IncrementalTask,按照前面说的阅读增量 Task 代码的步骤,依次看三个方法的实现:isIncremental,doFullTaskAction,doIncrementalTaskAction




  • isIncremental


// 说明 Task 支持增量
protected boolean isIncremental() {
return true;
}



  • doFullTaskAction



  1. 通过 getConfiguredResourceSets() 获取 resourceSets,包括了自己的 res/ 和 依赖库的 res/ 以及 build/generated/res/rs


// MergeResources.doFullTaskAction()
List resourceSets = getConfiguredResourceSets(preprocessor);



  1. 创建 ResourceMerger


// MergeResources.doFullTaskAction()
ResourceMerger merger = new ResourceMerger(minSdk);



  1. 创建 QueueableResourceCompiler,因为 gradle3.x 以后支持了 aapt2,所以这里有两种选择 aapt 和 aapt2。其中 aapt2 有三种模式,OutOfProcessAaptV2,AaptV2Jni,QueueableAapt2,这里默认创建了 QueueableAapt2,resourceCompiler = QueueableAapt2


// MergeResources.doFullTaskAction()
// makeAapt 中会判断使用 aapt 还是 aapt2,这里以 aapt2 为例,返回的是 QueueableAapt2 对象
QueueableResourceCompiler resourceCompiler =
makeAapt(
aaptGeneration,
getBuilder(),
fileCache,
crunchPng,
variantScope,
getAaptTempDir(),
mergingLog)



  1. 将第一步获取的 resourceSet 加入 ResourceMerger 中


for (ResourceSet resourceSet : resourceSets) {
resourceSet.loadFromFiles(getILogger());
merger.addDataSet(resourceSet);
}



  1. 创建 MergedResourceWriter

  2. 调用 ResourceMerger.mergeData 合并资源


// MergeResources.doFullTaskAction()
merger.mergeData(writer, false /*doCleanUp*/);



  1. 调用 MergedResourceWriter 的 start(),addItem(),end() 方法,伪代码如下:


// DataMerger.mergeData
consumer.start()
for item in sourceSets:
// item 包括了需要处理的资源,包括 xml 和 图片资源,每一个 item 对应的文件,会创建一个 CompileResourceRequest 对象,加入到 mCompileResourceRequests 里
consumer.addItem(item)
consumer.end()



  1. 调用 QueueableAapt2 -> Aapt2QueuedResourceProcessor -> AaptProcess 处理资源


// MergedResourceWriter.end()
Future 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
job,
@Nullable ProcessOutputHandler processOutputHandler)
throws IOException {
// ...
// 使用 AaptV2CommandBuilder 生成 aapt2 命令
mWriter.write(joiner.join(AaptV2CommandBuilder.makeCompile(request)));
mWriter.flush();
// 输出命令
}

这一步调用 aapt2 命令去处理资源,处理完以后 xxx.xml.flat 格式


  • doIncrementalTaskAction
    增量任务过程和全量其实差异不大,只不过是在获取 resourceSets 的时候,使用的是修改后的文件



4.3 processDebugResources


4.3.1 实现类

ProcessAndroidResources

4.3.2 整体实现图

4.3.3 调用链路

ProcessAndroidResources.doFullTaskAction -> ProcessAndroidResources.invokeAaptForSplit -> AndroidBuilder.processResources -> QueueAapt2.link -> Aapt2QueuedResourceProcessor.link -> AaptProcess.link -> AaptV2CommandBuilder.makeLink




4.3.4 主要代码分析

ProcessAndroidResources 也是继承自 IncrementalTask,但是没有重写 isIncremental,所以不是增量的 Task,直接看 doFullTaskAction 即可


  • doFullTaskAction
    这个里面代码虽然多,但是主要的逻辑比较简单,就是调用 aapt2 link 去生成资源包。
    这里会处理 splits apk 相关的内容,关于 splits apk 具体可以查看 splits apk,简单来说,就是可以按照屏幕分辨率,abis 来生成不同的 apk,从而让特定用户的安装包变小。
    分下面几个步骤:



  1. 获取 split 数据


List 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 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;
}
}
}



  1. 调用 invokeAaptForSplit 处理资源


// 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);
}



  1. 调用 AndroidBuilder.processResources -> QueueAapt2.link -> Aapt2QueuedResourceProcessor.link -> AaptProcess.link -> AaptV2CommandBuilder.makeLink 处理资源,生成资源包以及 R.java 文件

  2. 处理其他 ApkData 资源,这里只会生成资源包而不会生成 R.java 文件

关于 aapt2 的 compile 和 link 参数,可以在 developer.android.com/studio/comm… 这里看

4.4 processDebugManifest


4.4.1 实现类

MergeManifests

4.4.2 整体实现图


 

 

4.4.3 调用链路

MergeManifests.dofFullTaskAction -> AndroidBuilder.mergeManifestsForApplication -> Invoker.merge -> ManifestMerge2.merge




4.4.4 主要代码分析

MergeManifests 也是继承了 IncrementalTask,但是没有实现 isIncremental,所以只看其 doFullTaskAction 即可。
这个 task 功能主要是合并 mainfest,包括 module 和 flavor 里的,整个过程通过 MergingReport,ManifestMerger2 和 XmlDocument 进行。
这里直接看 ManifestMerger2.merge() 的 merge 过程 。
主要有几个步骤:


  1. 获取依赖库的 manifest 信息,用 LoadedManifestInfo 标识

  2. 获取主 module 的 manifest 信息

  3. 替换主 module 的 Manifest 中定义的某些属性,替换成 gradle 中定义的属性 例如: package, version_code, version_name, min_sdk_versin 等等




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 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();
}
}




  1. 合并依赖库的 manifest


for (LoadedManifestInfo libraryDocument : loadedLibraryDocuments) {
mLogger.verbose(
"Merging library manifest " + libraryDocument.getLocation());
xmlDocumentOptional
= merge(
xmlDocumentOptional, libraryDocument, mergingReportBuilder);
if (!xmlDocumentOptional.isPresent()) {
return mergingReportBuilder.build();
}
}



  1. 处理 manifest 的 placeholders


performPlaceHolderSubstitution(loadedMainManifestInfo, xmlDocumentOptional.get(), mergingReportBuilder, severity);



  1. 之后对最终合并后的 manifest 中的一些属性重新进行一次替换,类似步骤 4

  2. 保存 manifest 到 build/intermediates/manifest/fullxxx/AndroidManifest.xml 这就生成了最终的 Manifest 文件


4.5 transformClassesWithDexBuilderForDebug


4.5.1 实现类

DexArchiveBuilderTransform

4.5.2 整体实现图



 

 

4.5.3 调用链路

DexArchiveBuilderTransform.transform -> DexArchiveBuilderTransform.convertJarToDexArchive -> DexArchiveBuilderTransform.convertToDexArchive -> DexArchiveBuilderTransform.launchProcessing -> DxDexArchiveBuilder.convert




4.5.4 主要代码分析

在 DexArchiveBuilderTransform 中,对 class 的处理分为两种方式,一种是对 目录下的 class 进行处理,一种是对 .jar 里的 class 进行处理。
为什么要分为这两种方式呢?.jar 中的 class 一般来说都是依赖库,基本上不会改变,gradle 在这里做了一个缓存,但是两种方式最终都会调用到 convertToDexArchive,可以说是殊途同归吧。


  • convertJarToDexArchive 处理 jar
    处理 .jar 时,会对 jar 包中的每一个 class 都单独打成一个 .dex 文件,之后还是放在 .jar 包中


private List 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();
}
}



  • convertToDexArchive 处理 dir 以及 jar 的后续处理
    对 dir 处理使用 convertToDexArchive
    其中会调用 launchProcessing


private static void launchProcessing(
@NonNull DexConversionParameters dexConversionParameters,
@NonNull OutputStream outStream,
@NonNull OutputStream errStream)
throws IOException, URISyntaxException {
// ...
boolean hasIncrementalInfo =
dexConversionParameters.isDirectoryBased()
&& dexConversionParameters.isIncremental;
// 判断 class 是否新增或者修改过,如果新增或者修改过,就需要处理
Predicate toProcess =
hasIncrementalInfo
? path -> {
Map
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 中,有下面几个步骤:


  1. 判断目录下的 class 是否新增或者修改过

  2. 调用 DexArchiveBuilder.build 去处理修改过的 class

  3. DexArchiveBuilder 有两个子类,D8DexArchiveBuilder 和 DxDexArchiveBuilder,分别是调用 d8 和 dx 去打 dex


4.6 transformDexArchiveWithExternalLibsDexMergerForDebug


4.6.1 实现类

ExternalLibsMergerTransform

4.6.2 整体实现图


 

4.6.3 调用链路

这一步是处理依赖库的 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 命令

这里逻辑比较简单,就不具体分析了

4.7 transformDexArchiveWithDexMergerForDebug


4.7.1 实现类

DexMergerTransform

4.7.2 整体实现图

 

 

4.7.3 调用链路

和上一步类似



// 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 命令


五、本文重点



  1. Android Gradle Plugin 中各个 Task 的作用及实现类,具体可参考文中第二节「Task 对应实现类」

  2. 如何阅读 Task 的代码





 


 


 

以上是关于Gradle之Android Gradle Plugin 主要 Task 分析的主要内容,如果未能解决你的问题,请参考以下文章

Gradle之Android Gradle Plugin 主要 Task 分析

Android Studio 基础控件飘红 处理办法

VS生成Cordova for Android应用之Gradle

Android知识要点整理(16)----Gradle 之项目结构

Android Studio之Gradle自动化构建打包

Android Gradle的基本概念梳理


推荐阅读
  • Android中将独立SO库封装进JAR包并实现SO库的加载与调用
    在Android开发中,将独立的SO库封装进JAR包并实现其加载与调用是一个常见的需求。本文详细介绍了如何将SO库嵌入到JAR包中,并确保在外部应用调用该JAR包时能够正确加载和使用这些SO库。通过这种方式,开发者可以更方便地管理和分发包含原生代码的库文件,提高开发效率和代码复用性。文章还探讨了常见的问题及其解决方案,帮助开发者避免在实际应用中遇到的坑。 ... [详细]
  • 本文深入探讨了如何利用Maven高效管理项目中的外部依赖库。通过介绍Maven的官方依赖搜索地址(),详细讲解了依赖库的添加、版本管理和冲突解决等关键操作。此外,还提供了实用的配置示例和最佳实践,帮助开发者优化项目构建流程,提高开发效率。 ... [详细]
  • 本文介绍了如何使用Flume从Linux文件系统收集日志并存储到HDFS,然后通过MapReduce清洗数据,使用Hive进行数据分析,并最终通过Sqoop将结果导出到MySQL数据库。 ... [详细]
  • JVM钩子函数的应用场景详解
    本文详细介绍了JVM钩子函数的多种应用场景,包括正常关闭、异常关闭和强制关闭。通过具体示例和代码演示,帮助读者更好地理解和应用这一机制。适合对Java编程和JVM有一定基础的开发者阅读。 ... [详细]
  • Hadoop的文件操作位于包org.apache.hadoop.fs里面,能够进行新建、删除、修改等操作。比较重要的几个类:(1)Configurati ... [详细]
  • 原文网址:https:www.cnblogs.comysoceanp7476379.html目录1、AOP什么?2、需求3、解决办法1:使用静态代理4 ... [详细]
  • 基于Net Core 3.0与Web API的前后端分离开发:Vue.js在前端的应用
    本文介绍了如何使用Net Core 3.0和Web API进行前后端分离开发,并重点探讨了Vue.js在前端的应用。后端采用MySQL数据库和EF Core框架进行数据操作,开发环境为Windows 10和Visual Studio 2019,MySQL服务器版本为8.0.16。文章详细描述了API项目的创建过程、启动步骤以及必要的插件安装,为开发者提供了一套完整的开发指南。 ... [详细]
  • Android 构建基础流程详解
    Android 构建基础流程详解 ... [详细]
  • 使用Maven JAR插件将单个或多个文件及其依赖项合并为一个可引用的JAR包
    本文介绍了如何利用Maven中的maven-assembly-plugin插件将单个或多个Java文件及其依赖项打包成一个可引用的JAR文件。首先,需要创建一个新的Maven项目,并将待打包的Java文件复制到该项目中。通过配置maven-assembly-plugin,可以实现将所有文件及其依赖项合并为一个独立的JAR包,方便在其他项目中引用和使用。此外,该方法还支持自定义装配描述符,以满足不同场景下的需求。 ... [详细]
  • 在Java Web服务开发中,Apache CXF 和 Axis2 是两个广泛使用的框架。CXF 由于其与 Spring 框架的无缝集成能力,以及更简便的部署方式,成为了许多开发者的首选。本文将详细介绍如何使用 CXF 框架进行 Web 服务的开发,包括环境搭建、服务发布和客户端调用等关键步骤,为开发者提供一个全面的实践指南。 ... [详细]
  • 在本地环境中部署了两个不同版本的 Flink 集群,分别为 1.9.1 和 1.9.2。近期在尝试启动 1.9.1 版本的 Flink 任务时,遇到了 TaskExecutor 启动失败的问题。尽管 TaskManager 日志显示正常,但任务仍无法成功启动。经过详细分析,发现该问题是由 Kafka 版本不兼容引起的。通过调整 Kafka 客户端配置并升级相关依赖,最终成功解决了这一故障。 ... [详细]
  • Presto:高效即席查询引擎的深度解析与应用
    本文深入解析了Presto这一高效的即席查询引擎,详细探讨了其架构设计及其优缺点。Presto通过内存到内存的数据处理方式,显著提升了查询性能,相比传统的MapReduce查询,不仅减少了数据传输的延迟,还提高了查询的准确性和效率。然而,Presto在大规模数据处理和容错机制方面仍存在一定的局限性。本文还介绍了Presto在实际应用中的多种场景,展示了其在大数据分析领域的强大潜力。 ... [详细]
  • 今天我开始学习Flutter,并在Android Studio 3.5.3中创建了一个新的Flutter项目。然而,在首次尝试运行时遇到了问题,Gradle任务 `assembleDebug` 执行失败,退出状态码为1。经过初步排查,发现可能是由于依赖项配置不当或Gradle版本不兼容导致的。为了解决这个问题,我计划检查项目的 `build.gradle` 文件,确保所有依赖项和插件版本都符合要求,并尝试更新Gradle版本。此外,还将验证环境变量配置是否正确,以确保开发环境的稳定性。 ... [详细]
  • Spring框架的核心组件与架构解析 ... [详细]
  • 本文深入探讨了 Git 与 SVN 的高效使用技巧,旨在帮助开发者轻松应对版本控制中的各种挑战。通过详细解析两种工具的核心功能与最佳实践,读者将能够更好地掌握版本管理的精髓,提高开发效率。 ... [详细]
author-avatar
kerra璐56_609
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有