[万字长文]一文带你深入了解Android Gradle

Posted 伯努力不努力

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[万字长文]一文带你深入了解Android Gradle相关的知识,希望对你有一定的参考价值。

作为每一个android研发,相信对gradle并不陌生,android studio新建每个项目或者module都会自动生成gradle文件,AS默认也是采用Gradle作为构建工具的。

但是apk打包背后是如何gradle产生联系,以及gradle还能为我们平时的开发工作带来哪些帮助并不是都能有时间了解,那么本文就将带大家深入了解Android Gradle。

看完本文,你将了解到这些内容:
1.掌握 gradle 的基本使用
2.了解 gradle 及 android gradle plugin
3.了解 gradle 构建阶段及生命周期回调
4.掌握 Task,Transform 等概念
5.学会自定义 task,自定义 gradle 插件
6.了解 android gradle plugin 的构建流程
7.了解 android gradle plugin 的主要 task 的实现及作用

Part1 Gradle基础知识

1.gradle是什么?

gradle 是一个自动化构建工具,通过组织一系列 task 来最终完成自动化构建,所以 task 是 gradle 里最重要的概念之一,
以打包生成apk 为例,整个过程要经过资源的处理,javac 编译,dex 打包,apk 打包,签名等等步骤,每个步骤就对应到 gradle 里的一个 task。

gradle 可以使用 Groovy 或者 Kotlin DSL编写,这里我们涉及一个概念DSL ,DSL 也就是 Domain Specific Language 的简称,相对应的是 GPL (General-Purpose Language),比如 java语言,与 GPL 相比起来,DSL 使用简单,定义比较简洁,比起配置文件,DSL 又可以实现语言逻辑
对 gradle 脚本来说,通过DSL实现了简洁的定义,又有充分的语言逻辑,以 android 为例,这本身是一个函数调用,参数是一个闭包,但是这种定义方式明显要简洁很多

2.gradle中的基本概念

当我们使用每天吃饭的工具As新建一个Android工程时候,我们会看到如下的组成:

如此之多和gradle有关系的文件,那么它们分别是什么作用呢?下面带你一一进行分析

2.1 settings.gradle
settings.gradle 是负责配置项目的脚本,其对应为gradle源码中的Setting.java ,如下图所示

它的Api调用可以参考Setting Api

一般我们接触比较多的如下几个方法:

include(projectPaths)
includeFlat(projectNames)
project(projectDir)

一般在项目里见到的引用子模块的方法,就是使用 include,这样引用,子模块位于根项目的下一级

include ':app'

如果想指定子模块的位置,可以使用 project 方法获取 Project 对象,设置其 projectDir 参数

include ':app'
project(':app').projectDir = new File('./app')

效果如下:

2.2 rootproject/build.gradle
也就是project根目录下的 gradle,gradle 构建的时候,会根据 build.gradle 生成 Project 对象,Project 其实是一个接口,真正的实现类是 DefaultProject,DefaultProject源码Project Api

相信下面几个方法,大家在project的.gradle中再熟悉不过了

buildscript // 配置脚本的 classpath
allprojects // 配置项目及其子项目
respositories // 配置仓库地址,后面的依赖都会去这里配置的地址查找
dependencies // 配置项目的依赖

一个Android Project工程的标准配置

buildscript  // 配置项目的 classpath
    repositories   // 项目的仓库地址,会按顺序依次查找
        google()
        jcenter()
        mavenLocal()
    
    dependencies  // 项目的依赖
        classpath 'com.android.tools.build:gradle:4.2.1'
        classpath 'com.xx.plugin:xxplugin:0.0.1'
    


allprojects  // 子项目的配置
    repositories 
        google()
        jcenter()
        mavenLocal()
    

2.3 module/build.gradle
子项目和根项目的配置是差不多的,不过在子项目里可以看到有一个明显的区别,就是App module下会自动引入插件 apply plugin “com.android.application”,Library module 会自动引入插件 apply plugin: “com.android.library”

