Andorid&Kotlin编译速度原理剖析(上)

这一过程中使用到的工具是aidl.exe,位于android-sdk/tools目录下。 Android接口定义语言,Android提供的IPC (Inter Process Communication,进程间通信)的一种独特实现。

这个阶段处理.aidl文件,生成对应的.java文件。如果在项目没有使用到aidl文件。

3、javac:生成.class文件

通过Java Compiler 编译项目中所有的Java代码,包括R.java、.aidl文件生成的.java文件、Java源文件,生成.class文件。在对应的build下可以找到相关的代码。

4、dex:转化.class成dex文件

dx工具位于android-sdk/tools 目录下,通过它生成可供Android系统虚拟机执行的classes.dex文件。在build下可以找到相应的代码,直接使用dex命令来进行转化。这个阶段任何第三方的libraries和.class文件都会被转换成.dex文件。dx工具的主要工作是将Java字节码转成成Dalvik字节码、压缩常量池、消除冗余信息等。

//D:\test.dex 是dex文件输出目录

//D:\test是存放class文件目录dx --dex --output=D:\test.dex D:\test

复制代码

5、apkbuilder:生成apk包

打包的工具apkbuilder位于 android-sdk/tools目录下。apkbuilder为一个脚本文件,实际调用的是android-sdk/tools/lib/sdklib.jar文件中的com.android.sdklib.build.ApkbuilderMain类。所有没有编译的资源(如 res/raw、images等)、Other Resources(assets文件)、编译过的资源 、.dex文件 、resources.arsc 和 AndroidManifest.xml 都会被apkbuilder工具打包到最终的.apk文件中。

6、jarsigner:对APK进行签名

一旦apk文件生成,它必须被签名才能被安装在设备上。在开发过程中,主要用到的就是两种签名的keystore。一种是用于调试的debug.keystore,它主要用于调试。另一种就是用于发布正式版本的keystore。

7、zipalign:签名对齐

如果你发布的apk是正式版的话,就必须对APK进行对齐处理,用到的工具是zipalign,它位于android-sdk/tools目录下。

Zipalign是一个android平台上整理APK文件的工具,它对apk中未压缩的数据进行4字节对齐,对齐的主要过程是将APK包中所有的资源文件距离文件起始偏移为4字节整数倍,对齐后就可以使用mmap函数读取文件,可以像读取内存一样对普通文件进行操作。如果没有4字节对齐,就必须显式的读取,这样比较缓慢并且会耗费额外的内存。

GradleTask

我们点击Run‘app’时gradle是如何工作的,在Build窗口可以看到详细的Task日志,主要的作用也是处理上述的7个打包流程中的每一步。在窗口的日志中我们可以看到熟悉的关键字比如第二行的compileDevDebugAidl从名字上我们可以知道是处理Aidl。还有generateDevDeubgBuildConfig是生成BuildConfig文件。我们常用的BuildConfig.isDebug就是这个Task中处理生成的。当然每个Gradle Task都是上述7个打包步骤流程的细化处理。我把整个系统中用到的Task和实现类列了出来感兴趣的小伙伴可以研究下源码。我们先简单分析下GenerateBuildConfig Task的源码,源码基于com.android.tools.build:gradle:4.1.0Kotlin版本。

abstract class GenerateBuildConfig : NonIncrementalTask(){

//版本名称

@get:Input

@get:Optional

abstract val versionName: Property<String?>

//版本号

@get:Input

@get:Optionalabstract

abstract val versionCode: Property<Int?>

//父类NonIncrementalTask的唯一抽象方法,也就是BuildConfig的主要逻辑处理方法

override fun doTaskAction() {

//获取类里面的属性包括一些自定义的属性

val buildConfigData = BuildConfigData.Builder()

.setBuildConfigPackageName(buildConfigPackageName.get())

.apply {

//此处省略了BUILD_TYPE、FLAVOR、DEBUG等属性的获取,思路是一样的

if (hasVersionInfo.get()) {

versionCode.orNull?.let {

addIntField(“VERSION_CODE”, it)

addStringField(“VERSION_NAME”, “${versionName.getOrElse(”“)}”)

}

}

//

val generator: GeneratedCodeFileCreator =

if (bytecodeOutputFile.isPresent) {

//创建一个JVM字节码BuildConfig,Kotlin版本进行了改造

BuildConfigByteCodeGenerator(byteCodeBuildConfigData)

} else {

//创建一个java文件的BuildConfig,java版本的GenerateBuildConfig一直是这种方案

BuildConfigGenerator(sourceCodeBuildConfigData)

}

}

//调用内部实现类,用JavaWriter创建 generator.generate()

}

}

