AGP资源链接之LinkApplicationAndroidResourcesTask流程分析

查找实现类

AGP link资源的过程所执行task的名称叫processDebugResource,我们需要根据task名称找到具体的实现类,Gradle提供了一个很实用的命令,可根据任务名查询实现类。

./gradlew help –task

查找processDebugResources:

1
2
3
4
5
6
7
8
9
10
11
$ ./gradlew help --task processDebugResources
> Task :help
Detailed task information for processDebugResources
Path
:app:processDebugResources
Type
LinkApplicationAndroidResourcesTask (com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask)
Description
-
Group
-

从终端的输出信息我们可以发现具体的实现类为com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask

流程分析

本次分析的AGP源码版本7.2.2

LinkApplicationAndroidResourcesTask虽然也继承至IncrementalTask,但实际上该任务每次都会处理合并任务的全量结果集。虽然说LinkApplicationAndroidResourcesTask可以解析合并后资源的变化,但是因为link流程限制的原因:没有中间状态或缓存则无从处理变化的资源文件。
因为LinkApplicationAndroidResourcesTask每次都是全量编译,我们直接看doFullTaskAction方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
fun doFullTaskAction(inputStableIdsFile: File?) {
workerExecutor.noIsolation().submit(TaskAction::class.java) { parameters ->
//配置了很多参数
parameters.initializeFromAndroidVariantTask(this)

parameters.mainDexListProguardOutputFile.set(mainDexListProguardOutputFile)
parameters.outputStableIdsFile.set(stableIdsOutputFileProperty)
parameters.proguardOutputFile.set(proguardOutputFile)
parameters.rClassOutputJar.set(rClassOutputJar)
parameters.resPackageOutputDirectory.set(resPackageOutputFolder)
parameters.sourceOutputDirectory.set(sourceOutputDirProperty)
parameters.symbolsWithPackageNameOutputFile.set(symbolsWithPackageNameOutputFile)
parameters.textSymbolOutputFile.set(textSymbolOutputFileProperty)

parameters.androidJarInput.set(androidJarInput)
parameters.aapt2.set(aapt2)
parameters.symbolTableBuildService.set(symbolTableBuildService)

parameters.aaptOptions.set(AaptOptions(noCompress.orNull, aaptAdditionalParameters.orNull))
parameters.applicationId.set(applicationId)
parameters.buildTargetDensity.set(buildTargetDensity)
parameters.canHaveSplits.set(canHaveSplits)
parameters.compiledDependenciesResources.from(compiledDependenciesResources)
parameters.dependencies.from(dependenciesFileCollection)
parameters.featureResourcePackages.from(featureResourcePackages)
parameters.imports.from(sharedLibraryDependencies)
parameters.incrementalDirectory.set(incrementalFolder)
parameters.inputResourcesDirectory.set(inputResourcesDir)
parameters.inputStableIdsFile.set(inputStableIdsFile)
parameters.library.set(isLibrary)
parameters.localResourcesFile.set(localResourcesFile)
parameters.manifestFiles.set(if (aaptFriendlyManifestFiles.isPresent) aaptFriendlyManifestFiles else manifestFiles)
parameters.manifestMergeBlameFile.set(manifestMergeBlameFile)
parameters.mergeBlameDirectory.set(mergeBlameLogFolder)
parameters.namespace.set(namespace)
parameters.namespaced.set(isNamespaced)
parameters.packageId.set(resOffset)
parameters.resourceConfigs.set(resourceConfigs)
parameters.sharedLibraryDependencies.from(sharedLibraryDependencies)
parameters.sourceSetMaps.from(sourceSetMaps)
parameters.useConditionalKeepRules.set(useConditionalKeepRules)
parameters.useFinalIds.set(useFinalIds)
parameters.useMinimalKeepRules.set(useMinimalKeepRules)
parameters.useStableIds.set(useStableIds)
parameters.variantName.set(variantName)
parameters.variantOutputs.set(variantOutputs.get().map { it.toSerializedForm() })
parameters.variantType.set(type)
}
}

这里使用到AGP自己内部的workerExecutor并行执行,触发TaskActionrun方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
abstract class TaskAction : ProfileAwareWorkAction<TaskWorkActionParameters>() {

@get:Inject
abstract val workerExecutor: WorkerExecutor

override fun run() {
//合并后的Manifest工件
val manifestBuiltArtifacts = BuiltArtifactsLoaderImpl().load(parameters.manifestFiles)
?: throw RuntimeException("Cannot load processed manifest files, please file a bug.")
// 'Incremental' runs should only preserve the stable IDs file.
FileUtils.deleteDirectoryContents(parameters.resPackageOutputDirectory.get().asFile)

val variantOutputsList: List<VariantOutputImpl.SerializedForm> = parameters.variantOutputs.get()
val mainOutput = chooseOutput(variantOutputsList)

invokeAaptForSplit(
mainOutput,
manifestBuiltArtifacts.getBuiltArtifact(mainOutput.variantOutputConfiguration)
?: throw RuntimeException("Cannot find built manifest for $mainOutput"),
true,
parameters.aapt2.get().getLeasingAapt2(),
parameters.inputStableIdsFile.orNull?.asFile, parameters
)
//...
}
//...
}