我们常见的子项目build.gradle的属性如下:
compileSdkVersion // 指定编译需要的 sdk 版本
defaultConfig // 指定默认的属性,会运用到所有的 variants 上
buildTypes // 一些编译属性可以在这里配置,可配置的所有属性在 这里
productFlavor // 配置项目的 flavor

2.4 依赖api
我们在gradle中依赖一个库,都会使用到implementation,api等,同时在gradle版本3.4开始,compile被implementation和api取代,provided被provided取代,各api作用如下:

为了让大家更好的理解这几个api的作用,下面举个栗子,项目里有三个模块:app,module1, module2
模块 app 中有一个类 ModuleApi
模块 module1 中有一个类 Module1Api
模块 module2 中有一个类 Module2Api

implementation 依赖
当 module1 使用 implementation 依赖 module2 时,在 app 模块中无法引用到 Module2Api 类

api 依赖
当 module1 使用 api 依赖 module2 时,在 app 模块中可以正常引用到 Module2Api 类

compileOnly 依赖
当 module1 使用 compileOnly 依赖 module2 时,在编译阶段 app 模块无法引用到 Module2Api 类,module1 中正常引用,但是在运行时会报错


同时,通过反编译 apk,可以发现 Module2Api 是没有被打包到 apk 里的

runtimeOnly 依赖
当 module1 使用 runtimeOnly 依赖 module2 时,在编译阶段,module1 也无法引用到 Module2Api

tips:日常开发中,我们经常会使用到排除依赖传递的情况,我们可以通过如下声明实现:

dependencies 
    implementation('some-library') 
    	// 排除指定模块
        exclude group: 'com.tencent.demo', module: 'native'
    

2.5 variant、type 、flavor
先来了解这几个概念的意思:
flavor 是风味的意思,可用于区分不同渠道、区分免费版和收费版。
type 是类型的意思,区分在开发过程中不同阶段使用的包比如debug、测试、灰度、生产
variant 是变体的意思,以上两个维度相交就会有多种变体,比如收费版的debug包、免费版测试包,每种变体都可以添加一些特殊配置。
一般呈现形式如下:

android
    ...
    //定义风味维度
    flavorDimensions "channel" 
    
    //声明产品风味
    productFlavors 
       //声明风味
        huawei 
            //指定风味维度
            dimension "channel"
        
        xiaomi 
            dimension "channel"
        
        ...
    


场景实践
这几个属性,在Android日常开发中,最典型的场景就是多渠道打包的场景,我们国内的App市场有很多,小米,华为,应用宝等,如果我想针对不同市场,做一些打包差异化逻辑,比如上架小米市场的apk打入小米的支付SDK,而上架华为市场的apk打入华为的支付SDK,我们就可以通过flavor来实现。具体伪代码如下:

android
    ...
    //定义风味维度
    flavorDimensions "channel" 
    
    //声明产品风味
    productFlavors 
       //声明风味
        huawei 
            //指定风味维度
            dimension "channel"
        
        xiaomi 
            dimension "channel"
        
        ...
    


这样我们就新建了2个不同的渠道,同步一下工程,你就能在 Build Variants 工具栏看到配置的相关信息。
打包不同风味的 apk:

可以通过 Gradle 工具栏来选择性的进行打包:

# 打所有风味的包
assemble
# 只打华为风味的包
assembleHuawei
# 只打小米风味的包
assembleXiaomi

当然,现实开发场景中,我们往往封装了多渠道打包签名的框架,这里只是通过最简单的栗子来帮助理解。