复制代码

可以看到GenerateBuildConfig已经改成了Kotlin,同时其他的系统Task也都变成了Kotlin版本。看来谷歌也是下了血本了。Kotlin的相关知识比如协程、suspend、非阻塞式挂起函数、扩展函数、泛型也会写一些文章欢迎点赞关注,给作者一些动力。言归正常可以看到GenerateBuildConfig继承了NonIncrementalTask,这个父类也是Kotlin版本改造后才有的基本上其他的系统Task也都继承于这个类。主要作用是一个增量编译处理类。内部有一个抽象方法doTaskAction,也就是GenerateBuildConfig里面的主要逻辑实现方法。同时还有个cleanUpTaskOutputs方法在doTaskAction之前调用,主要作用于确保在任务运行之前删除任务输出。

生成Java类的主要逻辑流程:

doTaskAction–>buildConfigData -->BuildConfigGenerator–>JavaWriter

复制代码

生成字节码类的主要逻辑流程:

doTaskAction–>buildConfigData -->BuildConfigByteCodeGenerator–>ClassWriter

复制代码

主要流程拆分

  1. 生成buildConfigData类,这是一个Builder的设计模式

  2. 添加一些默认的属性比如:BUILD_TYPE、FLAVOR、DEBUG等

  3. isPresent则生成BuildConfigByteCodeGenerator否则生成BuildConfigGenerator

  4. 如果是BuildConfigGenerator则通过items.get()添加自定义的属性

  5. 调用generate生成具体实现类内部用JavaWriter or ClassWriter实现

系统其他Task、对应实现类和作用

三、编译耗时检测


1、gradlew命令

对于较大的项目或者实现大量自定义Transfrom-API 项目,可能需要深入了解构建流程才能找到瓶颈。为此,可以剖析 Gradle 执行构建生命周期的每个阶段和每个构建任务所需的时间。

如需生成和查看构建性能剖析报告,请按以下步骤操作:

1、打开项目根目录下的命令行终端。

2、输入以下命令,可以先执行Claen。因为如果某个任务的输入内容(例如源代码)未发生更改,Gradle 就会跳过它。因此输入内容未发生更改的第二个 build 始终会以更快的速度运行,因为任务不会重复运行。在 build 之前运行 clean 任务可以确保您能够剖析完整的构建流程。

//mac

gradlew clean

//window

gradle clean

复制代码

3、执行完Clean后可以根据需要分析的构建环境执行以下命令

//mac

gradlew assembleDebug --profile

//window

gradle assembleDebug --profile

复制代码

4、构建完成后,可以在项目的根目录下的/build/reports/profile/ 目录找到对应的html报告

5、可以查看报告中的每个标签页以了解您的构建,例如,Task Execution 标签页显示了 Gradle 执行各个构建任务所花费的时间。这里需要注意的地方是,Summary的task Execution是每个模块累计相加,实际上多个模块都是并行的。

Summary:构建时间概要

Configuration:配置时间

DependencyResolution:依赖解析花费的时间

TaskExecution:每个任务执行的时间

2、自定义Gradle生命周期实现方法

可以看到在每次的运行构建编译后会对每个gradleTask进行耗时的打印,因此可以针对耗时任务严重的Task做针对性的优化处理还可以针对耗时超过一定时间的任务做监控,如果触发了临界值就会做报警处理这样就保证了以后的Task一直处于较低的耗时,因为内容比较多这个监控方案第二章的时候会详细讲解。

其他生命周期的方法以省略,具体代码如下:

