Android Gradle学习(八)- gradle任务解析

338 阅读20分钟

一:前言

  1. gradle是如何构建apk
  2. 各个Task做了什么操作
  3. 了解了构建过程,我们可以做什么

二:构建./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

image.png

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

小结

  1. 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 对于专注于图像处理、计算摄影或计算机视觉的应用来说尤其有用。

参考: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文件生成逻辑

  1. 读取配置
  2. 清空目录项目根目录\build\generated\source\buildConfig\release
  3. 创建BuildConfig.java文件,路径项目根目录\build\generated\source\buildConfig\release\com\kongge\demo\BuildConfig.java
  4. 使用JavaWriter将类数据写入BuildConfig.java文件

BuildConfig.java文件用途

  1. 定义:build.gradle中定义字段名,字段类型和值,例如buildConfigField "String", "APP_ID", '"xxxx"'
  2. 使用:BuildConfig.APP_ID
  3. 范围:build.gradledefaultConfigbuildTypesproductFlavors等都可以使用
// 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  
}

小结

  1. build.gradle中新增注解处理器annotationProcessor 'com.alibaba:arouter-compiler:1.2.1'
  2. 获取所有处理器配置:processorClasspath(List)
    1. SerializableArtifact(displayName=arouter-compiler-1.2.1.jar
    2. SerializableArtifact(displayName=arouter-annotation-1.0.6.jar
    3. SerializableArtifact(displayName=auto-service-1.0-rc2.jar
    4. SerializableArtifact(displayName=javapoet-1.7.0.jar
    5. SerializableArtifact(displayName=commons-lang3-3.4.jar
    6. SerializableArtifact(displayName=commons-collections4-4.1.jar
    7. SerializableArtifact(displayName=fastjson-1.2.48.jar
    8. SerializableArtifact(displayName=auto-common-0.3.jar
    9. SerializableArtifact(displayName=guava-18.0.jar
  3. 查找注解处理器:从processorClasspath处理器列表中,遍历各个jar文件中是否包含"META-INF/services/javax.annotation.processing.Processor"文件,如果包含则是注解处理器,汇总到annotationProcessors(LinkedHashMap)
    1. "arouter-compiler-1.2.1.jar (com.alibaba:arouter-compiler:1.2.1)" -> {Boolean@15280} false
    2. "auto-service-1.0-rc2.jar (com.google.auto.service:auto-service:1.0-rc2)" -> {Boolean@15280} false
  4. 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)
        }
    }
}

小结

  1. 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")
  2. generateDebugResValues读取build.gradle配置
  3. 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

  1. 读取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'  
        }  
    }
}
  1. 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))
    }
}

小结

  1. 配置src/main/res/navigation/nav_graph.xml,参考导航组件
  2. ExtractDeepLinksTask.kt遍历res/navigation里面的文件,例如nav_graph.xml,解析里面的标签,fragmentdeepLink
  3. 将解析的数据写入文件navigation.json,路径为:E:\git\Demo2\app\build\intermediates\navigation_json\debug\navigation.json

3.8 mergeDebugResources

合并资源,将res目录下所有资源拷贝一份到build/intermediates/packaged_res/debug,并生成values.xmlmerge.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*/));
        }
    }
}

image.png

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"

小结

  1. res目录下所有资源拷贝一份到build/intermediates/packaged_res/debug
  2. 生成values.xmlmerge.xml文件
  3. 使用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;
    }
    
}

小结

  1. ProcessLibraryManifest.kt遍历各个LibraryAndroidManifest.xml,解析并检查versionCodeminSdk等信息,写入build\intermediates\merged_manifest\debug\out\AndroidManifest.xml
  2. 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>标签,然后分别写入xxxdpixhdp等不同目录中,比如E:\git\Demo2\app\build\intermediates\packaged_manifests\debug\mdpi\AndroidManifest.xml

image.png

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

image.png

小结

  1. 根据split生成不同分辨率的资源链接文件
  2. 生成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()  
                    )  
                )  
            }  
        }  
    }
}

image.png

小结

  1. 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属性:

image.png

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")  
        }  
    }
}

image.png

image.png

3.21 mergeExtDexDebugmergeLibDexDebug

相关类: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);
    }
}

image.png

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类。产物如下: image.png

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

image.png

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

image.png

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 打包过程之后,可以在多个方面发挥作用,无论是在开发、优化、问题解决还是项目管理等领域,都能带来显著的好处。上述任务分析只是个引子,还有很多细节没分析,比如资源如何编译的、代码混淆过程等等,后续再分析。