2.6 gradle wrapper
gradlew / gradlew.bat
这个文件用来下载特定版本的 gradle 然后执行的,避免开发者在本地再安装 gradle 的情况。好处是,如果开发者在本地安装 gradle,会碰到的问题是不同项目使用不同版本的 gradle 的问题,用 wrapper 就能很好的解决,可以在不同项目里使用不同的 gradle 版本。gradle wrapper
一般下载在 GRADLE_CACHE/wrapper/dists 目录下
gradle/wrapper/gradle-wrapper.properties
是一些 gradlewrapper 的配置,其中用的比较多的就是 distributionUrl,可以执行 gradle 的下载地址和版本
gradle/wrapper/gradle-wrapper.jar
是 gradlewrapper 运行需要的依赖包

3.gradle 生命周期及回调

gradle 构建分为三个阶段
初始化阶段
初始化阶段主要做的事情是有哪些项目需要被构建,然后为对应的项目创建 Project 对象

配置阶段
配置阶段主要做的事情是对上一步创建的项目进行配置,这时候会执行 build.gradle 脚本,并且会生成要执行的 task

执行阶段
执行阶段主要做的事情就是执行 task,进行主要的构建工作

gradle 在构建过程中,会提供一些列回调接口,方便在不同的阶段做一些事情,主要的接口有下面几个

gradle.addBuildListener(new BuildListener() 
    @Override
    void buildStarted(Gradle gradle) 
        println('构建开始')
        // 这个回调一般不会调用,因为我们注册的时机太晚,注册的时候构建已经开始了,是 gradle 内部使用的
    

    @Override
    void settingsEvaluated(Settings settings) 
        println('settings 文件解析完成')
    

    @Override
    void projectsLoaded(Gradle gradle) 
        println('项目加载完成')
        gradle.rootProject.subprojects.each  pro ->
            pro.beforeEvaluate 
                println("$pro.name 项目配置之前调用")
            
            pro.afterEvaluate
                println("$pro.name 项目配置之后调用")
            
        
    

    @Override
    void projectsEvaluated(Gradle gradle) 
        println('项目解析完成')
    

    @Override
    void buildFinished(BuildResult result) 
        println('构建完成')
    
)

gradle.taskGraph.whenReady 
    println("task 图构建完成")

gradle.taskGraph.beforeTask 
    println("每个 task 执行前会调这个接口")

gradle.taskGraph.afterTask 
    println("每个 task 执行完成会调这个接口")

使用场景,获取构建各阶段耗时情况
我们可以通过这个listener去打印每一个task的构建耗时,方便我们筛选出构建过程中的耗时任务。在beforeTask回调时记录开始时间,在afterTask回调时记录结束时间。
也可以通过在settings.gradle脚本文件的开头添加下面的代码,再执行任意构建任务,你就可以看到各阶段、各任务的耗时情况。

long beginOfSetting = System.currentTimeMillis()

gradle.projectsLoaded 
  println '初始化阶段,耗时:' + (System.currentTimeMillis() - beginOfSetting) + 'ms'


def beginOfConfig
def configHasBegin = false
def beginOfProjectConfig = new HashMap()
gradle.beforeProject  project ->
  if (!configHasBegin) 
    configHasBegin = true
    beginOfConfig = System.currentTimeMillis()
  
  beginOfProjectConfig.put(project, System.currentTimeMillis())

gradle.afterProject  project ->
  def begin = beginOfProjectConfig.get(project)
  println '配置阶段,' + project + '耗时:' + (System.currentTimeMillis() - begin) + 'ms'

def beginOfProjectExcute
gradle.taskGraph.whenReady 
  println '配置阶段,总共耗时:' + (System.currentTimeMillis() - beginOfConfig) + 'ms'
  beginOfProjectExcute = System.currentTimeMillis()

gradle.taskGraph.beforeTask  task ->
  task.doFirst 
    task.ext.beginOfTask = System.currentTimeMillis()
  
  task.doLast 
    println '执行阶段,' + task + '耗时:' + (System.currentTimeMillis() - task.beginOfTask) + 'ms'
  

gradle.buildFinished 
  println '执行阶段,耗时:' + (System.currentTimeMillis() - beginOfProjectExcute) + 'ms'