aapt2 link的过程,指定构建的Android清单文件的路径是一个必须的配置,因为清单文件中包含应用的基本信息(如软件包名称和应用ID)。
内部调用invokeAaptForSplit方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
private fun invokeAaptForSplit(
variantOutput: VariantOutputImpl.SerializedForm,
manifestOutput: BuiltArtifactImpl,
generateRClass: Boolean,
aapt2: Aapt2,
stableIdsInputFile: File?,
parameters: TaskWorkActionParameters) {

//...
val resOutBaseNameFile =
getOutputBaseNameFile(variantOutput,
parameters.resPackageOutputDirectory.get().asFile
)//link过程中的输出文件路径build/intermediates/processed_res/debug/out/resources-debug.ap_
val manifestFile = manifestOutput.outputFile
//...
try {

// If the new resources flag is enabled and if we are dealing with a library process
// resources through the new parsers
run {
//构建configBuilder
val configBuilder = AaptPackageConfig.Builder()
.setManifestFile(File(manifestFile))//合并后的AndroidManifest的文件路径
.setOptions(parameters.aaptOptions.get())
.setCustomPackageForR(packageForR)
.setSymbolOutputDir(symbolOutputDir)
.setSourceOutputDir(srcOut)
.setResourceOutputApk(resOutBaseNameFile)//设置link过程的输出文件路径
.setProguardOutputFile(proguardOutputFile)
.setMainDexListProguardOutputFile(mainDexListProguardOutputFile)
.setVariantType(parameters.variantType.get())
.setResourceConfigs(parameters.resourceConfigs.get())
.setPreferredDensity(preferredDensity)
.setPackageId(parameters.packageId.orNull)
.setAllowReservedPackageId(
parameters.packageId.isPresent && parameters.packageId.get() < FeatureSetMetadata.BASE_ID
)
.setDependentFeatures(featurePackagesBuilder.build())
.setImports(parameters.imports.files)
.setIntermediateDir(parameters.incrementalDirectory.get().asFile)
.setAndroidJarPath(parameters.androidJarInput.get()
.getAndroidJar()
.get().absolutePath)//平台的android.jar的路径
.setUseConditionalKeepRules(parameters.useConditionalKeepRules.get())
.setUseMinimalKeepRules(parameters.useMinimalKeepRules.get())
.setUseFinalIds(parameters.useFinalIds.get())
.setEmitStableIdsFile(parameters.outputStableIdsFile.orNull?.asFile)
.setConsumeStableIdsFile(stableIdsInputFile)
.setLocalSymbolTableFile(parameters.localResourcesFile.orNull?.asFile)
.setMergeBlameDirectory(parameters.mergeBlameDirectory.get().asFile)
.setManifestMergeBlameFile(parameters.manifestMergeBlameFile.orNull?.asFile)
.apply {
val compiledDependencyResourceFiles =
parameters.compiledDependenciesResources.files
// In the event of running process[variant]AndroidTestResources
// on a module that depends on a module with no precompiled resources,
// we must avoid passing the compiled resource directory to AAPT link.
if (compiledDependencyResourceFiles.all(File::exists)) {
//添加依赖库中的编译文件
addResourceDirectories(
compiledDependencyResourceFiles.reversed().toImmutableList())
}
}

if (parameters.namespaced.get()) {
configBuilder.setStaticLibraryDependencies(ImmutableList.copyOf(parameters.dependencies.files))
} else {
if (generateRClass) {
//生生成R.txt文件
configBuilder.setLibrarySymbolTableFiles(parameters.dependencies.files)
}
//添加资源编译任务的产物,build/intermediates/merged_res/debug/目录的文件
configBuilder.addResourceDir(checkNotNull(parameters.inputResourcesDirectory.orNull?.asFile))
}

val logger = Logging.getLogger(LinkApplicationAndroidResourcesTask::class.java)

configBuilder.setIdentifiedSourceSetMap(
mergeIdentifiedSourceSetFiles(
parameters.sourceSetMaps.files.filterNotNull())
)
//触发link动作
processResources(
aapt = aapt2,
aaptConfig = configBuilder.build(),
rJar = if (generateRClass) parameters.rClassOutputJar.orNull?.asFile else null,
logger = logger,
errorFormatMode = parameters.aapt2.get().getErrorFormatMode(),
symbolTableLoader = parameters.symbolTableBuildService.get()::loadClasspath,
)

if (LOG.isInfoEnabled) {
LOG.info("Aapt output file {}", resOutBaseNameFile.absolutePath)
}
}

if (generateRClass
&& (parameters.library.get() || !parameters.dependencies.files.isEmpty())
&& parameters.symbolsWithPackageNameOutputFile.isPresent
) {
SymbolIo.writeSymbolListWithPackageName(
parameters.textSymbolOutputFile.orNull?.asFile!!.toPath(),
packageForR,
parameters.symbolsWithPackageNameOutputFile.get().asFile.toPath()
)
}
appendOutput(
parameters.applicationId.get().orEmpty(),
parameters.variantName.get(),
manifestOutput.newOutput(
resOutBaseNameFile.toPath()
),
parameters.resPackageOutputDirectory.get().asFile
)
} catch (e: ProcessException) {
throw BuildException(
"Failed to process resources, see aapt output above for details.", e
)
}
}

