Android打包流程Gradle Plugin 主要 Task 分析

Android打包流程Gradle Plugin 主要 Task 分析

一、Android 打包流程

官方流程图:


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

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

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

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

以 Task 的维度来看 apk 的打包,则分为以下流程:

```

:android-gradle-plugin-source:preBuild UP-TO-DATE

:android-gradle-plugin-source:preDebugBuild

:android-gradle-plugin-source:compileDebugAidl

:android-gradle-plugin-source:compileDebugRenderscript

:android-gradle-plugin-source:checkDebugManifest

:android-gradle-plugin-source:generateDebugBuildConfig

:android-gradle-plugin-source:prepareLintJar UP-TO-DATE

:android-gradle-plugin-source:generateDebugResValues

:android-gradle-plugin-source:generateDebugResources

:android-gradle-plugin-source:mergeDebugResources

:android-gradle-plugin-source:createDebugCompatibleScreenManifests

:android-gradle-plugin-source:processDebugManifest

:android-gradle-plugin-source:splitsDiscoveryTaskDebug

:android-gradle-plugin-source:processDebugResources

:android-gradle-plugin-source:generateDebugSources

:android-gradle-plugin-source:javaPreCompileDebug

:android-gradle-plugin-source:compileDebugJavaWithJavac

:android-gradle-plugin-source:compileDebugNdk NO-SOURCE

:android-gradle-plugin-source:compileDebugSources

:android-gradle-plugin-source:mergeDebugShaders

:android-gradle-plugin-source:compileDebugShaders

:android-gradle-plugin-source:generateDebugAssets

:android-gradle-plugin-source:mergeDebugAssets

:android-gradle-plugin-source:transformClassesWithDexBuilderForDebug

:android-gradle-plugin-source:transformDexArchiveWithExternalLibsDexMergerForDebug

:android-gradle-plugin-source:transformDexArchiveWithDexMergerForDebug

:android-gradle-plugin-source:transformClassesWithDexBuilderForDebug

:android-gradle-plugin-source:transformDexArchiveWithExternalLibsDexMergerForDebug

:android-gradle-plugin-source:transformDexArchiveWithDexMergerForDebug

:android-gradle-plugin-source:mergeDebugJniLibFolders

:android-gradle-plugin-source:transformNativeLibsWithMergeJniLibsForDebug

:android-gradle-plugin-source:transformNativeLibsWithStripDebugSymbolForDebug

:android-gradle-plugin-source:processDebugJavaRes NO-SOURCE

:android-gradle-plugin-source:transformResourcesWithMergeJavaResForDebug

:android-gradle-plugin-source:validateSigningDebug

:android-gradle-plugin-source:packageDebug

:android-gradle-plugin-source:assembleDebug

```

这些Task的对应实现类和作用如下表所示:

二、Task简单介绍

我们的所有Gradle的构建工作都是由Task组合完成的,它可以帮助我们处理很多工作。每次构建(build)至少由一个project构成,一个project由一个至多个task构成。每个task代表了构建过程当中的一个原子性操作,比如编译、打包、发布等等这些操作。在Gradle环境下可以通过命令./gradlew tasks 查看当前工程所有的task。

Gradle 为我们提供了很多默认的task来进行使用,下面是Gradle 官网文档上对copy Task 的使用提供的例子。除了copy,还有delete、upload、zip等许多使用的task。这些基础task可以用来解决插件资源复制等问题。

官方网站:https://docs.gradle.org/current/dsl/org.gradle.api.Task.html#N18D13

官网讲的很详细,我这边简要介绍一下。在 gradle plugin 中的 Task 主要有三种,一种是普通的 task,一种是增量 task,一种是 transform。

 1.   Task的创建和配置

Task创建主要是两种方式:直接使用task函数创建或者通过task容器TaskContainer对象的create()方法进行创建。

```

task hello{

}

this.task.create('hello'){

}

```

Task 配置:上面两种创建方式其实都有对应的重载方法,可以传入具体的配置参数来进行Task初始化配置。

2.Task的执行

当执行一个Task的时候,其实就是执行其拥有的actions列表,这个列表保存在Task对象实例中的actions成员变量中,其类型就是一个List。Task本质上又是由一组被顺序执行的Action对象构成,Action其实是一段代码块,类似与Java中的方法。这里主要介绍Task创建Action的两个方法,doFirst与doLast。doFirst{} 可以使代码在Gradle 的执行阶段中Task之前执行,而doLast{}则恰恰相反,是在Task之后执行。

3.Task的执行顺序

a.dependsOn 是task 配置参数之一,主要作用就是为task 添加依赖task ,保证task 之间的执行顺序。

A-->B-->C

b.TaskInputs TaskOutputs

TaskInputs:

Task的输入类,参数可以接收为任意对象以及文件、文件夹。 

TaskOutputs:

TaskOutputs files ( );

TaskOutputs file ( );

TaskOutputs dir ( );

Task的输出类,只接收文件类型。

c.API指定执行顺序

Task的shouldRunAfter 方法和mustRunAfter方法可以控制一个Task应该或者一定在某个Task之后执行。通过这种方式可以在某些情况下控制任务的执行顺序,而不是通过强依赖的方式。