效果如下:

4.Task

每次构建(build)至少由一个project构成,一个project 由一到多个task构成。每个task代表了构建过程当中的一个原子性操作,比如编译,打包,生成javadoc,发布等等这些操作。

gradle : 一个 project 包含多个 task,一个 task 包含多个 Action

project 
                  -- task1 (Action1、Action2...)
                  -- task2 (Action1、Action2...)
                  -- ... 

4.1 自定义 Task
方式1:

 task 任务的名字 
    //do some things
 

build.gradle中进行声明:

//定义 task , 名字 hello 
task hello
    println "hello world"

在终端运行 gradle 命令

//执行 hello task
./gradlew hello

方式2:

tasks.create("hello2")
    doFirst 
        println "hello2+++++"
    

当然上面2种方式实际开发中很少用到,最常使用以下这样的方式:
代码中新建一个task类继承DefaultTask

//伪代码
open class ProjectDependencyGraphGeneratorTask : DefaultTask() 
    @TaskAction
    fun run() 
        File(outputDirectory, projectGenerator.outputFileNameDot).writeText(graph.toString())

        val graphviz = Graphviz.fromGraph(graph)

        projectGenerator.outputFormats.forEach 
            graphviz.render(it).toFile(File(outputDirectory, projectGenerator.outputFileName))
        
    

在自定义plugin中将这个task进行注册

//伪代码
open class DependencyGraphGeneratorPlugin : Plugin<Project> 
  override fun apply(project: Project) 
     project.tasks.register(projectGenerator.gradleTaskName, ProjectDependencyGraphGeneratorTask::class.java)
  

4.2 Task依赖关系和排序
我们在自定义task时候,往往需要定义几个task的依赖关系,常用涉及task依赖和顺序的api如下:

Task.dependsOn
Task.finalizedBy

Task.dependsOn
Task A ‘dependsOn’ task B,意思是如果task B 没有完成的话, task A不成执行它的任务.
例如:你必须先穿上袜子后,才能穿鞋

task putOnSocks 
    doLast 
        println "Putting on Socks."
    


task putOnShoes 
    dependsOn "putOnSocks"
    doLast 
        println "Putting on Shoes."
    


Task.finalizedBy
Task A finalizedBy task B是如果每次执行Task A,Task B 都会在其之后执行.
例如:每次吃完早餐后,要刷牙

task eatBreakfast 
    finalizedBy "brushYourTeeth"
    doLast
        println "Om nom nom breakfast!"
    


task brushYourTeeth 
    doLast 
        println "Brushie Brushie Brushie."
    

5.Gradle Plugin Transform

android gradle plugin 提供了 transform api 用来在 .class to dex 过程中对 class 进行处理,可以理解为一种特殊的 Task,因为 transform 最终也会转化为 Task 去执行
要实现 transform 需要继承 com.android.build.api.transform.Transform 并实现其方法,实现了 Transform 以后,要想应用,就调用 project.android.registerTransform()