invokeAaptForSplit这个方法很长,省去很多其它不那么重要的代码方便阅读。通过建造者构建一个configBuilder对象,可以看到有很多配置。其中有一些关键的配置需要说明一下:
processDebugResource任务会输出一个resources-debug.ap_压缩文件,setResourceOutputApk就是在配置压缩文件的路径——build/intermediates/processed_res/debug/out/resources-debug.ap_
setAndroidJarPath是在设置平台的android.jar路径,link的过程会使用android.jar 中包含的API信息来验证你的代码。
addResourceDirectories使用设置远程依赖库中的*.flat文件,configBuilder.addResourceDir设置项目build/intermediates/merged_res/debug/目录下的*.flat文件。
配置这些数据都是为后面的link作准备,直接进入processResources方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
fun processResources(
aapt: Aapt2,
aaptConfig: AaptPackageConfig,
rJar: File?,
logger: Logger,
errorFormatMode: SyncOptions.ErrorFormatMode,
symbolTableLoader: (Iterable<File>) -> List<SymbolTable> = { SymbolIo().loadDependenciesSymbolTables(it) }
) {

try {
aapt.link(aaptConfig, LoggerWrapper(logger))
} catch (e: Aapt2Exception) {
throw rewriteLinkException(
e,
errorFormatMode,
aaptConfig.mergeBlameDirectory,
aaptConfig.manifestMergeBlameFile,
aaptConfig.identifiedSourceSetMap,
logger
)
} catch (e: Aapt2InternalException) {
throw e
} catch (e: Exception) {
throw ProcessException("Failed to execute aapt", e)
}
//...
}

在上篇分析MergeResources流程分析的文章中知道aapt的实例对象是PartialInProcessResourceProcessor,内部调用delegatelink方法,最终调用的是Aapt2DaemonImpllink方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
abstract class Aapt2Daemon(
protected val displayName: String,
protected val logger: ILogger) : Aapt2 {
override fun link(request: AaptPackageConfig, logger: ILogger) {
checkStarted()//检查aapt进程是开启
try {
doLink(request, logger)//调用
} catch (e: Aapt2Exception) {
// Propagate errors in the users sources directly.
throw e
} catch (e: TimeoutException) {
handleError("Link timed out", e)
} catch (e: Exception) {
handleError("Unexpected error during link", e)
}
}
//...
}

checkStarted流程在MergeResources流程分析已经看过,Aapt2DaemonImpldoLink

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Aapt2DaemonImpl(
displayId: String,
private val aaptPath: String,
private val aaptCommand: List<String>,
versionString: String,
private val daemonTimeouts: Aapt2DaemonTimeouts,
logger: ILogger) :
Aapt2Daemon(
displayName = "AAPT2 $versionString Daemon $displayId",
logger = logger){
override fun doLink(request: AaptPackageConfig, logger: ILogger) {
val waitForTask = WaitForTaskCompletion(displayName, logger)
try {
processOutput.delegate = waitForTask
Aapt2DaemonUtil.requestLink(writer, request)
val result = waitForTask.future.get(daemonTimeouts.link, daemonTimeouts.linkUnit)
//...
} finally {
processOutput.delegate = noOutputExpected
}
}
}

Aapt2DaemonUtil.requestLink方法是往aapt进程的写入句柄输入参数并flush,然后读取link命令的执行结果。具体各个参数的说明大家可以参考link_options官方文档。

结语

LinkApplicationAndroidResourcesTask相比MergeResouces的过程要简单很多,只是启动了一个appt进程,把编译后的*.flat产物给aapt进程处理生产产物。
Alt text