taskB.shouldRunAfter(taskA)表示taskB应该在taskA执行之后执行,这里并不是强制的,所以有可能任务顺序并不会按预设的执行。

taskB.mustRunAfter(taskA)表示taskB必须在taskA执行之后执行,执行任务的顺序是确认的。

4.挂接自定义Task到构建过程中

Gradle 的生命周期:

A. 初始化阶段(Initialization)

初始化阶段gradle会去解析项目根工程中setting.gradle中的include信息,确定哪些工程加入构建。 

B. 配置阶段(Configuration)

配置阶段将解析所有工程的build.gradle脚本,配置project对象,创建、配置task等相关信息。

C. 执行阶段(Execution)

根据具体的gradle命令,执行对应相关的task以及其依赖的task。

而实际项目中我们将要挂接的正是gradle的执行阶段,因为只有在配置阶段完成,执行阶段gradle才会去执行系统默认task 以及自定义task 。project为我们提供了这样的方法project.afterEvaluate{}。

project.afterEvaluate{} 在配置完成后,可以保证获取到所有的task,包括系统默认执行的task,这样就可以将我们自定义的task 通过顺序执行指定,挂接到构建过程中。

后面我们有个例子详细解读。

5.Task的启用与禁用

Task中有个enabled属性,用于启用和禁用任务,默认是true,表示启用,设置为false则禁止该任务,在执行阶段该任务会被跳过。在实际项目中是很使用的一个技巧,如提升build编译速度,禁止一些测试相关的Task,从而缩短执行时间。

hello.enabled=false

三、重点Task分析

官方网站:https://android.googlesource.com/platform/tools/base/+/refs/tags/gradle_3.0.0/build-system/gradle-core/src/main/java/com/android/build/gradle/tasks/GenerateBuildConfig.java

我们把这几个重点Task拿出来分析,因为这几个代表了 gradle 自动生成代码,资源的处理,以及 dex 的处理,算是 apk 打包过程中比较重要的几环。

generateDebugBuildConfig ----生成BuildConfig.java

processDebugManifest ----合并Manifest文件

mergeDebugResources ----合并资源文件

processDebugResources ---aapt打包资源

transformClassesWithDexBuilderForDebug ----class打包dex

transformDexArchiveWithExternalLibsDexMergerForDebug ----打包三方库的dex

transformDexArchiveWithDexMergerForDebug ----打包最终的dex

3.1 generateDebugBuildConfig

3.1.1 实现类

GenerateBuildConfig

3.1.2 代码调用链路

GenerateBuildConfig.generate -> BuildConfigGenerator.generate -> JavaWriter

3.1.3 主要代码分析

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

生成 BuildConfigGenerator

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

添加自定义属性

调用 JavaWriter 生成 BuildConfig.java 文件

3.2 mergeDebugResources

3.2.1 实现类

MergeResources

3.2.2 调用链路

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

3.2.3 代码分析

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

isIncremental

// 说明 Task 支持增量

    protected boolean isIncremental() {

        return true;

    }

doFullTaskAction

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

// MergeResources.doFullTaskAction()

List<ResourceSet> resourceSets = getConfiguredResourceSets(preprocessor);

创建 ResourceMerger

// MergeResources.doFullTaskAction()

ResourceMerger merger = new ResourceMerger(minSdk);

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

3.3 processDebugResources

3.3.1 实现类

ProcessAndroidResources


3.3.2 调用链路

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

3.3.3 代码分析

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

doFullTaskAction

这个里面代码虽然多,但是主要的逻辑比较简单,就是调用 aapt2 link 去生成资源包。

这里会处理 splits apk 相关的内容,关于 splits apk 具体可以查看 splits apk,简单来说,就是可以按照屏幕分辨率,abis 来生成不同的 apk,从而让特定用户的安装包变小。

3.4 processDebugManifest

3.4.1 实现类

MergeManifests


3.4.2 调用链路

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

3.4.3 代码分析

MergeManifests 也是继承了 IncrementalTask,但是没有实现 isIncremental,所以只看其 doFullTaskAction 即可。

这个 task 功能主要是合并 mainfest,包括 module 和 flavor 里的,整个过程通过 MergingReport,ManifestMerger2 和 XmlDocument 进行。

这里直接看 ManifestMerger2.merge() 的 merge 过程 。 主要有几个步骤:

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

获取主 module 的 manifest 信息,替换主 module 的 Manifest 中定义的某些属性,替换成 gradle 中定义的属性 例如: package, version_code, version_name, min_sdk_versin 等等

3.5 transformClassesWithDexBuilderForDebug

3.5.1 实现类

DexArchiveBuilderTransform


3.5.2 调用链路

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

3.5.4 主要代码分析

在 DexArchiveBuilderTransform 中,对 class 的处理分为两种方式,一种是对 目录下的 class 进行处理,一种是对 .jar 里的 class 进行处理。

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

convertJarToDexArchive 处理 jar

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

四、插件化框架Task Hook

举例:滴滴插件化框架学习笔记之virtualapk-gradle-plugin

https://juejin.im/post/6847902216590721038

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349