public class MyTransform extends Transform 
    @Override
    public String getName() 
        // 返回 transform 的名称,最终的名称会是 transformClassesWithMyTransformForDebug 这种形式   
        return "MyTransform";
    

    @Override
    public Set<QualifiedContent.ContentType> getInputTypes() 
        /**
        返回需要处理的数据类型 有 下面几种类型可选
        public static final Set<ContentType> CONTENT_CLASS = ImmutableSet.of(CLASSES);
        public static final Set<ContentType> CONTENT_JARS = ImmutableSet.of(CLASSES, RESOURCES);
        public static final Set<ContentType> CONTENT_RESOURCES = ImmutableSet.of(RESOURCES);
        public static final Set<ContentType> CONTENT_NATIVE_LIBS = ImmutableSet.of(NATIVE_LIBS);
        public static final Set<ContentType> CONTENT_DEX = ImmutableSet.of(ExtendedContentType.DEX);
        public static final Set<ContentType> DATA_BINDING_ARTIFACT = ImmutableSet.of(ExtendedContentType.DATA_BINDING);
        */
        return TransformManager.CONTENT_CLASS;
    

    @Override
    public Set<? super QualifiedContent.Scope> getScopes() 
        /**
        返回需要处理内容的范围,有下面几种类型
        PROJECT(1), 只处理项目的内容
        SUB_PROJECTS(4), 只处理子项目
        EXTERNAL_LIBRARIES(16), 只处理外部库
        TESTED_CODE(32), 只处理当前 variant 对应的测试代码
        PROVIDED_ONLY(64), 处理依赖
        @Deprecated
        PROJECT_LOCAL_DEPS(2),
        @Deprecated
        SUB_PROJECTS_LOCAL_DEPS(8);
        */
        return Sets.immutableEnumSet(QualifiedContent.Scope.PROJECT);
    

    @Override
    public boolean isIncremental() 
        // 是否增量,如果返回 true,TransformInput 会包括一份修改的文件列表,返回 false,会进行全量编译,删除上一次的输出内容
        return false;
    

    @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException 
        // 在这里处理 class
        super.transform(transformInvocation)
        // 在 transform 里,如果没有任何修改,也要把 input 的内容输出到 output,否则会报错
        for (TransformInput input : transformInvocation.inputs) 
            input.directoryInputs.each  dir ->
                // 获取对应的输出目录
                File output = transformInvocation.outputProvider.getContentLocation(dir.name, dir.contentTypes, dir.scopes, Format.DIRECTORY)
                dir.changedFiles // 增量模式下修改的文件
                dir.file // 获取输入的目录
                FileUtils.copyDirectory(dir.file, output) // input 内容输出到 output
            
            input.jarInputs.each  jar ->
                // 获取对应的输出 jar
                File output = transformInvocation.outputProvider.getContentLocation(jar.name, jar.contentTypes, jar.scopes, Format.JAR)
                jar.file // 获取输入的 jar 文件
                FileUtils.copyFile(jar.file, output) // input 内容输出到 output
            
        
    


// 注册 transform
android.registerTransform(new MyTransform())

在 transform 中的处理,一般会涉及到 class 文件的修改,操纵字节码的工具主要为 javassist 和 asm , 二者性能对比如下:

总体来说ASM的性能会好些,建议使用ASM,至于字节码相关内容本文不展开讲解。
一般自定义transform的使用场景如下:
1)通过字节码插桩进行无痕埋点
2)一些插件化,热修复场景,通过字节码插桩,生成代码
3)统计项目中方法耗时
4)一些router框架用于生成路由表或者服务的关系表

6.自定义plugin

6.1 创建 Plugin
我们在项目中看到的如下代码:

apply plugin: 'com.android.application'

这里的 'com.android.application’就是一个自定义插件,我们也可以实现自定义插件,对于Android研发,我们已经可以使用kotlin来编写插件的代码,不需要再去了解groovy。自定义方法很简单,只需要继承Plugin类,实现apply方法

class FastPlugin : Plugin<Project> 
    override fun apply(project: Project) 
        //处理的你的逻辑
    

当开发者在build.gradle中使用apply plugin引入我们的插件时,上述的apply(project: Project) 方法会被触发

6.2 创建插件的 task
再定义一个 task 类 MyTask,继承自 DefaultTask,简单的输出一些信息

class MyTask : DefaultTask 
    @TaskAction
    fun doAction()
        println("my task run")
    

然后在 plugin 中注册这个 task即可

class FastPlugin : Plugin<Project> 
    override fun apply(project: Project) 
        println("apply my plugin")
        project.tasks.register("mytask", MyTask::class.java)
    

6.3 创建插件的transform
自定义transform继承Transform类

class ClassTransform : Transform() 
    ......
    override fun transform(transformInvocation: TransformInvocation?) 
        //处理相关逻辑
    

