- zipalign:签名对齐
因为每个项目资源和代码数量是不同的,而整个打包流程是固定的。这就导致编译打包的速度会和项目的资源和代码数量成正比。每个打包流程节点分别能在android-sdk目录下找到对象的工具,而Android整个编译过程是根据Gradle才进行处理,那么Gradle是怎么处理单个节点打包流程的?又是怎么把我们新增一个Activity.class和drawable一步步转化成APK供ART虚拟机识别解码运行的?下面就介绍下每个节点下的流程和Gradle如何处理的。
1、aapt:打包资源文件
使用aapt来打包res资源文件,生成 R.java
、resources.arsc
和 res
文件,res文件分为 二进制 和 非二进制 文件,典型的非二进制文件如:res/raw和图片,它们保持原样,不被编译。
res目录有9种目录,如下:
-
animator :这类资源以
XML
文件保存在res/animator
目录下,用来描述属性动画
。 -
anim:这类资源以
XML
文件保存在res/anim
目录下,用来描述补间动画
。 -
color:这类资源以
XML
文件保存在res/color
目录下,用描述对象颜色状态选择。 -
drawable:这类资源以
XML
或者Bitmap
文件保存在res/drawable
目录下,用来描述可绘制对象。例如,我们可以在里面放置一些图片(.png, .9.png, .jpg, .gif),来作为程序界面视图的背景图。注意,保存在这个目录中的Bitmap文件在打包的过程中,可能会被优化的。例如,一个不需要多于256色的真彩色PNG文件可能会被转换成一个只有8位调色板的PNG面板,这样就可以无损地压缩图片,以减少图片所占用的内存资源。 -
layout:这类资源以
XML
文件保存在res/layout
目录下,用来描述应用程序界面布局。 -
menu:这类资源以
XML
文件保存在res/menu
目录下,用来描述应用程序菜单。 -
raw:这类资源以
任意格式
的文件保存在res/raw
目录下,它们和assets类资源一样,都是原装不动地打包在apk文件中的,不过它们会被赋予资源ID,这样我们就可以在程序中通过ID来访问它们。例如,假设在res/raw目录下有一个名称为filename的文件,并且它在编译的过程,被赋予的资源ID为R.raw.filename,那么就可以使用以下代码来访问它:Resources res = getResources(); InputStream is = res .openRawResource(R.raw.filename);
-
values:这类资源以
XML
文件保存在res/values
目录下,用来描述一些简单值,例如,数组、颜色、尺寸、字符串和样式值等,一般来说,这六种不同的值分别保存在名称为arrays.xml、colors.xml、dimens.xml、strings.xml和styles.xml文件中。 -
xml:这类资源以
XML
文件保存在res/xml
目录下,一般就是用来描述应用程序的配置信息。
2、aidl:处理aidl文件
aidl:是Android中IPC方式中的一种主要用于跨进程通讯,一般的项目中很少有此类文件。
这一过程中使用到的工具是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
复制代码
主要流程拆分
-
生成buildConfigData类,这是一个Builder的设计模式
-
添加一些默认的属性比如:BUILD_TYPE、FLAVOR、DEBUG等
-
isPresent则生成BuildConfigByteCodeGenerator否则生成BuildConfigGenerator
-
如果是BuildConfigGenerator则通过items.get()添加自定义的属性
-
调用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 格式。步骤如下:
-
右键点击某个图片文件或包含一些图片文件的文件夹,然后点击 Convert to WebP。
-
Converting Images to WebP 对话框随即打开。默认设置取决于当前模块的
minSdkVersion
设置。 -
点击 OK 以开始转换。如果要转换多张图片,只需一步即可完成转换操作,并且可以撤消转换操作以便一次性还原已转换的所有图片。
-
如果在上面选择了无损转换,系统会立即进行转换。图片会在原始位置进行转换。如果选择了有损转换,请继续执行下一步。
-
如果您选择了有损转换,并且选择在保存之前查看每张转换后图片的预览效果,那么 Android Studio 会在转换过程中显示每张图片,以便检查转换结果。
-
点击 Finish。图片会在原始位置进行转换。
左侧是原始 JPG 图片,右侧是有损编码 WebP 图片。对话框中显示了原始图片和转换后图片的文件大小。您可以向左或向右拖动滑块以更改质量设置,并能够立即看到编码图片的效果和文件大小。
4、格式停用 PNG
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
题外话
不管怎么样,不论是什么样的大小面试,要想不被面试官虐的不要不要的,只有刷爆面试题题做好全面的准备,当然除了这个还需要在平时把自己的基础打扎实,这样不论面试官怎么样一个知识点里往死里凿,你也能应付如流啊
这里我为大家准备了一些我工作以来以及参与过的大大小小的面试收集总结出来的一套进阶学习的视频及面试专题资料包,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家~
欢迎评论区讨论。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
题外话
不管怎么样,不论是什么样的大小面试,要想不被面试官虐的不要不要的,只有刷爆面试题题做好全面的准备,当然除了这个还需要在平时把自己的基础打扎实,这样不论面试官怎么样一个知识点里往死里凿,你也能应付如流啊
这里我为大家准备了一些我工作以来以及参与过的大大小小的面试收集总结出来的一套进阶学习的视频及面试专题资料包,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家~
[外链图片转存中…(img-yDnfHFuP-1713070715214)]
欢迎评论区讨论。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!