import java.util.concurrent.TimeUnitclass

TimingsListener implements TaskExecutionListener, BuildListener {

private long startTime

private timings = []

@Override

void beforeExecute(Task task) {

startTime = System.nanoTime()

}

@Override

void afterExecute(Task task, TaskState taskState) {

def ms = TimeUnit.MILLISECONDS.convert(

System.nanoTime() - startTime, TimeUnit.NANOSECONDS);

timings.add([ms, task.path])

task.project.logger.warn “${task.path} took ${ms}ms”

}

@Override

void buildFinished(BuildResult result) {

println “Task timings:”

for (timing in timings) {

if (timing[0] >= 50) {

printf “%7sms %s\n”, timing

}

}

}

}

gradle.addListener new TimingsListener()

复制代码

四、编译优化常规方案


俗话说的好“预先善其事,必先利其器”、“磨刀不误砍柴工” 、“先谋而后动”等。大致意思那就是先把需要用到的工具进阶升级下才能打怪更加的无伤或者在打怪前先计划好何时动手,何时使用必杀技等。根据以上结论就有了以下几种编译速度的优化方案:

1、使用最新版本工具

谷歌也一直很值开发中的痛楚,同时自己也改造了系统的Gradle Task和出了一些针对构建速度的Studio工具比如:Instant Run、Apply Changes。Instant Run这个技术是基于 Transfrom-API 技术,Transfrom-API 业界好多的热修复框架也是基于这个思想来实现的但是由于诟病太多在 Android Studio 3.5 Instant Run 就被废弃了。后来又出了Apply Changes它依赖的是 Android 8.0 开始虚拟机支持的特殊指令 (JVMTI) 来进行类的替换。这两个工具后面的深度编译速度优化章节会详细的介绍就不再这里陈诉了,回归正题。

几乎每次更新时,Android 工具都会有一定构建方面的优化所以说我们可以把以下工具升级到最新的版本:

2、Debug环境只编译需要的资源

避免编译不必要的资源

避免编译和打包不测试的资源(例如,其他语言本地化和屏幕密度资源)。为此,您可以仅为“dev”或者“debug”的版本指定一个语言资源和屏幕密度,如下面的示例中所示:

android {

productFlavors {

debug {

//在debug环境编译时只会处理中文的语言和xxhdpi的资源图片

//这样就减少了打包的第一步AAPT的资源合并的流程,

resConfigs “zh”, “xxhdpi”

}

}

}

复制代码

对调试 build 停用 Crashlytics

如果您不需要运行 Crashlytics 报告,请按如下方法停用该插件,以提高调试 build 的构建速度:

android {

buildTypes {

debug {

ext.enableCrashlytics = false

}

}

复制代码

禁止自动生成 build ID

如果想要将 Crashlytics 用于调试 build,可以通过阻止 Crashlytics 在每次构建过程中使用唯一 build ID 更新应用资源,提高增量构建的速度。由于此 build ID 存储在清单引用的资源文件中,因此禁止自动生成 build ID 还可以将 Apply Changes 和 Crashlytics 一起用于调试 build。如果需要阻止 Crashlytics 自动更新其 build ID可以配置如下:

android {

buildTypes {

debug {

ext.alwaysUpdateBuildId = false

}

}

复制代码

3、版本将图片转换为 WebP

WebP 是一种既可以提供有损压缩(像 JPEG 一样)也可以提供透明度(像 PNG 一样)的图片文件格式,不过与 JPEG 或 PNG 相比,这种格式可以提供更好的压缩。减小图片文件大小可以加快构建速度(无需在构建时进行压缩),尤其是当应用使用大量图片资源时。不过,在解压缩 WebP 图片时,能会注意到设备的 CPU 使用率有小幅上升。通过使用 Android Studio,您可以轻松地将图片转换为 WebP 格式。步骤如下:

  1. 右键点击某个图片文件或包含一些图片文件的文件夹,然后点击 Convert to WebP

  2. Converting Images to WebP 对话框随即打开。默认设置取决于当前模块的 minSdkVersion 设置。

  3. 点击 OK 以开始转换。如果要转换多张图片,只需一步即可完成转换操作,并且可以撤消转换操作以便一次性还原已转换的所有图片。

  4. 如果在上面选择了无损转换,系统会立即进行转换。图片会在原始位置进行转换。如果选择了有损转换,请继续执行下一步。

  5. 如果您选择了有损转换,并且选择在保存之前查看每张转换后图片的预览效果,那么 Android Studio 会在转换过程中显示每张图片,以便检查转换结果。

  6. 点击 Finish。图片会在原始位置进行转换。

左侧是原始 JPG 图片,右侧是有损编码 WebP 图片。对话框中显示了原始图片和转换后图片的文件大小。您可以向左或向右拖动滑块以更改质量设置,并能够立即看到编码图片的效果和文件大小。

4、格式停用 PNG

如果无法(或者不想)将 PNG 图像转换为 WebP 格式,仍可以在每次构建应用时停用自动图片压缩,从而提高构建速度。如果使用的是 Android 插件 3.0.0 或更高版本,默认情况下仅针对“调试”构建类型停用 PNG 处理。如需针对其他构建类型停用此优化,请将以下代码添加到 build.gradle 文件中:

android {

buildTypes {

debug{

//禁用PNG压缩。

crunchPngs false

}

}

复制代码

5、开启gradle缓存

构建缓存可以存储构建项目时 Android Plugin for Gradle 生成的特定输出(例如,未打包的 AAR 和经过 dex 预处理的远程依赖项)。使用缓存时,干净构建的速度会显著加快,因为构建系统在进行后续构建时可以直接重用这些缓存的文件,而无需重新创建。

#开启gradle缓存

org.gradle.caching=true

android.enableBuildCache=true

最后

文章不易,如果大家喜欢这篇文章,或者对你有帮助希望大家多多点赞转发关注哦。文章会持续更新的。绝对干货!!!

  • Android进阶学习全套手册
    关于实战,我想每一个做开发的都有话要说,对于小白而言,缺乏实战经验是通病,那么除了在实际工作过程当中,我们如何去更了解实战方面的内容呢?实际上,我们很有必要去看一些实战相关的电子书。目前,我手头上整理到的电子书还算比较全面,HTTP、自定义view、c++、MVP、Android源码设计模式、Android开发艺术探索、Java并发编程的艺术、Android基于Glide的二次封装、Android内存优化——常见内存泄露及优化方案、.Java编程思想 (第4版)等高级技术都囊括其中。

  • Android高级架构师进阶知识体系图
    关于视频这块,我也是自己搜集了一些,都按照Android学习路线做了一个分类。按照Android学习路线一共有八个模块,其中视频都有对应,就是为了帮助大家系统的学习。接下来看一下导图和对应系统视频吧!!!

  • Android对标阿里P7学习视频

  • BATJ大厂Android高频面试题
    这个题库内容是比较多的,除了一些流行的热门技术面试题,如Kotlin,数据库,Java虚拟机面试题,数组,Framework ,混合跨平台开发,等

    《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
    VP、Android源码设计模式、Android开发艺术探索、Java并发编程的艺术、Android基于Glide的二次封装、Android内存优化——常见内存泄露及优化方案、.Java编程思想 (第4版)等高级技术都囊括其中。

[外链图片转存中…(img-3CokEasp-1714380592979)]

  • Android高级架构师进阶知识体系图
    关于视频这块,我也是自己搜集了一些,都按照Android学习路线做了一个分类。按照Android学习路线一共有八个模块,其中视频都有对应,就是为了帮助大家系统的学习。接下来看一下导图和对应系统视频吧!!!
    [外链图片转存中…(img-XiRKBmVh-1714380592982)]

  • Android对标阿里P7学习视频

[外链图片转存中…(img-PNMSfChw-1714380592984)]

  • BATJ大厂Android高频面试题
    这个题库内容是比较多的,除了一些流行的热门技术面试题,如Kotlin,数据库,Java虚拟机面试题,数组,Framework ,混合跨平台开发,等
    [外链图片转存中…(img-xYYoEQD6-1714380592986)]
    《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
  • 26
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值