在自定义plugin中对Transform进行注册

class TransformPlugin implements Plugin<Project> 
    @Override
    void apply(Project project) 
        project.android.registerTransform(new ClassTransform(project))
    

6.4 处理增量编译
我们自定义的Transform,想要拥有良好的性能,支持增量编译是必须的选项,覆写以下方法,返回true,即代表支持增量编译

  override fun isIncremental(): Boolean 
        return true
    

虽然开启了增量编译,但也并非每次编译过程都是支持增量的,毕竟一次clean build完全没有增量的基础,所以,我们需要检查当前编译是否是增量编译。

如果不是增量编译,则清空output目录,然后按照前面的方式,逐个class/jar处理
如果是增量编译,则要检查每个文件的Status,Status分四种,并且对这四种文件的操作也不尽相同

NOTCHANGED: 当前文件不需处理,甚至复制操作都不用;
ADDED、CHANGED: 正常处理,输出给下一个任务;
REMOVED: 移除outputProvider获取路径对应的文件。

支持增量编译的Transform,都需要对以上4中状态进行处理,以下是处理的伪代码:

fun transform(transformInvocation: TransformInvocation) 
        val inputs = transformInvocation.inputs
        val outputProvider = transformInvocation.outputProvider
        val isIncremental = transformInvocation.isIncremental
        //如果非增量,则清空旧的输出内容
        if (!isIncremental) 
            outputProvider.deleteAll()
        
        for (input in inputs) 
            for (jarInput in input.jarInputs) 
                val status = jarInput.status
                val dest = outputProvider.getContentLocation(
                    jarInput.name,
                    jarInput.contentTypes,
                    jarInput.scopes,
                    Format.JAR
                )
                if (isIncremental) 
                    when (status) 
                        Status.NOTCHANGED -> 
                        
                        Status.ADDED, Status.CHANGED -> 
                        
                        Status.REMOVED -> if (dest.exists()) 
                            FileUtils.forceDelete(dest)
                        
                    
                 else 
                    transformJar(jarInput.file, dest, status)
                
            
            for (directoryInput in input.directoryInputs) 
                val dest = outputProvider.getContentLocation(
                    directoryInput.name,
                    directoryInput.contentTypes, directoryInput.scopes,
                    Format.DIRECTORY
                )
                FileUtils.forceMkdir(dest)
                if (isIncremental) 
                    val srcDirPath = directoryInput.file.absolutePath
                    val destDirPath = dest.absolutePath
                    val fileStatusMap = directoryInput.changedFiles
                    for ((inputFile, status) in fileStatusMap) 
                        val destFilePath = inputFile.absolutePath.replace(srcDirPath, destDirPath)
                        val destFile = File(destFilePath)
                        when (status) 
                            Status.NOTCHANGED -> 
                            
                            Status.REMOVED -> if (destFile.exists()) 
                                FileUtils.forceDelete(destFile)
                            
                            Status.ADDED, Status.CHANGED -> 
                            
                        
                    
                 else 
                    transformDir(directoryInput.file, dest)
                
            
        
    

Part2 Android构建主要task分析

1.Android 打包流程


上面这张图是Android官方的 apk 的构建流程图,这些流程都是通过一个个task顺序执行而达到最终产生一个apk的目的。

我们可以通过以下命令行,输出打包apk所需要的所有task

./gradlew android-gradle-plugin-source:assembleDebug --console=plain

可以得到如下的输出结果

:android-gradle-plugin-source:preBuild UP-TO-DATE
:android-gradle-plugin-source:preDebugBuild
:android-gradle-plugin-source:compileDebugAidl
:android-gradle-plugin-source:compileDebugRenderscript
:android-gradle-plugin-source:checkDebugManifest
:android-gradle-plugin-source:generateDebugBuildConfig
:android-gradle-plugin-source:prepareLintJar UP-TO-DATE
:android-gradle-plugin-source:generateDebugResValues
:android-gradle-plugin-source:generateDebugResources
:android-gradle-plugin-source:mergeDebugResources
......

