栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Andorid&Kotlin编译速度原理剖析(上),lambda表达式的作用与好处

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Andorid&Kotlin编译速度原理剖析(上),lambda表达式的作用与好处

@get:Optional

abstract val versionName: Property

//版本号

@get:Input

@get:Optionalabstract

abstract val versionCode: Property

//父类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 %sn”, 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 工具都会有一定构建方面的优化所以说我们可以把以下工具升级到最新的版本:

  • Android Studio 和 SDK 工具

  • Android Plugin for Gradle

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

复制代码

6、开启kotlin的增量和并行编译

#开启kotlin的增量和并行编译

kotlin.incremental=true

kotlin.incremental.java=true

kotlin.incremental.js=true

kotlin.caching.enabled=true

kotlin.parallel.tasks.in.project=true

复制代码

7、使用静态依赖项版本

在 build.gradle 文件中声明依赖项时,您应当避免在结尾处使用带加号的版本号,例如 'com.android.tools.build:gradle:2.+'。使用动态版本号可能会导致意外的版本更新和难以解析版本差异,并会因 Gradle 检查有无更新而减慢构建速度。应该使用静态/硬编码版本号。

8、合理调整堆大小

#设置jvmargs大小org.gradle.jvmargs=-Xmx4000M

复制代码

9、kapt 优化

APT:Java提供了一个编译时期插件, 在代码编译期对源代码进行扫描,找出代码中的注解, 根据开发者定义的解析规则生成新的Java文件, 并且执行生成的代码将会与你手动编写的代码一起被javac编译。

KAPT:官方提供三种解决方案已经迭代到kapt3选用的也是第三种方案:

  1. 重新设计,但违背与java共存原则。

  2. 生成"存根类"这个类里面所有方法的方法体为空,也就是只保留类的结构,然后把这些"存根类"加入javac classpath中编译。方法返回类型是需要对表达式进行分析,这样会大大降低编译速度

  3. Kotlin代码编译成Java编译器可识别的二进制文件

#优化kapt kapt.use.worker.api=true //并行运行 kapt.incremental.apt=true //增量编译 kapt.include.compile.classpath=false //开启缓存 kapt { useBuildCache = true }

10、使用增量注解处理器

Android Gradle 插件 3.3.0 及更高版本改进了对增量注解处理的支持。因此,如需提高增量构建速度,可以更新 Android Gradle 插件并尽可能仅使用增量注解处理器。

此外,如果在应用中使用 Kotlin,就需要使用 kapt 1.3.30 及更高版本才能在 Kotlin 代码中支持增量注解处理器。如果必须使用一个或多个不支持增量构建的注释处理器,注释处理将不会是增量的。但是,如果项目使用的是 kapt,Java 编译仍然是增量的。

第三方增量注释处理器支持 :

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/446114.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号