一:前言
gradle
是如何构建apk
的- 各个
Task
做了什么操作 - 了解了构建过程,我们可以做什么
二:构建./gradlew assembleDebug
去掉了Test相关的任务
Executing tasks: [:app:assembleDebug, :app:assembleDebugUnitTest, :app:assembleDebugAndroidTest] in project E:\git\Demo2
> Configure project :app
----------CommonPlugin start----------
----------CommonPlugin end----------
enable=true
doubleClickTimeSpace=600
> Task :app:preBuild UP-TO-DATE // 生命周期结点
> Task :app:preDebugBuild UP-TO-DATE // 生命周期结点
> Task :commonsdk:preBuild UP-TO-DATE // 生命周期结点
> Task :commonsdk:preDebugBuild UP-TO-DATE // 生命周期结点
> Task :commonsdk:compileDebugAidl NO-SOURCE // 编译aidl文件
> Task :app:compileDebugAidl NO-SOURCE // 编译aidl文件
> Task :commonsdk:packageDebugRenderscript NO-SOURCE // RenderScript 是用于在 Android 上以高性能运行计算密集型任务的框架。Gradle 插件 7.2 开始,废弃了 RenderScript API。
> Task :app:compileDebugRenderscript NO-SOURCE // RenderScript 是用于在 Android 上以高性能运行计算密集型任务的框架。Gradle 插件 7.2 开始,废弃了 RenderScript API。
> Task :app:generateDebugBuildConfig // 生成BuildConfing.java文件,详情见下方
> Task :app:javaPreCompileDebug // 生成annotationProcessors.json文件,详情见下方
> Task :app:generateDebugResValues // 生成gradleResValues.xml文件,详情见下方
> Task :app:generateDebugResources // 空任务,作为锚点
> Task :commonsdk:compileDebugRenderscript NO-SOURCE // sdk,参考app
> Task :commonsdk:generateDebugResValues // sdk,参考app
> Task :commonsdk:generateDebugResources // sdk,参考app
> Task :commonsdk:packageDebugResources // sdk,参考app
> Task :commonsdk:writeDebugAarMetadata // sdk,参考app
> Task :app:checkDebugAarMetadata // 检测aar的依赖兼容性,`CheckAarMetadataTask`检测主项目和aar的minCompileSdk等
> Task :app:createDebugCompatibleScreenManifests // 根据android{ splits { density } }配置,在AndroidManifest.xml中生成compatible-screens,制定屏幕适配
> Task :app:extractDeepLinksDebug // 提取深度链接,解析navigation/xxx.xml导航文件
> Task :commonsdk:extractDeepLinksDebug
> Task :app:mergeDebugResources // 编译并合并drawable,values内资源(aapt)
> Task :commonsdk:compileDebugLibraryResources
> Task :commonsdk:processDebugManifest
> Task :app:processDebugMainManifest // 合并Manifest
> Task :app:processDebugManifest // 多渠道合并Manifest
> Task :commonsdk:parseDebugLocalResources
> Task :app:processDebugManifestForPackage // 根据split配置,配置不同的<compatible-screens>标签,写入不同分辨率的目录中
> Task :commonsdk:generateDebugBuildConfig
> Task :commonsdk:javaPreCompileDebug
> Task :app:mergeDebugNativeDebugMetadata NO-SOURCE
> Task :app:mergeDebugShaders // # RenderScript相关
> Task :app:compileDebugShaders NO-SOURCE // # RenderScript相关
> Task :app:generateDebugAssets UP-TO-DATE // 空task,锚点
> Task :commonsdk:mergeDebugShaders
> Task :commonsdk:compileDebugShaders NO-SOURCE
> Task :commonsdk:generateDebugAssets UP-TO-DATE
> Task :commonsdk:packageDebugAssets
> Task :app:mergeDebugAssets // 读取library和app的assets目录,合并生成一个merge.xml文件
> Task :commonsdk:generateDebugRFile
> Task :app:processDebugResources // 生成资源链接文件(AndroidManifest.xml、图片等)
> Task :commonsdk:compileDebugJavaWithJavac
> Task :app:compressDebugAssets // 将assets目录下的每个文件各自以zip压缩成单独文件
> Task :commonsdk:bundleLibCompileToJarDebug
> Task :app:compileDebugJavaWithJavac // 编译java文件
> Task :app:compileDebugSources // 空task,锚点
> Task :app:desugarDebugFileDependencies // 对依赖库中的代码进行反编译和重写,以适应新的Java版本。
> Task :commonsdk:bundleLibRuntimeToJarDebug
> Task :app:checkDebugDuplicateClasses // 检查重复class
> Task :app:mergeExtDexDebug // 将三方sdk里面的classes.dex合并成一个classes.dex
> Task :app:mergeLibDexDebug // 将library里面的classes.dex合并成一个classes.dex,流程同mergeExtDexDebug
> Task :app:dexBuilderDebug // 将 Java 字节码(`.class` 文件)转换为 Dalvik 字节码(`.dex` 文件)
> Task :app:mergeDebugJniLibFolders // 合并所有模块的`JNI`库(`Native`库,即 `.so` 文件)
> Task :commonsdk:processDebugJavaRes NO-SOURCE
> Task :commonsdk:bundleLibResDebug NO-SOURCE
> Task :commonsdk:mergeDebugJniLibFolders
> Task :commonsdk:mergeDebugNativeLibs NO-SOURCE
> Task :commonsdk:stripDebugDebugSymbols NO-SOURCE
> Task :commonsdk:copyDebugJniLibsProjectOnly
> Task :app:mergeProjectDexDebug // 合并项目中的dex
> Task :app:validateSigningDebug // 校验签名文件
> Task :app:mergeDebugNativeLibs // 合并app和Library的.so
> Task :app:stripDebugDebugSymbols // 剥离本地库(Native Libraries,即 `.so` 文件)中的调试符号(Debug Symbols)。
> Task :app:packageDebug // 将所有编译后的资源、代码、本地库等文件打包成一个完整的 APK 文件
> Task :app:assembleDebug
三:重要任务
任务的创建在com.android.build.gradle.internal.TaskManager#createTasks()
如何根据任务名称快速找到相关Task类,可以看VariantTaskCreationAction computeTaskName()
的调用地方。比如preDebugBuild
3.1 compileDebugAidl
编译aidl
文件,生成对应的Java
文件。相关类:AidlCompile.kt
abstract class AidlCompile : NonIncrementalTask() {
override fun doTaskAction() {
// destinationDir = E:\git\Demo2\app\build\generated\aidl_source_output_dir\debug\out
val destinationDir = sourceOutputDir.get().asFile
val parcelableDir = packagedDir.orNull
FileUtils.cleanOutputDir(destinationDir)
if (parcelableDir != null) {
FileUtils.cleanOutputDir(parcelableDir.asFile)
}
getWorkerFacadeWithThreads(false).use { workers ->
// sourceFolders有两个值
// E:\git\Demo2\app\src\main\aidl
// E:\git\Demo2\app\src\debug\aidl
val sourceFolders = sourceDirs.get()
// importFolders有三个值
// C:\Users\kongge\.gradle\caches\transforms-3\d92f567b0fc14f77a63bcd78645d8cc1\transformed\core-1.5.0\aidl
// C:\Users\kongge\.gradle\caches\transforms-3\f1ccdc114fbca9440e5044ebf05b2405\transformed\versionedparcelable-1.1.1\aidl
// E:\git\Demo2\commonsdk\build\intermediates\aidl_parcelable\debug\out
val importFolders = importDirs.files
val fullImportList = sourceFolders + importFolders
val processor = AidlProcessor(
sdkBuildService.get().aidlExecutableProvider.get().absolutePath,
getAidlFrameworkProvider().get().absolutePath,
fullImportList,
destinationDir,
parcelableDir?.asFile,
packageWhitelist,
DepFileProcessor(),
GradleProcessExecutor(execOperations::exec),
LoggedProcessOutputHandler(LoggerWrapper(logger))
)
for (dir in sourceFolders) {
workers.submit(AidlCompileRunnable::class.java, AidlCompileParams(dir, processor))
}
}
}
}
internal class AidlCompileRunnable @Inject
constructor(private val params: AidlCompileParams) : Runnable {
override fun run() {
try {
DirectoryWalker.builder()
.root(params.dir.toPath())
.extensions("aidl")
.action(params.processor)
.build()
.walk()
} catch (e: IOException) {
throw RuntimeException(e)
}
}
}
public class DirectoryWalker {
public DirectoryWalker walk() throws IOException {
// root = E:\git\Demo2\app\src\main\aidl
if (!Files.exists(root)) {
return this;
}
}
}
public class AidlProcessor implements DirectoryWalker.FileAction {
public void call(@NonNull Path startDir, @NonNull Path inputFilePath) throws IOException {
ProcessInfoBuilder builder = new ProcessInfoBuilder();
// mAidlExecutable = D:\software\Android\sdk\build-tools\29.0.2\aidl.exe
builder.setExecutable(mAidlExecutable);
// mFrameworkLocation = D:\software\Android\sdk\platforms\android-30\framework.aidl
builder.addArgs("-p" + mFrameworkLocation);
// mSourceOutputDir = E:\git\Demo2\app\build\generated\aidl_source_output_dir\debug\out
builder.addArgs("-o" + mSourceOutputDir.getAbsolutePath());
...
// C:\Users\THS\AppData\Local\Temp\aidl457617678321841340.d
File depFile = File.createTempFile("aidl", ".d");
...
// mProcessExecutor = GradleProcessExecutor
// builder.createProcess()使用aild命令生成Java文件,具体命令参数如下
// Executable : D:\software\Android\sdk\build-tools\29.0.2\aidl.exe
// arguments :
// -pD:\software\Android\sdk\platforms\android-30\framework.aidl
// ...
ProcessResult result = mProcessExecutor.execute(
builder.createProcess(), mProcessOutputHandler);
...
}
}
}
小结
build.gradle
开启aidl
配置
buildFeatures{
aidl = true
}
2. 定义aidl
接口,src/main/aidl/com/kongge/demo/IMusicPlayer.aidl
3. AidlCompile.kt
读取src/main/aidl
目录,如果存在则通过AidlProcessor.java
拼接命令,命令为aidl.exe
4. 生成Java
文件,目录为:app\build\generated\aidl_source_output_dir\debug\out
3.2 packageDebugRenderscript
RenderScript 是用于在 Android 上以高性能运行计算密集型任务的框架。RenderScript 主要用于数据并行计算,不过串行工作负载也可以从中受益。RenderScript 运行时可在设备上提供的多个处理器(如多核 CPU 和 GPU)间并行调度工作。这样您就能够专注于表达算法而不是调度工作。RenderScript 对于专注于图像处理、计算摄影或计算机视觉的应用来说尤其有用。
3.3 generateDebugBuildConfig
源码查看com.android.build.gradle.tasks.GenerateBuildConfig
val buildConfigData = BuildConfigData.Builder()
.setBuildConfigPackageName(buildConfigPackageName.get())
.apply {
if (sourceOutputDir.isPresent) {
addBooleanField("DEBUG", debuggable.get())
}
//...
}
val generator: GeneratedCodeFileCreator =
if (bytecodeOutputFile.isPresent) {
// ...
BuildConfigByteCodeGenerator(byteCodeBuildConfigData)
} else {
// destinationDir = E:\git\Demo2\app\build\generated\source\buildConfig\release
val destinationDir = sourceOutputDir.get().asFile
// 递归删除目录文件
FileUtils.cleanOutputDir(destinationDir)
//...
BuildConfigGenerator(sourceCodeBuildConfigData)
}
generator.generate()
BuildConfigGenerator
将数据写入BuildConfig.java
文件
class BuildConfigGenerator(buildConfigData: BuildConfigData) : GeneratedCodeFileCreator {
private val genFolder: String = buildConfigData.outputPath.toString()
private val buildConfigPackageName: String = buildConfigData.buildConfigPackageName
private val fields: Map<String, BuildConfigField<out Serializable>> = buildConfigData.buildConfigFields
/** buildConfigPackageName = com.kongge.demo,就是包名 */
override val folderPath =
File(
genFolder,
buildConfigPackageName.replace('.', File.separatorChar)
)
// BUILD_CONFIG_NAME = "BuildConfig.java"
override val generatedFilePath = File(folderPath, BUILD_CONFIG_NAME)
override fun generate() {
Closer.create().use { closer ->
// generatedFilePath = E:\git\Demo2\app\build\generated\source\buildConfig\release\com\kongge\demo\BuildConfig.java
val fos = closer.register(FileOutputStream(generatedFilePath))
val out = closer.register(
OutputStreamWriter(fos, Charsets.UTF_8)
)
val writer = closer.register(JavaWriter(out))
writer.emitJavadoc("Automatically generated file. DO NOT MODIFY")
.emitPackage(buildConfigPackageName)
.beginType(
"BuildConfig",
"class",
PUBLIC_FINAL
)
// fields内容如下
// DEBUG -> {BuildConfigField@16137} BuildConfigField(type=boolean, value=false, comment=null)
// APPLICATION_ID -> {BuildConfigField@16139} BuildConfigField(type=String, value="com.kongge.demo", comment=null)
// ...
for ((key, value) in fields) {
value.emit(key, writer)
}
writer.endType()
}
}
}
BuildConfig.java
文件生成逻辑
- 读取配置
- 清空目录
项目根目录\build\generated\source\buildConfig\release
- 创建
BuildConfig.java
文件,路径项目根目录\build\generated\source\buildConfig\release\com\kongge\demo\BuildConfig.java
- 使用
JavaWriter
将类数据写入BuildConfig.java
文件
BuildConfig.java
文件用途
- 定义:
build.gradle
中定义字段名,字段类型和值,例如buildConfigField "String", "APP_ID", '"xxxx"' - 使用:
BuildConfig.APP_ID
- 范围:
build.gradle
内defaultConfig
、buildTypes
、productFlavors
等都可以使用
// build.gradle
android {
defaultConfig {
buildConfigField "String", "APP_ID", '"xxxx"' // 注意String的值需要用单引号将双引号括起来
buildConfigField "boolean", "printLog", "true"
}
}
3.4 javaPreCompileDebug
读取注解处理器,生成annotationProcessors.json
文件,相关类是JavaPreCompileTask.java
public abstract class JavaPreCompileTask extends NonIncrementalTask {
protected void doTaskAction() {
try (WorkerExecutorFacade workerExecutor = getWorkerFacadeWithWorkers()) {
workerExecutor.submit(
PreCompileRunnable.class,
new PreCompileParams(
processorListFile.get().getAsFile(),
toSerializable(annotationProcessorConfiguration),
apOptionClassNames));
}
}
}
public static class PreCompileRunnable implements Runnable {
public void run() {
// 寻找注解处理器
// apOptionClassNames为空
// annotationProcessorConfiguration size=9(处理器的个数,项目配置+默认配置)
Map<String, Boolean> annotationProcessors =
JavaCompileUtils.detectAnnotationProcessors(
params.apOptionClassNames, params.annotationProcessorConfiguration);
// annotationProcessors size=2(注解处理器的个数),将注解处理器写入文件`annotationProcessors.json
JavaCompileUtils.writeAnnotationProcessorsToJsonFile(
annotationProcessors, params.processorListFile);
}
}
fun detectAnnotationProcessors(
apOptionClassNames: List<String>,
processorClasspath: Collection<SerializableArtifact>
): Map<String, Boolean> {
val processors = mutableMapOf<String, Boolean>()
if (!apOptionClassNames.isEmpty()) {
...
} else {
val processorArtifacts = mutableMapOf<SerializableArtifact, Boolean>()
processorArtifacts.putAll(detectAnnotationProcessors(processorClasspath))
processors.putAll(processorArtifacts.mapKeys { it.key.displayName })
}
return processors
}
fun detectAnnotationProcessors(
artifacts: Collection<SerializableArtifact>
): Map<SerializableArtifact, Boolean> {
val processors = mutableMapOf<SerializableArtifact, Boolean>()
for (artifact in artifacts) {
val artifactFile = artifact.file
if (artifactFile.isDirectory) {
// ANNOTATION_PROCESSORS_INDICATOR_FILE = "META-INF/services/javax.annotation.processing.Processor"
if (File(artifactFile, ANNOTATION_PROCESSORS_INDICATOR_FILE).exists()) {
processors[artifact] =
File(artifactFile, INCREMENTAL_ANNOTATION_PROCESSORS_INDICATOR_FILE)
.exists()
}
} else if (artifactFile.isFile) {
try {
JarFile(artifactFile).use { jarFile ->
if (jarFile.getJarEntry(ANNOTATION_PROCESSORS_INDICATOR_FILE) != null) {
processors[artifact] = jarFile.getJarEntry(
INCREMENTAL_ANNOTATION_PROCESSORS_INDICATOR_FILE
) != null
}
}
} catch (e: IOException) {
}
}
}
return processors
}
小结
build.gradle
中新增注解处理器annotationProcessor 'com.alibaba:arouter-compiler:1.2.1'
- 获取所有处理器配置:
processorClasspath
(List)- SerializableArtifact(displayName=arouter-compiler-1.2.1.jar
- SerializableArtifact(displayName=arouter-annotation-1.0.6.jar
- SerializableArtifact(displayName=auto-service-1.0-rc2.jar
- SerializableArtifact(displayName=javapoet-1.7.0.jar
- SerializableArtifact(displayName=commons-lang3-3.4.jar
- SerializableArtifact(displayName=commons-collections4-4.1.jar
- SerializableArtifact(displayName=fastjson-1.2.48.jar
- SerializableArtifact(displayName=auto-common-0.3.jar
- SerializableArtifact(displayName=guava-18.0.jar
- 查找注解处理器:从
processorClasspath
处理器列表中,遍历各个jar
文件中是否包含"META-INF/services/javax.annotation.processing.Processor"
文件,如果包含则是注解处理器,汇总到annotationProcessors
(LinkedHashMap)- "arouter-compiler-1.2.1.jar (com.alibaba:arouter-compiler:1.2.1)" -> {Boolean@15280} false
- "auto-service-1.0-rc2.jar (com.google.auto.service:auto-service:1.0-rc2)" -> {Boolean@15280} false
- 将
annotationProcessors
写入annotationProcessors.json
文件,路径为:E:\git\Demo2\app\build\intermediates\annotation_processor_list\debug\annotationProcessors.json
3.5 generateDebugResValues
读取build.gradle
里面的resValue
资源定义,生成gradleResValues.xml
,相关类GenerateResValues.java
android {
buildTypes {
release {
...
resValue("string", "author_name", "kongge")
resValue("bool", "debug_open", "false")
}
debug {
...
resValue("string", "author_name", "kongge")
resValue("bool", "debug_open", "true")
}
}
}
abstract class GenerateResValues : NonIncrementalTask() {
override fun doTaskAction() {
//resOutputDir = E:\git\Demo2\app\build\generated\res\resValues\debug
val folder = resOutputDir
// Always clean up the directory before use.
FileUtils.cleanOutputDir(folder)
if (items.get().isNotEmpty()) {
ResValueGenerator(folder, items.get()).generate()
}
}
}
class ResValueGenerator(
genFolder: File,
private val requests: Map<ResValue.Key, ResValue>
) {
companion object {
const val RES_VALUE_FILENAME_XML = "gradleResValues.xml"
private val RESOURCES_WITH_TAGS: List<ResourceType> =
ImmutableList.of(
ResourceType.ARRAY,
ResourceType.ATTR,
ResourceType.BOOL,
ResourceType.COLOR,
ResourceType.STYLEABLE,
ResourceType.DIMEN,
ResourceType.FRACTION,
ResourceType.INTEGER,
ResourceType.PLURALS,
ResourceType.STRING,
ResourceType.STYLE
)
}
fun generate() {
// E:\git\Demo2\app\build\generated\res\resValues\debug\values
val pkgFolder = folderPath
// RES_VALUE_FILENAME_XML = "gradleResValues.xml"
val resFile = File(pkgFolder, RES_VALUE_FILENAME_XML)
val builder = factory.newDocumentBuilder()
val document = builder.newDocument()
// TAG_RESOURCES = "resources"
val rootNode: Node = document.createElement(SdkConstants.TAG_RESOURCES)
// requests内容如下
// {ResValue$Key@15357} Key(type=string, name=author_name) -> {ResValue@15358} ResValue(value=kongge, comment=Value from build type: debug)
// {ResValue$Key@15662} Key(type=bool, name=debug_open) -> {ResValue@15663} ResValue(value=true, comment=Value from build type: debug)
for ((key, value) in requests) {
...
rootNode.appendChild(itemNode)
}
}
}
小结:
build.gradle
里面通过resValue
定义string
\bool
等资源,所有的类型如下ARRAY("array", "Array", "string-array", "integer-array")
ATTR("attr", "Attr")
BOOL("bool", "Boolean")
COLOR("color", "Color")
STYLEABLE("styleable", "Styleable", Kind.STYLEABLE)
DIMEN("dimen", "Dimension")
FRACTION("fraction", "Fraction")
INTEGER("integer", "Integer")
PLURALS("plurals", "Plurals")
STRING("string", "String")
STYLE("style", "Style")
generateDebugResValues
读取build.gradle
配置ResValueGenerator
将配置生成生成gradleResValues.xml
(若无定义resValue
,则不会生成文件),写入文件E:\git\Demo2\app\build\generated\res\resValues\debug\gradleResValues.xml
3.6 createDebugCompatibleScreenManifests
根据android{ splits { density } }
配置,在AndroidManifest.xml
中生成compatible-screens
,制定屏幕适配,相关类:CompatibleScreensManifest.java
abstract class CompatibleScreensManifest : NonIncrementalTask() {
override fun doTaskAction() {
BuiltArtifactsImpl(
artifactType = COMPATIBLE_SCREEN_MANIFEST,
applicationId = applicationId.get(),
variantName = variantName,
elements = variantOutputs.get().mapNotNull {
val generatedManifest = generate(it)
if (generatedManifest != null)
BuiltArtifactImpl.make(
outputFile = generatedManifest.absolutePath,
versionCode = it.versionCode.orNull,
versionName = it.versionName.orNull,
variantOutputConfiguration = it.variantOutputConfiguration
)
else
null
}
).save(outputFolder.get())
}
private fun generate(variantOutput: VariantOutputImpl): File? {
val densityFilter = variantOutput.variantOutputConfiguration.getFilter(
FilterConfiguration.FilterType.DENSITY) ?: return null
val content = StringBuilder()
content.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
.append("<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n")
.append("\n")
if (minSdkVersion.isPresent) {
content.append(" <uses-sdk android:minSdkVersion=\"")
.append(minSdkVersion.get())
.append("\"/>\n")
}
content.append(" <compatible-screens>\n")
// screenSizes为compatibleScreens里面配置的几个屏幕尺寸
for (size in screenSizes) {
content.append(" <screen android:screenSize=\"")
.append(size)
.append("\" " + "android:screenDensity=\"")
.append(density).append("\" />\n")
}
val splitFolder = File(outputFolder.get().asFile,
variantOutput.variantOutputConfiguration.dirName())
FileUtils.mkdirs(splitFolder)
// manifestFile = E:\git\Demo2\app\build\intermediates\compatible_screen_manifest\debug\mdpi\AndroidManifest.xml
val manifestFile = File(splitFolder, SdkConstants.ANDROID_MANIFEST_XML)
...
}
}
小结
split
分包参考:Android打包split
- 读取
build.gradle
配置
android {
splits {
// Configures multiple APKs based on screen density.
density {
// Configures multiple APKs based on screen density.
enable true
// Specifies a list of compatible screen size settings for the manifest.
compatibleScreens 'small', 'normal', 'large', 'xlarge'
}
}
}
CompatibleScreensManifest.java
读取splits
配置,向不同分辨率目录写入对应的AndroidManifest.xml
。
分辨率目录支持(下面是文件生成的顺序的):
- xxxhdpi
- mdpi
- ldpi
- xxhdpi
- hdpi
- xhdpi
例如文件路径:E:\git\Demo2\app\build\intermediates\compatible_screen_manifest\debug\mdpi\AndroidManifest.xml
文件内容:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-sdk android:minSdkVersion="21"/>
<compatible-screens>
<!--android:screenSize是build.gradle中配置的 -->
<!--screenDensity="mdpi"在debug\hdpi目录中为hdpi-->
<screen android:screenSize="small" android:screenDensity="mdpi" />
<screen android:screenSize="normal" android:screenDensity="mdpi" />
<screen android:screenSize="large" android:screenDensity="mdpi" />
<screen android:screenSize="xlarge" android:screenDensity="mdpi" />
</compatible-screens>
</manifest>
compatible-screens
:Android系统不会读取<compatible-screen>
清单元素(无论是在安装的时候,还是在运行的时候)。这个元素的信息只能被外部服务使用(如Google Play),以便使其能够更好的理解应用程序跟指定屏幕配置的兼容性。任何没有在这个元素中声明的屏幕配置,都是跟应用程序不兼容的屏幕。这样,外部服务(如Google Play)就不应该把应用程序提供给带有这样屏幕的设备。
3.7 extractDeepLinksDebug
参考:导航组件
读取res/navigation/xxx.xml
,写入navigation.json
,相关类AndroidVariantTask.kt
abstract class ExtractDeepLinksTask: AndroidVariantTask() {
fun create() {
val navigationIds = mutableSetOf<String>()
val navDatas = mutableListOf<NavigationXmlDocumentData>()
//navFilesFolders[0] = {File@16244} "E:\git\Demo2\app\src\debug\res\navigation"
//navFilesFolders[1] = {File@13692} "E:\git\Demo2\app\src\main\res\navigation"
navFilesFolders.forEach { folder ->
if (folder.exists()) {
folder.listFiles().map { navigationFile ->
// DOT_XML_EXT = Regex("\\.xml$"),去掉文件后缀
val navigationId = navigationFile.name.replace(DOT_XML_EXT, "")
if (navigationIds.add(navigationId)) {
navigationFile.inputStream().use { inputStream ->
navDatas.add(
// 解析xml文件,转化成json
NavigationXmlLoader
.load(navigationId, navigationFile, inputStream)
.convertToData())
}
}
}
}
}
// 将数据写入E:\git\Demo2\app\build\intermediates\navigation_json\debug\navigation.json
FileUtils.writeToFile(
navigationJson.asFile.get(),
GsonBuilder().setPrettyPrinting().create().toJson(navDatas))
}
}
小结
- 配置
src/main/res/navigation/nav_graph.xml
,参考导航组件 ExtractDeepLinksTask.kt
遍历res/navigation
里面的文件,例如nav_graph.xml
,解析里面的标签,fragment
、deepLink
等- 将解析的数据写入文件
navigation.json
,路径为:E:\git\Demo2\app\build\intermediates\navigation_json\debug\navigation.json
3.8 mergeDebugResources
合并资源,将res
目录下所有资源拷贝一份到build/intermediates/packaged_res/debug
,并生成values.xml
和merge.xml
文件,之后使用aapt
编译对应资源。相关类:MergeResources.java
public abstract class MergeResources extends ResourceAwareTask {
protected void doFullTaskAction() throws IOException, JAXBException {
ResourcePreprocessor preprocessor = getPreprocessor();
// destinationDir = E:\git\Demo2\app\build\intermediates\res\merged\debug
File destinationDir = getOutputDir().get().getAsFile();
// 指定资源路径,见下方图片
List<ResourceSet> resourceSets =
getConfiguredResourceSets(preprocessor, getAaptEnv().getOrNull());
ResourceMerger merger = new ResourceMerger(getMinSdk().get());
// try-with,执行完之后会调用close()方法
try (WorkerExecutorFacade workerExecutorFacade = getAaptWorkerFacade();
ResourceCompilationService resourceCompiler =
getResourceProcessor(
getProjectName(),
getPath(),
getAapt2FromMaven(),
workerExecutorFacade,
errorFormatMode,
flags,
processResources,
useJvmResourceCompiler,
getLogger(),
getAapt2DaemonBuildService().get())) {
// 汇总资源目录
Blocks.recordSpan(
getProjectName(),
getPath(),
GradleBuildProfileSpan.ExecutionType.TASK_EXECUTION_PHASE_1,
() -> {
for (ResourceSet resourceSet : resourceSets) {
resourceSet.loadFromFiles(new LoggerWrapper(getLogger()));
merger.addDataSet(resourceSet);
}
});
// 合并资源
Blocks.recordSpan(
getProjectName(),
getPath(),
GradleBuildProfileSpan.ExecutionType.TASK_EXECUTION_PHASE_2,
() -> merger.mergeData(writer, false /*doCleanUp*/));
}
}
}
abstract class DataMerger<I extends DataItem<F>, F extends DataFile<I>, S extends DataSet<I, F>>
implements DataMap<I> {
public void mergeData(@NonNull MergeConsumer<I> consumer, boolean doCleanUp)
throws MergingException {
consumer.start(mFactory);
try {
// get all the items keys.
Set<String> dataItemKeys = new HashSet<>();
for (S dataSet : mDataSets) {
dataSet.checkItems();
ListMultimap<String, I> map = dataSet.getDataMap();
dataItemKeys.addAll(map.keySet());
}
for (String dataItemKey : dataItemKeys) {
if (requiresMerge(dataItemKey)) {
...
mergeItems(dataItemKey, items, consumer);
continue;
}
} finally {
// 写入编译命令行等操作
consumer.end();
}
}
}
public class MergedResourceWriter
extends MergeWriter<ResourceMergerItem, MergedResourceWriter.FileGenerationParameters> {
public void end() throws ConsumerException {
// 父类调用了postWriteAction();
super.end();
try {
File tmpDir = new File(mTemporaryDirectory, "stripped.dir");
...// FileUtils.cleanOutputDir(tmpDir);
while (!mCompileResourceRequests.isEmpty()) {
CompileResourceRequest request = mCompileResourceRequests.poll();
...
mResourceCompiler.submitCompile(
new CompileResourceRequest(
fileToCompile,
request.getOutputDirectory(),
request.getInputDirectoryName(),
request.getInputFileIsFromDependency(),
pseudoLocalesEnabled,
crunchPng,
ImmutableMap.of(),
request.getInputFile()));
}
}
}
@Override
protected void postWriteAction() throws ConsumerException {
// mTemporaryDirectory = E:\git\Demo2\app\build\intermediates\incremental\mergeDebugResources
File tmpDir = new File(mTemporaryDirectory, "merged.dir");
for (String key : mValuesResMap.keySet()) {
// folderName = values
String folderName = key.isEmpty() ?
ResourceFolderType.VALUES.getName() :
ResourceFolderType.VALUES.getName() + RES_QUALIFIER_SEP + key;
// valuesFolder = E:\git\Demo2\app\build\intermediates\incremental\mergeDebugResources\merged.dir\values
File valuesFolder = new File(tmpDir, folderName);
// E:\git\Demo2\app\build\intermediates\incremental\mergeDebugResources\merged.dir\values\values.xml
File outFile = new File(valuesFolder, folderName + DOT_XML);
DocumentBuilder builder = mFactory.newDocumentBuilder();
Document document = builder.newDocument();
...
final String content;
Map<SourcePosition, SourceFilePosition> blame =
mMergingLog == null ? null : Maps.newLinkedHashMap();
if (blame != null) {
content = XmlUtils.toXml(document, blame);
} else {
content = XmlUtils.toXml(document);
}
// outFile = E:\git\Demo2\app\build\intermediates\incremental\mergeDebugResources\merged.dir\values\values.xml
// content是values目录下资源的合并集,见下方
Files.asCharSink(outFile, Charsets.UTF_8).write(content);
}
}
}
content
内容
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:ns1="urn:oasis:names:tc:xliff:document:1.2">
<attr format="reference" name="constraintSet"/>
<attr format="boolean" name="isLightTheme"/>
...
<string name="app_name">demo</string>
...
MergeResources#doFullTaskAction() try-with
语法,调用WorkerExecutorResourceCompilationService#close()
class WorkerExecutorResourceCompilationService(...) {
override fun close() {
requests.sortWith(compareBy({ getExtension(it.inputFile) }, { it.inputFile.length() }))
val buckets = minOf(requests.size, 8) // Max 8 buckets
for (bucket in 0 until buckets) {
val bucketRequests = requests.filterIndexed { i, _ ->
i.rem(buckets) == bucket
}
// b/73804575
workerExecutor.submit(
Aapt2CompileRunnable::class.java,
Aapt2CompileRunnable.Params(aapt2ServiceKey, bucketRequests, errorFormatMode, true)
)
}
requests.clear()
}
}
class Aapt2CompileRunnable @Inject constructor(
private val params: Params
) : Runnable {
override fun run() {
val logger = Logging.getLogger(this::class.java)
useAaptDaemon(params.aapt2ServiceKey) { daemon ->
params.requests.forEach { request ->
try {
//LeasedAaptDaemon#compile()
daemon.compile(request, LoggerWrapper(logger))
} catch (exception: Aapt2Exception) {
throw rewriteCompileException(
exception,
request,
params.errorFormatMode,
params.enableBlame,
logger
)
}
}
}
}
}
abstract class Aapt2Daemon(
protected val displayName: String,
protected val logger: ILogger) : Aapt2 {
override fun compile(request: CompileResourceRequest, logger: ILogger) {
checkStarted()
try {
doCompile(request, logger)
...
}
}
}
class Aapt2DaemonImpl(...) {
override fun doCompile(request: CompileResourceRequest, logger: ILogger) {
val waitForTask = WaitForTaskCompletion(displayName, logger)
try {
processOutput.delegate = waitForTask
Aapt2DaemonUtil.requestCompile(writer, request)
}
}
}
public class Aapt2DaemonUtil {
public static void requestCompile(
@NonNull Writer writer, @NonNull CompileResourceRequest command) throws IOException {
request(writer, "c", AaptV2CommandBuilder.makeCompileCommand(command));
}
}
command
:c
args
:- "--legacy"
- "-o"
- "E:\git\Demo2\app\build\intermediates\res\merged\release"
- "E:\git\Demo2\app\src\main\res\drawable\ic_launcher.png"
小结
- 将
res
目录下所有资源拷贝一份到build/intermediates/packaged_res/debug
- 生成
values.xml
和merge.xml
文件 - 使用
aapt
编译对应资源。
3.9 processDebugMainManifest
相关类:
- app:
ProcessApplicationManifest.kt
- library:
ProcessLibraryManifest.kt
最终都是调用ManifestHelper#mergeManifestsForApplication()
fun mergeManifestsForApplication(...) {
try {
// library编译:
// mainManifest = E:\git\Demo2\commonsdk\src\main\AndroidManifest.xml
// mergeType = LIBRARY
// applicaion编译:
// mainManifest = E:\git\Demo2\app\src\main\AndroidManifest.xml
// mergeType = APPLICATION
val manifestMergerInvoker = ManifestMerger2.newMerger(mainManifest, logger, mergeType)
...
// 插入PACKAGE、VERSION_CODE等属性
setInjectableValues(
manifestMergerInvoker,
packageOverride, versionCode, versionName,
minSdkVersion, targetSdkVersion, maxSdkVersion
)
// Invoker#merge()
val mergingReport = manifestMergerInvoker.merge()
// 保存合并后的清单文件,outMergedManifestLocation = E:\git\Demo2\app\build\intermediates\merged_manifest\debug\out\AndroidManifest.xml
if (outMergedManifestLocation != null) {
save(mergingReport.getMergedDocument(MergingReport.MergedManifestKind.MERGED),
File(outMergedManifestLocation))
}
}
}
public static class Invoker {
public MergingReport merge() throws MergeFailureException {
// {ManifestSystemProperty$1@16028} "PACKAGE" -> "com.kongge.demo"
// {ManifestSystemProperty$2@16795} "VERSION_CODE" -> "1"
// ...
ImmutableMap<ManifestSystemProperty, Object> systemProperties = mSystemProperties.build();
// 加包名,非library加APPLICATION_ID
...
ManifestMerger2 manifestMerger = new ManifestMerger2(...)
return manifestMerger.merge();
}
}
public class ManifestMerger2 {
private MergingReport merge() throws MergeFailureException {
LoadedManifestInfo loadedMainManifestInfo =
load(
new ManifestInfo(
mManifestFile.getName(),
mManifestFile,
mDocumentType,
null /* mainManifestPackageName */),
selectors,
mergingReportBuilder);
...
// 加载library的清单文件
List<LoadedManifestInfo> loadedLibraryDocuments =
loadLibraries(
selectors,
mergingReportBuilder,
mainPackageAttribute.map(XmlAttribute::getValue).orElse(null));
// 检查每个module/library唯一包名
checkUniquePackageName(...)
// 强制重新解析xml,因为元素可能是通过系统注入添加的。(versionCode等)
performSystemPropertiesInjection(...)
// 合并library
for (LoadedManifestInfo libraryDocument : loadedLibraryDocuments) {
mLogger.verbose("Merging library manifest " + libraryDocument.getLocation());
newMergedDocument = merge(xmlDocumentOptional, libraryDocument, mergingReportBuilder);
if (!newMergedDocument.isPresent()) {
return mergingReportBuilder.build();
}
xmlDocumentOptional = newMergedDocument.get();
}
...
// 如果是APPLICATION,处理navigation.json
...
// 处理PlaceHolder
...
// 再次强制重新解析xml,因为元素可能是通过系统注入添加的。(versionCode等)
performSystemPropertiesInjection(...)
...
// 检查合并后的文件
PostValidator.validate(finalMergedDocument, mergingReportBuilder);
...
MergingReport mergingReport = mergingReportBuilder.build();
// 记录log,mReportFile = E:\git\Demo2\app\build\outputs\logs\manifest-merger-debug-report.txt
if (mReportFile.isPresent()) {
writeReport(mergingReport);
}
return mergingReport;
}
}
小结
ProcessLibraryManifest.kt
遍历各个Library
的AndroidManifest.xml
,解析并检查versionCode
、minSdk
等信息,写入build\intermediates\merged_manifest\debug\out\AndroidManifest.xml
ProcessApplicationManifest.kt
读取app
和各个Library
生成的清单文件,再次走合并逻辑(同Library
),最终将合并后的内容写入build\intermediates\merged_manifest\debug\out\AndroidManifest.xml
3.10 processDebugManifest
相关类:ProcessMultiApkApplicationManifest.kt
,管理多渠道合并
3.11 processDebugManifestForPackage
相关类:ProcessPackagedManifestTask.kt
,跟split
有关,将合并后的清单文件,经过加工处理<compatible-screens>
标签,然后分别写入xxxdpi
、xhdp
等不同目录中,比如E:\git\Demo2\app\build\intermediates\packaged_manifests\debug\mdpi\AndroidManifest.xml
3.12 mergeDebugNativeDebugMetadata
demo无so,后续研究
3.13 mergeDebugShaders
相关类:MergeSourceSetFolders.kt
,处理RenderScript
相关,高版本已废弃,了解->你需要了解的RenderScript
3.14 compileDebugShaders
同上
3.15 mergeDebugAssets
相关类:MergeSourceSetFolders
,合并assets
资源,生成一个merger.xml
文件
abstract class MergeSourceSetFolders : IncrementalTask() {
override fun doFullTaskAction() {
// E:\git\Demo2\app\build\intermediates\incremental\mergeDebugAssets
val incFolder = incrementalFolder!!
// E:\git\Demo2\app\build\intermediates\merged_assets\debug\out
val destinationDir = outputDir.get().asFile
// 0 = {AssetSet@17820} "AssetSet{:commonsdk, sources=[E:\git\Demo2\commonsdk\build\intermediates\library_assets\debug\out]}"
// 1 = {AssetSet@17978} "AssetSet{main, sources=[E:\git\Demo2\app\src\main\assets, E:\git\Demo2\app\build\intermediates\shader_assets\debug\out]}"
// 2 = {AssetSet@18091} "AssetSet{debug, sources=[E:\git\Demo2\app\src\debug\assets]}"
val assetSets = computeAssetSetList()
val merger = AssetMerger()
try {
getWorkerFacadeWithWorkers().use { workerExecutor ->
for (assetSet in assetSets) {
// set needs to be loaded.
assetSet.loadFromFiles(logger)
merger.addDataSet(assetSet)
}
// get the merged set and write it down.
val writer = MergedAssetWriter(destinationDir, workerExecutor)
merger.mergeData(writer, false /*doCleanUp*/)
// 写入一个blob文件来存储DataMerger所知道的所有信息,merger.xml
merger.writeBlobTo(incFolder, writer, false)
}
} catch (e: Exception) {
}
}
}
3.16 processDebugResources
相关类:LinkApplicationAndroidResourcesTask.kt
,
abstract class LinkApplicationAndroidResourcesTask @Inject constructor(objects: ObjectFactory) :
ProcessAndroidResources() {
fun doFullTaskAction(inputStableIdsFile: File?) {
// E:\git\Demo2\app\build\intermediates\processed_res\debug\out
val outputDirectory = resPackageOutputFolder.get().asFile
...
getWorkerFacadeWithWorkers().use {
...
it.submit(AaptSplitInvoker::class.java,...)
if (canHaveSplits.get()) {
it.await()
for (variantOutput in unprocessedOutputs) {
it.submit(AaptSplitInvoker::class.java,...)
}
}
}
}
}
private class AaptSplitInvoker @Inject
internal constructor(private val params: AaptSplitInvokerParams) : Runnable {
override fun run() {
try {
invokeAaptForSplit(params)
} catch (e: IOException) {
throw RuntimeException(e)
}
}
private fun invokeAaptForSplit(params: AaptSplitInvokerParams) {
...
// E:\git\Demo2\app\build\intermediates\processed_res\debug\out\resources-universalDebug.ap_
// E:/git/Demo2/app/build/intermediates/packaged_manifests/debug/universal/AndroidManifest.xml
val resOutBaseNameFile =
getOutputBaseNameFile(params.variantOutput, params.resPackageOutputFolder)
val manifestFile = params.manifestOutput.outputFile
...
getAaptDaemon(params.aapt2ServiceKey!!).use { aaptDaemon ->
processResources(
aaptDaemon,
configBuilder.build(),
params.rClassOutputJar,
LoggerWrapper(logger)
)
}
}
}
fun processResources(
aapt: Aapt2,
aaptConfig: AaptPackageConfig,
rJar: File?,
logger: ILogger
) {
try {
// command:l
// args:
// 0 = "-I"
// 1 = "D:\software\Android\sdk\platforms\android-30\android.jar"
// 2 = "--manifest"
// 3 = "E:\git\Demo2\app\build\intermediates\packaged_manifests\debug\universal\AndroidManifest.xml"
// 4 = "-o"
// 5 = "E:\git\Demo2\app\build\intermediates\processed_res\debug\out\resources-universalDebug.ap_"
// 6 = "-R"
// 7 = "C:\Users\THS\.gradle\caches\transforms-3\c37dd26bdc51d6966263b7ca7557c8a5\transformed\androidx.core\drawable-hdpi-v4_notification_bg_low_normal.9.png.flat"
// ...
aapt.link(aaptConfig, logger)
}
// rJar = E:\git\Demo2\app\build\intermediates\compile_and_runtime_not_namespaced_r_class_jar\debug\R.jar
if (sourceOut != null || rJar != null) {
// 合并R.class
exportToCompiledJava(
depSymbolTables,
rJar.toPath(),
finalIds
)
}
}
小结
- 根据
split
生成不同分辨率的资源链接文件 - 生成
output-metadata.json
文件
3.17 compressDebugAssets
相关类:CompressAssetsTask.kt
,压缩assets
资源
abstract class CompressAssetsTask : NewIncrementalTask() {
override fun doTaskAction(inputChanges: InputChanges) {
CompressAssetsDelegate(
workerExecutor.noIsolation(),
inputDir.get().asFile,
outputDir.get().asFile,
PackagingUtils.getNoCompressPredicateForJavaRes(noCompress.get()),
compressionLevel.get(),
inputChanges.getFileChanges(inputDir)
).run()
}
}
class CompressAssetsDelegate(...) {
fun run() {
for (change in changes) {
if (change.fileType == FileType.DIRECTORY) {
continue
}
// assets/background_image.jpeg
val entryPath = "assets/${change.normalizedPath}"
// E:\git\Demo2\app\build\intermediates\compressed_assets\debug\out\assets\background_image.jpeg.jar
val targetFile = File(outputDir, entryPath + DOT_JAR)
val entryCompressionLevel = if (noCompressPredicate.test(entryPath)) {
Deflater.NO_COMPRESSION
} else {
compressionLevel
}
workQueue.submit(CompressAssetsWorkAction::class.java) {
it.input.set(change.file)
it.output.set(targetFile)
it.entryPath.set(entryPath)
it.entryCompressionLevel.set(entryCompressionLevel)
it.changeType.set(change.changeType)
}
}
}
}
abstract class CompressAssetsWorkAction(...) {
override fun execute() {
// E:\git\Demo2\app\build\intermediates\compressed_assets\debug\out\assets\background_image.jpeg.jar
val output = compressAssetsWorkParameters.output.get().asFile
// ADDED
val changeType = compressAssetsWorkParameters.changeType.get()
if (changeType != ChangeType.ADDED) {
FileUtils.deleteIfExists(output)
}
if (changeType != ChangeType.REMOVED) {
Files.createParentDirs(output)
// 压缩文件
ZipArchive(output).use { jar ->
jar.add(
BytesSource(
compressAssetsWorkParameters.input.get().asFile,
compressAssetsWorkParameters.entryPath.get(),
compressAssetsWorkParameters.entryCompressionLevel.get()
)
)
}
}
}
}
小结
- 将
assets
目录下的文件,单独使用ZIP
压缩,新文件名称为原文件名+.jar
,目录为build\intermediates\compressed_assets\debug\out\assets
3.18 compileDebugJavaWithJavac
相关类:JavaCompile
,编译java
文件
public class JavaCompile extends AbstractCompile implements HasCompileOptions {
@TaskAction
protected void compile(InputChanges inputs) {
DefaultJavaCompileSpec spec = createSpec();
if (!compileOptions.isIncremental()) {
performFullCompilation(spec);
} else {
performIncrementalCompilation(inputs, spec);
}
}
private void performIncrementalCompilation(InputChanges inputs, DefaultJavaCompileSpec spec) {
// E:\git\Demo2\app\build\tmp\compileDebugJavaWithJavac\source-classes-mapping.txt
File sourceClassesMappingFile = getSourceClassesMappingFile();
...
Compiler<JavaCompileSpec> compiler = createCompiler(spec);
compiler = makeIncremental(inputs, sourceFileClassNameConverter, (CleaningJavaCompiler<JavaCompileSpec>) compiler, getStableSources().getAsFileTree());
WorkResult workResult = performCompilation(spec, compiler);
}
}
spec
属性:
3.19 desugarDebugFileDependencies
相关类:DexFileDependenciesTask.kt
,对依赖库中的代码进行反编译和重写,以适应新的Java版本。
3.20 checkDebugDuplicateClasses
相关类:CheckDuplicateClassesTask.kt
,检查重复class
abstract class CheckDuplicateClassesTask : NonIncrementalTask() {
override fun doTaskAction() {
workerExecutor.noIsolation().submit(CheckDuplicatesRunnable::class.java) { params ->
params.projectName.set(projectName)
params.enumeratedClasses.set(enumeratedClassesArtifacts.artifacts.map { it.id.displayName to it.file }.toMap())
}
}
}
abstract class CheckDuplicatesRunnable @Inject constructor(): WorkAction<CheckDuplicatesParams> {
override fun execute() {
CheckDuplicateClassesDelegate().run(
parameters.projectName.get(),
parameters.enumeratedClasses.get()
)
}
}
class CheckDuplicateClassesDelegate {
fun run(
projectName: String,
enumeratedClasses: Map<String, File>) {
// classesMap: HashMap<String,ArrayList<String>>,key是三方依赖库名,value是该库的依赖库名称列表,运行时参数见下方截图
val classesMap = extractClasses(enumeratedClasses)
val maxSize = classesMap.map { it.value.size }.sum()
// 运行时参数见下方截图
val classes = Maps.newHashMapWithExpectedSize<String, MutableList<String>>(maxSize)
classesMap.forEach {
val artifactName = it.key
it.value.forEach { className ->
classes.getOrPut(className) { mutableListOf() }.add(artifactName)
}
}
val duplicatesMap = classes.filter { it.value.size > 1 }.toSortedMap()
if (!duplicatesMap.isEmpty()) {
val lineSeparator = System.lineSeparator()
val duplicateMessages = duplicatesMap
.map { duplicateClassMessage(it.key, it.value) }
.joinToString(lineSeparator)
throw RuntimeException("$duplicateMessages$lineSeparator$lineSeparator$RECOMMENDATION")
}
}
}
3.21 mergeExtDexDebug
、mergeLibDexDebug
相关类:DexMergingTask.kt
- 将依赖库的classes.dex合并成一个classes.dex
- 将library的classes.dex合并成一个classes.dex
abstract class DexMergingTask : NewIncrementalTask() {
override fun doTaskAction(inputChanges: InputChanges) {
// TODO(132615300) Make this task incremental
getWorkerFacadeWithWorkers().use {
it.submit(
DexMergingTaskRunnable::class.java,
DexMergingParams(
dexingType.get(),
errorFormatMode.get(),
dexMerger.get(),
minSdkVersion.get(),
debuggable.get(),
mergingThreshold.get(),
mainDexListFile.orNull?.asFile,
dexFiles.files,
fileDependencyDexFiles.orNull?.asFile,
outputDir.get().asFile
)
)
}
}
}
class DexMergingTaskRunnable @Inject constructor(
private val params: DexMergingParams
) : Runnable {
override fun run() {
...
// demo里没有三方jar,所以这里是空
val jarsInInput = params.getAllDexFiles().filter { it.isDirectory }.flatMap { dir ->
dir.walkTopDown()
.filter { it.isFile && it.extension == SdkConstants.EXT_JAR }
.toSet()
}
// 0 = {File@20303} "C:\Users\THS\.gradle\caches\transforms-3\545296895a93d68428c8751051d1f255\transformed\appcompat-1.3.1-runtime"
// 1 = {File@20304} "C:\Users\THS\.gradle\caches\transforms-3\548a9e75a59ad2fe2d3cf424af65f3fe\transformed\constraint-layout-1.0.2-runtime"
// ...,当前项目依赖的三方sdk
val dexFiles = params.getAllDexFiles() + jarsInInput
}
val allDexFiles = lazy { getAllRegularFiles(dexFiles) }
if (dexFiles.size >= params.mergingThreshold
|| allDexFiles.value.size >= params.mergingThreshold) {
DexMergerTransformCallable(
messageReceiver,
params.dexingType,
processOutput,
params.outputDir,
dexFiles.map { it.toPath() }.iterator(),
params.mainDexListFile?.toPath(),
forkJoinPool,
params.dexMerger,
params.minSdkVersion,
params.isDebuggable
).call()
}
}
public class DexMergerTransformCallable implements Callable<Void> {
public Void call() throws Exception {
DexArchiveMerger merger;
// D8
switch (dexMerger) {
case DX:
DxContext dxContext =
new DxContext(
processOutput.getStandardOutput(), processOutput.getErrorOutput());
merger = DexArchiveMerger.createDxDexMerger(dxContext, forkJoinPool, isDebuggable);
break;
case D8:
int d8MinSdkVersion = minSdkVersion;
if (d8MinSdkVersion < 21 && dexingType == DexingType.NATIVE_MULTIDEX) {
d8MinSdkVersion = 21;
}
merger =
DexArchiveMerger.createD8DexMerger(
messageReceiver, d8MinSdkVersion, isDebuggable, forkJoinPool);
break;
default:
throw new AssertionError("Unknown dex merger " + dexMerger.name());
}
merger.mergeDexArchives(dexArchives, dexOutputDir.toPath(), mainDexList, dexingType);
return null;
}
}
final class D8DexArchiveMerger implements DexArchiveMerger {
public void mergeDexArchives(...) {
// 三方sdk路径
// 0 = {WindowsPath@20624} "C:\Users\THS\.gradle\caches\transforms-3\545296895a93d68428c8751051d1f255\transformed\appcompat-1.3.1-runtime"
// 1 = {WindowsPath@20625} "C:\Users\THS\.gradle\caches\transforms-3\548a9e75a59ad2fe2d3cf424af65f3fe\transformed\constraint-layout-1.0.2-runtime"
// ...
List<Path> inputsList = Lists.newArrayList(inputs);
D8Command.Builder builder = D8Command.builder(d8DiagnosticsHandler);
builder.setDisableDesugaring(true);
builder.setIncludeClassesChecksum(compilationMode == CompilationMode.DEBUG);
...
// minSdkVersion = 21
// compilationMode = DEBUG
// outputDir = E:\git\Demo2\app\build\intermediates\dex\debug\mergeExtDexDebug
//
builder.setMinApiLevel(minSdkVersion)
.setMode(compilationMode)
.setOutput(outputDir, OutputMode.DexIndexed)
.setDisableDesugaring(true)
.setIntermediate(false);
D8.run(builder.build(), forkJoinPool);
}
}
3.22 dexBuilderDebug
相关类:DexArchiveBuilderTask.kt
,将 Java 字节码(.class
文件)转换为 Dalvik 字节码(.dex
文件)
abstract class DexArchiveBuilderTask : NewIncrementalTask() {
override fun doTaskAction(inputChanges: InputChanges) {
DexArchiveBuilderTaskDelegate(...).doProcess()
}
}
class DexArchiveBuilderTaskDelegate(...) {
fun doProcess() {
val processInputType = { classes: Set<File>,
changedClasses: Set<FileChange>,
outputDir: File,
outputKeepRules: File?,
// Not null iff impactedFiles == null
desugarGraphDir: File? ->
processClassFromInput(
inputFiles = classes,
inputFileChanges = changedClasses,
outputDir = outputDir,
outputKeepRules = outputKeepRules,
impactedFiles = impactedFiles,
desugarGraphDir = desugarGraphDir,
bootClasspathKey = bootclasspathServiceKey,
classpathKey = classpathServiceKey
)
}
processInputType(
// 0 = {File@19714} "E:\git\Demo2\app\build\intermediates\compile_and_runtime_not_namespaced_r_class_jar\debug\R.jar"
// 1 = {File@19715} "E:\git\Demo2\app\build\intermediates\javac\debug\classes"
projectClasses,
projectChangedClasses,
// E:\git\Demo2\app\build\intermediates\project_dex_archive\debug\out
projectOutputDex,
projectOutputKeepRules,
desugarGraphDir?.resolve("currentProject").takeIf { impactedFiles == null }
)
processInputType(
// size=0
subProjectClasses,
subProjectChangedClasses,
subProjectOutputDex,
subProjectOutputKeepRules,
desugarGraphDir?.resolve("otherProjects").takeIf { impactedFiles == null }
)
processInputType(
// size=0
mixedScopeClasses,
mixedScopeChangedClasses,
mixedScopeOutputDex,
mixedScopeOutputKeepRules,
desugarGraphDir?.resolve("mixedScopes").takeIf { impactedFiles == null }
)
processInputType(
externalLibClasses,
externalLibChangedClasses,
externalLibsOutputDex,
externalLibsOutputKeepRules,
desugarGraphDir?.resolve("externalLibs").takeIf { impactedFiles == null }
)
}
private fun processClassFromInput(...) {
...
val (directoryInputs, jarInputs) =
inputFiles
.filter { it.exists() }
.partition { it.isDirectory }
//directoryInputs size=1, E:\git\Demo2\app\build\intermediates\javac\debug\classes
if (directoryInputs.isNotEmpty()) {
directoryInputs.forEach { loggerWrapper.verbose("Processing input %s", it.toString()) }
convertToDexArchive(
inputs = DirectoryBucketGroup(directoryInputs, numberOfBuckets),
outputDir = outputDir,
isIncremental = isIncremental,
bootClasspath = bootClasspathKey,
classpath = classpathKey,
changedFiles = changedFiles,
impactedFiles = impactedFiles,
desugarGraphDir = desugarGraphDir,
outputKeepRulesDir = outputKeepRules
)
}
// jarInputs size=1,E:\git\Demo2\app\build\intermediates\compile_and_runtime_not_namespaced_r_class_jar\debug\R.jar
for (input in jarInputs) {
loggerWrapper.verbose("Processing input %s", input.toString())
check(input.extension == SdkConstants.EXT_JAR) { "Expected jar, received $input" }
convertJarToDexArchive(
isIncremental = isIncremental,
jarInput = input,
outputDir = outputDir,
bootclasspath = bootClasspathKey,
classpath = classpathKey,
changedFiles = changedFiles,
impactedFiles = impactedFiles,
desugarGraphDir = desugarGraphDir,
outputKeepRulesDir = outputKeepRules
)
}
}
private fun convertToDexArchive(...) {
val dexOutputs = DexOutputs()
...
// preDexOutputFile = E:\git\Demo2\app\build\intermediates\project_dex_archive\debug\out
dexOutputs.addDex(preDexOutputFile)
...
if (useGradleWorkers) {
workerExecutor.submit(
DexWorkAction::class.java) {...}
) else {...}
}
}
class DexWorkAction @Inject constructor(private val params: DexWorkActionParams) : Runnable {
override fun run() {
try {
launchProcessing(
params,
System.out,
System.err,
MessageReceiverImpl(
params.dexSpec.dexParams.errorFormatMode,
Logging.getLogger(DexArchiveBuilderTaskDelegate::class.java)
)
)
} catch (e: Exception) {
throw BuildException(e.message, e)
}
}
}
fun launchProcessing(
dexWorkActionParams: DexWorkActionParams,
outStream: OutputStream,
errStream: OutputStream,
receiver: MessageReceiver
) {
val dexArchiveBuilder = getDexArchiveBuilder(
dexWorkActionParams,
outStream,
errStream,
receiver
)
if (dexWorkActionParams.dexSpec.isIncremental) {
processIncrementally(dexArchiveBuilder, dexWorkActionParams)
} else {
processNonIncrementally(dexArchiveBuilder, dexWorkActionParams)
}
}
private fun processNonIncrementally(...) {
...
process(
dexArchiveBuilder = dexArchiveBuilder, // D8DexArchiveBuilder
inputClassFiles = dexWorkActionParams.dexSpec.inputClassFiles,
inputFilter = { _, _ -> true },
outputPath = dexWorkActionParams.dexSpec.outputPath,
desugarGraphUpdater = desugarGraph
)
}
private fun process(...) {
// E:\git\Demo2\app\build\intermediates\javac\debug\classes
val inputRoots = inputClassFiles.bucketGroup.getRoots()
try {
Closer.create().use { closer ->
inputClassFiles.getClassFiles(filter = inputFilter, closer = closer).use {
dexArchiveBuilder.convert(it, outputPath.toPath(), desugarGraphUpdater)
}
}
} catch (ex: DexArchiveBuilderException) {
throw DexArchiveBuilderException(
"Failed to process: ${inputRoots.joinToString(", ") { it.path }}",
ex
)
}
}
final class D8DexArchiveBuilder extends DexArchiveBuilder {
public void convert(...) {
D8DiagnosticsHandler d8DiagnosticsHandler = new InterceptingDiagnosticsHandler();
try {
D8Command.Builder builder = D8Command.builder(d8DiagnosticsHandler);
AtomicInteger entryCount = new AtomicInteger();
input.forEach(
entry -> {
builder.addClassProgramData(
readAllBytes(entry), D8DiagnosticsHandler.getOrigin(entry));
entryCount.incrementAndGet();
});
if (entryCount.get() == 0) {
// nothing to do here, just return
return;
}
builder.setMode(
dexParams.getDebuggable()
? CompilationMode.DEBUG
: CompilationMode.RELEASE)
.setMinApiLevel(dexParams.getMinSdkVersion())
.setIntermediate(true)
.setOutput(
output,
dexParams.getDexPerClass()
? OutputMode.DexFilePerClassFile
: OutputMode.DexIndexed)
.setIncludeClassesChecksum(dexParams.getDebuggable());
if (dexParams.getDebuggable()) {
builder.addAssertionsConfiguration(
AssertionsConfiguration.Builder::enableAllAssertions);
}
if (dexParams.getWithDesugaring()) {
builder.addLibraryResourceProvider(
dexParams.getDesugarBootclasspath().getOrderedProvider());
builder.addClasspathResourceProvider(
dexParams.getDesugarClasspath().getOrderedProvider());
if (dexParams.getCoreLibDesugarConfig() != null) {
builder.addSpecialLibraryConfiguration(dexParams.getCoreLibDesugarConfig());
if (dexParams.getCoreLibDesugarOutputKeepRuleFile() != null) {
builder.setDesugaredLibraryKeepRuleConsumer(
new FileConsumer(
dexParams.getCoreLibDesugarOutputKeepRuleFile().toPath()));
}
}
if (desugarGraphUpdater != null) {
builder.setDesugarGraphConsumer(
new D8DesugarGraphConsumerAdapter(desugarGraphUpdater));
}
} else {
builder.setDisableDesugaring(true);
}
D8.run(builder.build(), MoreExecutors.newDirectExecutorService());
} catch (Throwable e) {
throw getExceptionToRethrow(e, d8DiagnosticsHandler);
}
}
}
每个类单独打成了一个dex,R.jar打成了一个随机串.jar,里面classes.dex就是R类。产物如下:
3.23 mergeDebugJniLibFolders
相关类:MergeSourceSetFolders.kt
,过程同mergeDebugAssets
,合并所有模块的JNI
库(Native
库,即 .so
文件),目标目录:E:\git\Demo2\app\build\intermediates\merged_jni_libs\debug\out
3.24 mergeProjectDexDebug
相关类:DexMergingTask.kt
,同mergeExtDexDebug
3.25 validateSigningDebug
相关类:ValidateSigningTask.kt
,校验签名
abstract class ValidateSigningTask : NonIncrementalTask() {
override fun doTaskAction() = when {
signingConfig.storeFile == null -> throw InvalidUserDataException(
"""Keystore file not set for signing config ${signingConfig.name}""")
isSigningConfigUsingTheDefaultDebugKeystore() ->
createDefaultDebugKeystoreIfNeeded()
signingConfig.storeFile?.isFile == true -> {
}
else -> throw InvalidUserDataException(
"""Keystore file '${signingConfig.storeFile?.absolutePath}' """
+ """not found for signing config '${signingConfig.name}'.""")
}
private fun isSigningConfigUsingTheDefaultDebugKeystore(): Boolean {
return signingConfig.name == BuilderConstants.DEBUG &&
signingConfig.keyAlias == DefaultSigningConfig.DEFAULT_ALIAS &&
signingConfig.keyPassword == DefaultSigningConfig.DEFAULT_PASSWORD &&
signingConfig.storePassword == DefaultSigningConfig.DEFAULT_PASSWORD &&
signingConfig.storeType == KeyStore.getDefaultType() &&
signingConfig.storeFile.isSameFile(defaultDebugKeystoreLocation)
}
}
- 如果签名文件为空,则抛出异常
- 如果是debug默认签名(DEBUG模式并且别名等于默认别名。。。),如果没有创建签名文件,就创建默认签名
- 最后如果签名文件存在,则接续打包流程
3.26 mergeDebugNativeLibs
相关类:MergeNativeLibsTask.kt
,合并.so
库,写入\build\intermediates\merged_native_libs\debug\out
abstract class MergeNativeLibsTask
@Inject constructor(objects: ObjectFactory) : IncrementalTask() {
override fun doFullTaskAction() {
getWorkerFacadeWithWorkers().use {
it.submit(
MergeJavaResRunnable::class.java,
MergeJavaResRunnable.Params(...),
...
)
}
}
class MergeJavaResRunnable @Inject constructor(val params: Params) : Runnable {
override fun run() {
if (!params.isIncremental) {
if (params.output.isDirectory) {
// output = E:\git\Demo2\app\build\intermediates\merged_native_libs\debug\out
FileUtils.cleanOutputDir(params.output)
} else {
FileUtils.deleteIfExists(params.output)
}
}
// cacheDir = E:\git\Demo2\app\build\intermediates\incremental\debug-mergeNativeLibs\zip-cache
FileUtils.mkdirs(params.cacheDir)
...
val inputMap = mutableMapOf<File, ScopeType>()
// 0 = E:\git\Demo2\app\build\intermediates\merged_jni_libs\debug\out
params.projectJavaRes.forEach { inputMap[it] = PROJECT}
// 0 = {File@20592} "E:\git\Demo2\commonsdk\build\intermediates\library_java_res\debug\res.jar"
// 1 = {File@20593} "E:\git\Demo2\commonsdk\build\intermediates\library_jni\debug\jni"
params.subProjectJavaRes?.forEach { inputMap[it] = SUB_PROJECTS}
// 0 = {File@20600} "C:\Users\THS\.gradle\caches\transforms-3\53d25a99ecd8d6e0b29c0c2b456b3e39\transformed\appcompat-1.3.1\jars\classes.jar"
// 1 = {File@20601} "C:\Users\THS\.gradle\caches\transforms-3\86d2306ccd347d79a8de82075a375843\transformed\constraint-layout-1.0.2\jars\classes.jar"
// ...
// 29 = {File@20629} "C:\Users\THS\.gradle\caches\transforms-3\595118fa741ac25b5c5b988aee7a8080\transformed\annotation-experimental-1.0.0\jars\classes.jar"
// 30 = {File@20630} "C:\Users\THS\.gradle\caches\transforms-3\c3afff075c244fd997a6961dbdacd262\transformed\crashsdk-3.2.2.2\jni"
params.externalLibJavaRes?.forEach { inputMap[it] = EXTERNAL_LIBRARIES}
// null
params.featureJavaRes?.forEach { inputMap[it] = InternalScope.FEATURES}
val mergeJavaResDelegate = MergeJavaResourcesDelegate(...)
mergeJavaResDelegate.run()
cacheUpdates.forEach(Runnable::run)
}
}
class MergeJavaResourcesDelegate(...) {
fun run() {
... // 过滤
saveMergeState(
IncrementalFileMerger.merge(
inputs.toList(),
output,
loadMergeState(),
PackagingUtils.getNoCompressPredicateForJavaRes(noCompress)
)
)
}
}
public final class IncrementalFileMerger {
public static IncrementalFileMergerState merge(...) {
...
List<String> inputNames =
inputs.stream()
.map(IncrementalFileMergerInput::getName)
.collect(Collectors.toList());
...
if (inputNames.equals(state.getInputNames())) {
mergeNoChangedInputs(inputs, output, state, newState, noCompressPredicate);
} else {
mergeChangedInputs(inputs, output, state, newState, noCompressPredicate);
}
}
private static void mergeChangedInputs(...) {
// 0 = "E:\git\Demo2\app\build\intermediates\merged_jni_libs\debug\out"
// ...
// 32 = "C:\Users\THS\.gradle\caches\transforms-3\c3afff075c244fd997a6961dbdacd262\transformed\crashsdk-3.2.2.2\jni"
List<String> allNames = new ArrayList<>(state.getInputNames());
// 过滤后
// 0 = "lib/armeabi/libkcy.so"
// 1 = ...
Set<String> maybeImpactedPaths = new HashSet<>();
for (String path : maybeImpactedPaths) {
...
if (changed) {
updateChangedFile(...);
}
}
}
private static void updateChangedFile(...) {
if (inputsForFile.isEmpty()) {
// No current inputs have this file, remove it.
output.remove(path);
newState.remove(path);
} else if (prevInputNames.isEmpty()) {
// No old inputs had the file, create a new one.
// com.android.build.gradle.internal.tasks.MergeJavaResourcesDelegate$run$output$1@322273d8
output.create(path, ImmutableList.copyOf(inputsForFile), compress);
newState.set(path, getInputNames(inputsForFile));
} else {
// Both new and old inputs have the file, update it.
output.update(path, prevInputNames, ImmutableList.copyOf(inputsForFile), compress);
newState.set(path, getInputNames(inputsForFile));
}
}
}
public final class MergeOutputWriters {
public void create(...) {
// path = lib/armeabi/libc++_shared.so
// f: E:\git\Demo2\app\build\intermediates\merged_native_libs\debug\out\lib\armeabi\libc++_shared.so
File f = toFile(path);
FileUtils.mkdirs(f.getParentFile());
try (FileOutputStream fos = new FileOutputStream(f)) {
ByteStreams.copy(data, fos);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
3.27 stripDebugDebugSymbols
相关类:StripDebugSymbolsTask.kt
,剥离本地库(Native Libraries
,即 .so
文件)中的调试符号(Debug Symbols
)。调试符号是编译本地代码时生成的额外信息,用于调试和故障排查,但在发布版本中通常不需要保留,因为它们会增加文件大小。过程是将build\intermediates\merged_native_libs\debug\out\lib
里面的.so
文件去掉调试符号后,拷贝到build\intermediates\stripped_native_libs\debug\out\lib
abstract class StripDebugSymbolsTask : IncrementalTask() {
override fun doFullTaskAction() {
getWorkerFacadeWithThreads(useGradleExecutor = false).use { workers ->
StripDebugSymbolsDelegate(...).run()
}
}
}
class StripDebugSymbolsDelegate(...) {
fun run() {
...
// 0 = E:\git\Demo2\commonsdk\build\intermediates\merged_native_libs\debug\out\lib
for (input in FileUtils.getAllFiles(inputDir)) {
if (input.isDirectory) {
continue
}
// lib\armeabi\libc++_shared.so
val path = FileUtils.relativePath(input, inputDir)
// E:\git\Demo2\commonsdk\build\intermediates\stripped_native_libs\debug\out\lib\armeabi\libc++_shared.so
val output = File(outputDir, path)
workers.submit(
StripDebugSymbolsRunnable::class.java,
...
)
}
}
}
private class StripDebugSymbolsRunnable @Inject constructor(val params: Params): Runnable {
override fun run() {
val exe =
params.stripToolFinder.stripToolExecutableFile(params.input, params.abi) {
UnstrippedLibs.add(params.input.name)
logger.verbose("$it Packaging it as is.")
return@stripToolExecutableFile null
}
if (exe == null || params.justCopyInput) {
// input = E:\git\Demo2\app\build\intermediates\merged_native_libs\debug\out\lib\armeabi\libc++_shared.so
// output = E:\git\Demo2\app\build\intermediates\stripped_native_libs\debug\out\lib\armeabi\libc++_shared.so
FileUtils.copyFile(params.input, params.output)
return
}
val builder = ProcessInfoBuilder()
builder.setExecutable(exe)
builder.addArgs("--strip-unneeded")
builder.addArgs("-o")
builder.addArgs(params.output.toString())
builder.addArgs(params.input.toString())
val result =
params.processExecutor.execute(
builder.createProcess(), LoggedProcessOutputHandler(logger)
)
if (result.exitValue != 0) {
UnstrippedLibs.add(params.input.name)
logger.verbose(
"Unable to strip library ${params.input.absolutePath} due to error "
+ "${result.exitValue} returned from $exe, packaging it as is."
)
FileUtils.copyFile(params.input, params.output)
}
}
}
3.28 packageDebug
相关类:PackageApplication.kt
,它是构建 APK 的最后一步,将所有编译后的资源、代码、本地库等文件打包成一个完整的 APK 文件,供安装和测试使用。最终将所有资源写入build\outputs\apk\debug\xxx.apk
,过程包括写入资源,签名,对齐。
public abstract class PackageAndroidArtifact extends NewIncrementalTask {
public void doTaskAction(@NonNull InputChanges changes) {
//
HashSet<File> changedResourceFiles = new HashSet<>();
...
getTransformationRequest()
.submit(
this,
getWorkerExecutor().noIsolation(),
IncrementalSplitterRunnable.class,
SplitterParams.class,
configure(changedResourceFiles, changes));
}
public abstract static class IncrementalSplitterRunnable implements WorkAction<SplitterParams> {
public void execute() {
try {
// E:\git\Demo2\app\build\intermediates\incremental\packageDebug\tmp\universalDebug
File incrementalDirForSplit =
new File(
params.getIncrementalFolder().get().getAsFile(),
params.getVariantOutput().get().getFullName());
// E:\git\Demo2\app\build\intermediates\incremental\packageDebug\tmp\universalDebug\zip-cache
File cacheDir = new File(incrementalDirForSplit, ZIP_DIFF_CACHE_DIR);
Map<File, String> cacheKeyMap = new HashMap<>();
addCacheKeys(cacheKeyMap, "dex", params.getDexFiles().get());
addCacheKeys(cacheKeyMap, "javaResources", params.getJavaResourceFiles().get());
addCacheKeys(cacheKeyMap, "assets", params.getAssetsFiles().get());
cacheKeyMap.put(
params.getAndroidResourcesFile().get().getAsFile(), "androidResources");
addCacheKeys(cacheKeyMap, "jniLibs", params.getJniFiles().get());
// cacheKeyMap如下
// {File@23115} "E:\git\Demo2\app\build\intermediates\compressed_assets\debug\out" -> "assets0"
// {File@23117} "E:\git\Demo2\app\build\intermediates\dex\debug\mergeProjectDexDebug" -> "dex1"
// {File@23119} "E:\git\Demo2\app\build\intermediates\processed_res\debug\out\resources-universalDebug.ap_" -> "androidResources"
// {File@23121} "E:\git\Demo2\app\build\intermediates\dex\debug\mergeExtDexDebug" -> "dex0"
// {File@23123} "E:\git\Demo2\app\build\intermediates\dex\debug\mergeLibDexDebug" -> "dex2"
// {File@23156} "E:\git\Demo2\app\build\intermediates\stripped_native_libs\debug\out" -> "jniLibs0"
KeyedFileCache cache =
new KeyedFileCache(
cacheDir, file -> Objects.requireNonNull(cacheKeyMap.get(file)));
Set<Runnable> cacheUpdates = new HashSet<>();
// {RelativeFile@23189} "RelativeFile{base=E:\git\Demo2\app\build\intermediates\dex\debug\mergeExtDexDebug\classes.dex, path=classes.dex, type=DIRECTORY}" -> {FileStatus@23190} "NEW"
// {RelativeFile@23191} "RelativeFile{base=E:\git\Demo2\app\build\intermediates\dex\debug\mergeProjectDexDebug\classes.dex, path=classes.dex, type=DIRECTORY}" -> {FileStatus@23190} "NEW"
// {RelativeFile@23192} "RelativeFile{base=E:\git\Demo2\app\build\intermediates\dex\debug\mergeLibDexDebug\classes_0.dex, path=classes_0.dex, type=DIRECTORY}" -> {FileStatus@23190} "NEW"
Map<RelativeFile, FileStatus> changedDexFiles =
IncrementalChanges.classpathToRelativeFileSet(
params.getDexFiles().get(), cache, cacheUpdates);
// size = 0
Map<RelativeFile, FileStatus> changedJavaResources =
getChangedJavaResources(params, cacheKeyMap, cache, cacheUpdates);
// {RelativeFile@23429} "RelativeFile{base=E:\git\Demo2\app\build\intermediates\processed_res\debug\out\resources-universalDebug.ap_, path=res/drawable-v21/abc_list_divider_material.xml, type=JAR}" -> {FileStatus@23190} "NEW"
// {RelativeFile@23430} "RelativeFile{base=E:\git\Demo2\app\build\intermediates\processed_res\debug\out\resources-universalDebug.ap_, path=res/drawable-xhdpi-v4/abc_text_select_handle_middle_mtrl.png, type=JAR}" -> {FileStatus@23190} "NEW"
// ...
final Map<RelativeFile, FileStatus> changedAndroidResources;
if (params.getAndroidResourcesChanged().get()) {
changedAndroidResources =
IncrementalRelativeFileSets.fromZip(
new ZipCentralDirectory(
params.getAndroidResourcesFile().get().getAsFile()),
cache,
cacheUpdates);
} else {
changedAndroidResources = ImmutableMap.of();
}
// {RelativeFile@23768} "RelativeFile{base=E:\git\Demo2\app\build\intermediates\stripped_native_libs\debug\out\lib\armeabi-v7a\libcrashsdk.so, path=lib/armeabi-v7a/libcrashsdk.so, type=DIRECTORY}" -> {FileStatus@23190} "NEW"
// {RelativeFile@23769} "RelativeFile{base=E:\git\Demo2\app\build\intermediates\stripped_native_libs\debug\out\lib\x86_64\libcrashsdk.so, path=lib/x86_64/libcrashsdk.so, type=DIRECTORY}" -> {FileStatus@23190} "NEW"
// {RelativeFile@23840} "RelativeFile{base=E:\git\Demo2\app\build\intermediates\stripped_native_libs\debug\out\lib\x86\libcrashsdk.so, path=lib/x86/libcrashsdk.so, type=DIRECTORY}" -> {FileStatus@23190} "NEW"
// {RelativeFile@23841} "RelativeFile{base=E:\git\Demo2\app\build\intermediates\stripped_native_libs\debug\out\lib\arm64-v8a\libcrashsdk.so, path=lib/arm64-v8a/libcrashsdk.so, type=DIRECTORY}" -> {FileStatus@23190} "NEW"
// {RelativeFile@23842} "RelativeFile{base=E:\git\Demo2\app\build\intermediates\stripped_native_libs\debug\out\lib\armeabi\libkcy.so, path=lib/armeabi/libkcy.so, type=DIRECTORY}" -> {FileStatus@23190} "NEW"
// {RelativeFile@23843} "RelativeFile{base=E:\git\Demo2\app\build\intermediates\stripped_native_libs\debug\out\lib\armeabi\libc++_shared.so, path=lib/armeabi/libc++_shared.so, type=DIRECTORY}" -> {FileStatus@23190} "NEW"
// {RelativeFile@23844} "RelativeFile{base=E:\git\Demo2\app\build\intermediates\stripped_native_libs\debug\out\lib\armeabi\libcrashsdk.so, path=lib/armeabi/libcrashsdk.so, type=DIRECTORY}" -> {FileStatus@23190} "NEW"
Map<RelativeFile, FileStatus> changedJniLibs =
IncrementalChanges.classpathToRelativeFileSet(
params.getJniFiles().get(), cache, cacheUpdates);
// applicationId = com.kongge.demo
// variantName = debug
// elements =
// 0 = {BuiltArtifactImpl@23809} BuiltArtifactImpl(outputFile=E:/git/Demo2/app/build/intermediates/packaged_manifests/debug/universal/AndroidManifest.xml, versionCode=1, versionName=1.0, variantOutputConfiguration=VariantOutputConfigurationImpl(isUniversal=true, filters=[]))
// 1 = {BuiltArtifactImpl@23810} BuiltArtifactImpl(outputFile=E:/git/Demo2/app/build/intermediates/packaged_manifests/debug/xxxhdpi/AndroidManifest.xml, versionCode=1, versionName=1.0, variantOutputConfiguration=VariantOutputConfigurationImpl(isUniversal=false, filters=[FilterConfiguration(filterType=DENSITY, identifier=xxxhdpi)]))
// 2 = {BuiltArtifactImpl@23811} BuiltArtifactImpl(outputFile=E:/git/Demo2/app/build/intermediates/packaged_manifests/debug/mdpi/AndroidManifest.xml, versionCode=1, versionName=1.0, variantOutputConfiguration=VariantOutputConfigurationImpl(isUniversal=false, filters=[FilterConfiguration(filterType=DENSITY, identifier=mdpi)]))
// 3 = {BuiltArtifactImpl@23812} BuiltArtifactImpl(outputFile=E:/git/Demo2/app/build/intermediates/packaged_manifests/debug/ldpi/AndroidManifest.xml, versionCode=1, versionName=1.0, variantOutputConfiguration=VariantOutputConfigurationImpl(isUniversal=false, filters=[FilterConfiguration(filterType=DENSITY, identifier=ldpi)]))
// 4 = {BuiltArtifactImpl@23813} BuiltArtifactImpl(outputFile=E:/git/Demo2/app/build/intermediates/packaged_manifests/debug/xxhdpi/AndroidManifest.xml, versionCode=1, versionName=1.0, variantOutputConfiguration=VariantOutputConfigurationImpl(isUniversal=false, filters=[FilterConfiguration(filterType=DENSITY, identifier=xxhdpi)]))
// 5 = {BuiltArtifactImpl@23814} BuiltArtifactImpl(outputFile=E:/git/Demo2/app/build/intermediates/packaged_manifests/debug/hdpi/AndroidManifest.xml, versionCode=1, versionName=1.0, variantOutputConfiguration=VariantOutputConfigurationImpl(isUniversal=false, filters=[FilterConfiguration(filterType=DENSITY, identifier=hdpi)]))
// 6 = {BuiltArtifactImpl@23815} BuiltArtifactImpl(outputFile=E:/git/Demo2/app/build/intermediates/packaged_manifests/debug/xhdpi/AndroidManifest.xml, versionCode=1, versionName=1.0, variantOutputConfiguration=VariantOutputConfigurationImpl(isUniversal=false, filters=[FilterConfiguration(filterType=DENSITY, identifier=xhdpi)]))
BuiltArtifactsImpl manifestOutputs =
new BuiltArtifactsLoaderImpl().load(params.getManifestDirectory());
doTask(...);
}
}
}
private static void doTask(...) {
...
// manifestForSplit.outputManifest = E:/git/Demo2/app/build/intermediates/packaged_manifests/debug/universal/AndroidManifest.xml
BuiltArtifact manifestForSplit =
manifestOutputs.getBuiltArtifact(
params.getVariantOutput().get().getVariantOutputConfiguration());
try (IncrementalPackager packager =
new IncrementalPackagerBuilder(
// FILE, CLEAN
params.getApkFormat().get(), params.getPackagerMode().get())
// E:\git\Demo2\app\build\outputs\apk\debug\app-universal-debug.apk
.withOutputFile(outputFile)
// 签名配置
.withSigning(
params.getSigningConfig().get().resolve(),
params.getMinSdkVersion().get(),
params.getTargetApi().getOrNull(),
dependencyData)
// Android Gradle 4.1.3
.withCreatedBy(params.getCreatedBy().get())
// TODO: allow extra metadata to be saved in the split scope to avoid
// reparsing
// these manifest files.
.withNativeLibraryPackagingMode(
// manifest = E:\git\Demo2\app\build\intermediates\packaged_manifests\debug\universal\AndroidManifest.xml
PackagingUtils.getNativeLibrariesLibrariesPackagingMode(manifest))
.withNoCompressPredicate(
PackagingUtils.getNoCompressPredicate(
// mov, manifest
params.getAaptOptionsNoCompress().get(), manifest))
// E:\git\Demo2\app\build\intermediates\incremental\packageDebug\tmp\universalDebug\zip-cache
.withIntermediateDir(incrementalDirForSplit)
// true
.withDebuggableBuild(params.getIsDebuggableBuild().get())
// size=0
.withAcceptedAbis(getAcceptedAbis(params))
// false
.withJniDebuggableBuild(params.getIsJniDebuggableBuild().get())
// APK_FLINGER
.withApkCreatorType(params.getApkCreatorType().get())
// {RelativeFile@20158} "RelativeFile{base=E:\git\Demo2\app\build\intermediates\dex\debug\mergeExtDexDebug\classes.dex, path=classes.dex, type=DIRECTORY}" -> {FileStatus@20159} "NEW"
// {RelativeFile@20160} "RelativeFile{base=E:\git\Demo2\app\build\intermediates\dex\debug\mergeProjectDexDebug\classes.dex, path=classes.dex, type=DIRECTORY}" -> {FileStatus@20159} "NEW"
// {RelativeFile@20161} "RelativeFile{base=E:\git\Demo2\app\build\intermediates\dex\debug\mergeLibDexDebug\classes_0.dex, path=classes_0.dex, type=DIRECTORY}" -> {FileStatus@20159} "NEW"
.withChangedDexFiles(changedDex)
// size = 0
.withChangedJavaResources(changedJavaResources)
// 0 = {SerializableChange@20177} SerializableChange(file=E:\git\Demo2\app\build\intermediates\compressed_assets\debug\out\assets\background_gif.gif.jar, fileStatus=NEW, normalizedPath=assets/background_gif.gif.jar)
// 1 = {SerializableChange@20178} SerializableChange(file=E:\git\Demo2\app\build\intermediates\compressed_assets\debug\out\assets\background_image.jpeg.jar, fileStatus=NEW, normalizedPath=assets/background_image.jpeg.jar)
// ...
.withChangedAssets(changedAssets)
// {RelativeFile@20295} "RelativeFile{base=E:\git\Demo2\app\build\intermediates\processed_res\debug\out\resources-universalDebug.ap_, path=res/drawable-v21/abc_list_divider_material.xml, type=JAR}" -> {FileStatus@20159} "NEW"
// {RelativeFile@20296} "RelativeFile{base=E:\git\Demo2\app\build\intermediates\processed_res\debug\out\resources-universalDebug.ap_, path=res/drawable-xhdpi-v4/abc_text_select_handle_middle_mtrl.png, type=JAR}" -> {FileStatus@20159} "NEW"
// ...
.withChangedAndroidResources(changedAndroidResources)
// {RelativeFile@20781} "RelativeFile{base=E:\git\Demo2\app\build\intermediates\stripped_native_libs\debug\out\lib\armeabi-v7a\libcrashsdk.so, path=lib/armeabi-v7a/libcrashsdk.so, type=DIRECTORY}" -> {FileStatus@20159} "NEW"
// {RelativeFile@20782} "RelativeFile{base=E:\git\Demo2\app\build\intermediates\stripped_native_libs\debug\out\lib\x86_64\libcrashsdk.so, path=lib/x86_64/libcrashsdk.so, type=DIRECTORY}" -> {FileStatus@20159} "NEW"
// ...
.withChangedNativeLibs(changedNLibs)
.build()) {
packager.updateFiles();
// try-with,调用IncrementalPackagerBuilder#close(),触发签名过程
}
}
}
public class IncrementalPackager implements Closeable {
public void updateFiles() throws IOException {
List<PackagedFileUpdate> packagedFileUpdates = new ArrayList<>();
// add dex
packagedFileUpdates.addAll(mDexRenamer.update(mChangedDexFiles));
... add resource,so
deleteFiles(packagedFileUpdates);
// assets文件压缩写入apk
updateSingleEntryJars(mChangedAssets);
addFiles(packagedFileUpdates);
}
private void addFiles(@NonNull Collection<PackagedFileUpdate> updates) throws IOException {
// 包含所有dex和so
Iterable<PackagedFileUpdate> newOrChangedNonArchiveFiles = ...;
for (PackagedFileUpdate rf : newOrChangedNonArchiveFiles) {
File out = rf.getSource().getFile();
getApkCreator().writeFile(out, rf.getName());
}
// 包含所有的res里面的资源文件
Iterable<PackagedFileUpdate> newOrChangedArchiveFiles = ...;
// 0 = E:\git\Demo2\app\build\intermediates\processed_res\debug\out\resources-universalDebug.ap_
Set<File> archives = ...;
...
// 写入res资源
for (File arch : archives) {
getApkCreator().writeZip(arch, pathNameMap::get, name -> !names.contains(name));
}
}
}
签名过程:
调用getApkCreator()
时会初始化,PackageAndroidArtifact#doTask
里面try-with
会调用IncrementalPackager#close()
方法,即所有资源写入apk
文件之后,开始签名,流程上是V1,V2,V3,V4
的顺序签名。
public class IncrementalPackager implements Closeable {
private ApkCreator getApkCreator() {
if (mApkCreator == null) {
switch (mApkCreatorType) {
case APK_Z_FILE_CREATOR:
mApkCreator = mApkCreatorFactory.make(mCreationData);
break;
case APK_FLINGER:
// 走此处
int compressionLevel = mIsDebuggableBuild ? BEST_SPEED : DEFAULT_COMPRESSION;
mApkCreator =
new ApkFlinger(mCreationData, compressionLevel, !mIsDebuggableBuild);
break;
default:
throw new RuntimeException("unexpected apkCreatorType");
}
}
return mApkCreator;
}
}
class ApkFlinger(...) {
init {
val signingOptions: SigningOptions? = creationData.signingOptions.orNull()
val innerArchive =
if (signingOptions == null) {
ZipArchive(creationData.apkPath, Zip64.Policy.FORBID)
} else {
SignedApk(...){}
}
archive =
SynchronizedArchive(
if (deterministicEntryOrder) StableArchive(innerArchive) else innerArchive
)
}
}
public class StableArchive implements Archive {
public void close() throws IOException {
bytesSources.sort(Comparator.comparing(Source::getName));
zipSources.sort(Comparator.comparing(ZipSource::getName));
for (ZipSource zipSource : zipSources) {
zipSource.getSelectedEntries().sort(Comparator.comparing(Source::getName));
}
deletedEntries.sort(Comparator.naturalOrder());
try (Archive arch = archive) {
for (String toDelete : deletedEntries) {
arch.delete(toDelete);
}
for (BytesSource source : bytesSources) {
arch.add(source);
}
for (ZipSource zipSource : zipSources) {
arch.add(zipSource);
}
}
}
}
public class SignedApk implements Archive {
public void close() throws IOException {
try {
finishSigning();
// At this point the archive has been closed.
// V4 can be done if needed.
signV4();
} finally {
signer.close();
}
}
private void finishSigning() throws IOException {
try {
finishV1();
finishV2andV3();
} finally {
if (!archive.isClosed()) {
archive.close();
}
}
}
}
四:应用
了解打包过程之后,可以在任务之间插入我们自己的操作,比如在打包完成之后,自动将apk上传到服务器发布,可以在app/build.gradle
里面添加逻辑。
tasks.whenTaskAdded { task ->
if (task.name == "assembleRelease") {
task.doLast {
println("upload apk to server")
}
}
}
这样运行./gradlew assembleRelease
任务,在assembleRelease
任务执行完成之后,就会执行println("upload apk to server")
。
或者编译自动生成了一些额外的文件,在点击Build - Clean Project
希望把这些额外的文件一起删除。
task cleanCache {
doFirst {
println("clean your cache dir!")
}
}
tasks.getByName("clean").dependsOn("cleanCache")
这样在点击编译器Build - Clean Project
时,就会先执行cleanCache
任务
> Configure project :app
> Task :app:cleanCache
clean your cache dir!
> Task :app:clean UP-TO-DATE
五:总结
了解 Android Gradle
打包过程之后,可以在多个方面发挥作用,无论是在开发、优化、问题解决还是项目管理等领域,都能带来显著的好处。上述任务分析只是个引子,还有很多细节没分析,比如资源如何编译的、代码混淆过程等等,后续再分析。