2.Android 打包各task分析

apk构建过程中,所有的task注册逻辑都在TaskManager和ApplicationTaskManager,对应的方法分别为TaskManager.createTasksBeforeEvaluate()和ApplicationTaskManager.createTasksForVariantScope()

在apk构建过程中,我们一般比较关心的是资源的处理,以及 dex 的处理,涉及的task有如下几个:
generateDebugBuildConfig
processDebugManifest
mergeDebugResources
processDebugResources
transformClassesWithDexBuilderForDebug

下面将对这几个task的流程进行分析

2.1 generateDebugBuildConfig

task实现类:GenerateBuildConfig
在 GenerateBuildConfig 中,主要生成代码的步骤如下:
1)生成 BuildConfigGenerator
2)添加默认的属性,包括 DEBUG,APPLICATION_ID,FLAVOR,VERSION_CODE,VERSION_NAME等
3)添加自定义属性
4)调用 JavaWriter 生成 BuildConfig.java 文件

2.2 generateDebugBuildConfig
task实现类:MergeResources

aapt2将原先的资源编译打包过程拆分成了两部分,即编译和链接,generateDebugBuildConfig处理的是编译部分的工作。
相比以前的aapt,支持单独编译修改过的资源文件,然后和未修改的文件进行链接即可。而在aapt中,只要一个文件发生改变,都要进行全量编译,编译性能提升很多。

aapt2编译流程如下:

(此图片来自于互联网)

2.3 processDebugResources
task实现类:ProcessAndroidResources

该task主要是调用aapt2对于已经进行编译的资源进行link链接,关于 aapt2 的 compile 和 link 参数,可以参考这里

2.4 processDebugManifest
task实现类:MergeManifests

MergeManifests主要职责就是合并 mainfest,包括 module 和 flavor 里的,整个过程通过 MergingReport,ManifestMerger2 和 XmlDocument 进行。可以归纳为如下几个步骤:
1)获取依赖库的 manifest 信息,用 LoadedManifestInfo 标识
2)获取主 module 的 manifest 信息
3)替换主 module 的 Manifest 中定义的某些属性,替换成 gradle 中定义的属性 例如: package, version_code, version_name, min_sdk_versin 等等

最终的 Manifest 文件保存在 build/intermediates/manifest/fullxxx/AndroidManifest.xml 中

2.5 transformClassesWithDexBuilderForDebug
task实现类:DexArchiveBuilderTask
在 DexArchiveBuilderTask 中,对 class 的处理分为两种方式,一种是对 目录下的 class 进行处理,一种是对 .jar 里的 class 进行处理。.jar 中的 class 一般来说都是依赖库,基本上不会改变,gradle 在这里做了一个缓存。

判断目录下的 class 是否新增或者修改过
调用 DexArchiveBuilder.build 去处理修改过的 class
DexArchiveBuilder 有两个子类,D8DexArchiveBuilder 和 DxDexArchiveBuilder,分别是调用 d8 和 dx 去打 dex

文章引用:
Android官方构建文档
aapt2 资源 compile 过程

以上是关于[万字长文]一文带你深入了解Android Gradle的主要内容,如果未能解决你的问题,请参考以下文章

你真的了解http,https吗?万字长文带你深入了解http!

(建议收藏)万字长文,带你一文吃透 Linux 提权

Linux疑难杂症解决方案100篇(十五)-万字长文带你深入Linux 内核学习:环境搭建和内核编译

Linux疑难杂症解决方案100篇(十五)-万字长文带你深入Linux 内核学习:环境搭建和内核编译

万字长文,一文读懂Linux的常规操作(墙裂建议收藏)

预训练模型需要知道的知识点总结,万字长文带你深入学习(建议收藏)