本日就和各人一起去探索下Android中编译打包的那些事儿。
整个构建流程大概分为两部分:编译(Compile),打包(Package)
编译过程就是将 java文件 编译成 class文件,末了优化成 dex文件。
打包流程就是将DEX文件和编译后的资源组合成单个APK,而且签名,天生终极的APK。
在这些工作中会有许多工具来辅助完成,比如AAPT,aidl,javac,apk builder 等等。
那在Android Studio中,又是由谁来调理这些工具的呢?Gradle构建工具。
也就是说,在我们点击 generate APK 之后,Gradle就会实行一系列的约定好的 task,每个task有本身的构建工作,按照编译打包的序次,分别调用具体的工具,终极构造起了整个构建流程。
Gradle Task
在Android Studio中,我们运行一个debug包,Build控制台上就可以看到全部的构建干系task:
Starting Gradle Daemon...Gradle Daemon started in 902 ms> Task :app:preBuild UP-TO-DATE> Task :app:preDebugBuild UP-TO-DATE> Task :app:compileDebugAidl NO-SOURCE> Task :app:compileDebugRenderscript NO-SOURCE> Task :app:generateDebugBuildConfig UP-TO-DATE> Task :app:checkDebugAarMetadata UP-TO-DATE> Task :app:generateDebugResValues UP-TO-DATE> Task :app:generateDebugResources UP-TO-DATE> Task :app:mergeDebugResources UP-TO-DATE> Task :app:createDebugCompatibleScreenManifests UP-TO-DATE> Task :app:extractDeepLinksDebug UP-TO-DATE> Task :app:processDebugMainManifest> Task :app:processDebugManifest> Task :app:javaPreCompileDebug UP-TO-DATE> Task :app:mergeDebugNativeDebugMetadata NO-SOURCE> Task :app:mergeDebugShaders UP-TO-DATE> Task :app:compileDebugShaders NO-SOURCE> Task :app:generateDebugAssets UP-TO-DATE> Task :app:mergeDebugAssets UP-TO-DATE> Task :app:compressDebugAssets UP-TO-DATE> Task :app:processDebugJavaRes NO-SOURCE> Task :app:mergeDebugJniLibFolders UP-TO-DATE> Task :app:mergeDebugNativeLibs UP-TO-DATE> Task :app:stripDebugDebugSymbols NO-SOURCE> Task :app:validateSigningDebug UP-TO-DATE> Task :app:mergeLibDexDebug> Task :app:processDebugManifestForPackage> Task :app:processDebugResources> Task :app:compileDebugKotlin UP-TO-DATE> Task :app:compileDebugJavaWithJavac UP-TO-DATE> Task :app:compileDebugSources UP-TO-DATE> Task :app:mergeDebugJavaResource UP-TO-DATE> Task :app:dexBuilderDebug> Task :app:mergeExtDexDebug> Task :app:mergeProjectDexDebug> Task :app:packageDebug> Task :app:assembleDebugBUILD SUCCESSFUL in 22s27 actionable tasks: 10 executed, 17 up-to-date我精简了下,留下了比力紧张的task:
//aidl 转换aidl文件为java文件> Task :app:compileDebugAidl//天生BuildConfig文件> Task :app:generateDebugBuildConfig//获取gradle中设置的资源文件> Task :app:generateDebugResValues// merge资源文件> Task :app:mergeDebugResources// merge assets文件> Task :app:mergeDebugAssets> Task :app:compressDebugAssets// merge全部的manifest文件> Task :app:processDebugManifest//AAPT 天生R文件> Task :app:processDebugResources//编译kotlin文件> Task :app:compileDebugKotlin//javac 编译java文件> Task :app:compileDebugJavaWithJavac//转换class文件为dex文件> Task :app:dexBuilderDebug//打包成apk并签名> Task :app:packageDebug这里涉及到的代码许多,本日就不具体说了,task紧张涉及到的工具和功能我已经标注上了,下面就具体说说编译打包流程。
(感爱好的朋侪可以本身看看源码,查察gradle源码的方法我也放到文末的附目次了)
aidl(编译aidl文件)
对于AIDL,各人应该都很认识,它是一种用于历程间通讯的接口文件。
实在它是Google为了资助我们举行历程间通讯的轻便写法,末了照旧需要被分析编译为java文件,而做这个工作的就是aidl工具,存在于sdk/build-tools目次。
紧张的工作就是将项目中的aidl文件编译为java文件。
天生BuildConfig文件,资源文件
在引入Gradle编译工具之后,Apk的打包流程就多了这么一步,天生BuildConfig文件和资源文件。
也就是会根据build.gradle内里设置的内容天生相应的java代码大概res代码。简朴举个例子:
//build.gradle buildTypes { debug{ buildConfigField("boolean", "ISDEBUG", "true") resValue "string", "TestName", "love1" } release { buildConfigField("boolean", "ISDEBUG", "false") resValue "string", "TestName", "love2" } } //BuildConfig.java public final class BuildConfig { // Field from build type: debug public static final boolean ISDEBUG = true; } R.string.TestNamemerge 资源文件
这一步就是归并res资源文件,assets文件,manifest文件。
由于在项目中会依赖差异的库、组件,也会有多渠道的需求,以是merge这一步操作就是将差异地方的资源文件举行整合。
多个manifest文件需要整理成一个完备的文件,以是假如有属性辩论这一步就会报错。资源文件也会整理分类到差异的分辨率目次中。
AAPT/AAPT2(打包资源文件)
然后就是打包资源文件,涉及到的工具是AAPT。
AAPT,全称Android Asset Packaging Tool,以是这个构建工具就是用来打包资源文件的。
资源文件包罗:图片,res目次下的xml文件,AndroidManifest.xml文件;处置惩罚资源文件紧张包罗两步:
把全部的Android资源文件举行分析,天生扩展名为.flat的二进制文件。比如是png图片,那么就会被压缩处置惩罚,采用.png.flat的扩展名。
- 2、链接:归并全部已编译的文件并打包到一个软件包中。
起首,这一步会天生辅助文件,比如R.java(R文件),R文件各人应该都比力认识,就是一个资源索引文件,我们平常引用也都是通过R.的方式引用资源id。
末了,会将R文件和之前的二进制文件举行打包,打包到一个APK压缩包(没有dex文件、没有签名)。
再扩展一个题目,关于AAPT2。(之前有朋侪口试遇到问这个的,真是问的比力细啊,以是这里就提一嘴) Android Gradle插件 3.0.0 及更高版本默认环境下会启用 AAPT2,而老版本的AAPT已经被弃用,那么AAPT2到底优化改进了什么呢?
在AAPT中是没有链接功能的,会将全部的资源举行编译天生压缩包。如许处置惩罚方式有个缺点就是每次编译都要全量编译。
以是在AAPT2中用到链接的功能,当修改了某个资源文件之后,只需要重新编译这个改变的文件,然后与其他资源举行链接即可,支持了增量更新,大大提拔了服从。
对一些活动举行了优化,一些错误的元素从前不会报错,只会告诫大概忽略,如今会直接报错,包管步调精确运行。比如
1)、在从前的AAPT版本,Android 清单文件中出现错误的节点元素只会被忽略或告诫,而AAPT2开始会对这些节点举行报错,比如:
<activity android:name=".MainActivity"> <action android:name="android.intent.action.TEST" /></activity>AndroidManifest.xml:15: error: unknown element <action> found.2)、在AAPT2中,无法通过name属性指明资源范例了,需要单独利用type属性:
<item name="attr/my_attr">@color/pink</item> // 修改为 <item type="attr" name="my_attr">@color/pink</item>3)、ForegroundLinearLayout(远景致干系)属性限定严酷
foregroundInsidePadding属性,不属于android定名空间,以是AAPT2的改进就是对于这个属性利用更加严酷了,原来利用android:foregroundInsidePadding的时间会被忽略,如今会报错,需要改为foregroundInsidePadding。
4)、@ 资源引用符号利用严酷
对于遗漏大概错误引用@(资源引用符号)时间,AAPT2会报错。
5)、库设置不精确
当某些库创建过程中R文件字段声明为final会导致报错,AAPT2就会对这种环境举行优化。
javac(编译java文件)
接下来就是编译java文件了,用到的工具就是各人熟知的javac,通过它将java文件编译成.class文件。
注解代码也是在这个阶段天生的。当注解的生命周期被设置为CLASS的时间,就代表该注解会在编译class文件的时间收效,而且存在与java源文件和Class字节码文件。
javac的根本下令照旧可以相识下:
javac -d destdir(class文件存放目次) srcFile(java文件)dx/r8/d8 (编译class文件)
这一步就是转换.class文件为dex文件。
有人就奇怪了,.class文件不就是JVM可以识别的二进制文件吗,为什么还要举行一次转化呢?这就涉及到另一个题目:JVM 和 Dalvik(ART)的区别。
此中一个紧张的区别就是Dalvik(ART)有本身的二进制文件,也就是.dex文件,以是需要将class文件举行再一次转换。
你可以把dex文件明白为一个class文件包,内里装着许多的class文件,让这些类可以或许共享数据,类似这种关系:
再谈谈这三个工具(dx/r8/d8)的区别:
- dx是最早的转换工具,用于转换class文件为dex文件。
- Android Studio 3.1之后,引入了D8编译器和 R8 工具。
注意这里的说话:D8 编译器和 R8 工具。
以是D8就是用来代替dx用来举行转换class文件的,它的上风在于:编译更快、更小的dex文件、更好的性能。
而R8工具是用来更换ProGuard的,用于代码的压缩和肴杂。
编译class文件过程也常用于编译插桩,比如ASM,通过直接操作字节码文件完成代码修改或天生。
apkbuilder/zipflinger(天生APK包)
这一步就是天生APK文件,将manifest文件、resources文件、dex文件、assets文件等等打包成一个压缩包,也就是apk文件。
在老版本利用的工具是apkbuilder,但是在最新的版本我发现没有这个工具了,sdk目次下也找不到了。
以是我想到从打包的task——packageDebug中找找答案,果然,让我找到了新的打包工具——zipflinger。
//PackageAndroidArtifact.java (packageDebug干系代码)for (File arch : archives) { mApkCreator.writeZip(arch, pathNameMap::get, name -> !names.contains(name));}mApkCreator =new ApkFlinger(mCreationData, compressionLevel, !mIsDebuggableBuild);/** An implementation of [ApkCreator] using the zipflinger library */class ApkFlinger同时在Android Studio的更新日志中也找到了对应的阐明:
Android 构建团队不断举行更改以进步天生性能,在此版本中(Android Studio 3.6),我们将默认打包工具更改为 zipflinger 以举行调试天生。
zipalign(对齐处置惩罚)
zipalign 是一种归档对齐工具,可对 Android 应用 (APK) 文件提供紧张的优化
具体来说,它会使 APK 中的全部未压缩数据(比方图片或原始文件)在 4 字节界限上对齐。
这里涉及到一个Data structurealignment(数据对齐)的知识点,其大概意思就是假如数据是自然对齐的,CPU读写就会更高效。
有的朋侪大概会迷惑,这个对齐处置惩罚不是应该放在签名之后吗?实在这里就涉及到了签名工具的差异带来的对齐处置惩罚的序次差异:
- 假如利用的是 apksigner,只能在为 APK 文件签名之前实行 zipalign。
- 假如利用的是 jarsigner,只能在为 APK 文件签名之后实行 zipalign。
下面具体聊聊两种签名工具。
jarsigner/apksigner(签名)
在天生APK文件之后,必须对该apk文件举行签名,否则无法被安装。
之前各人比力熟知的签名工具是JDK提供的jarsigner,而apksigner是Google专门为Android提供的签名和签证工具。
其区别就在于jarsigner只能举行v1签名,而apksigner可以举行v2、v3、v4签名。
什么?尚有v4?我开始看到的时间也是大吃一惊,没想到都有v4签名了,那就顺带先容下这几个签名机制吧:
v1签名方式紧张是利用META-INFO文件夹中的三个文件。
起首,将apk中除了META-INFO文件夹中的全部文件举行举行择要写到 META-INFO/MANIFEST.MF;然后盘算MANIFEST.MF文件的择要写到CERT.SF;末了盘算CERT.SF的择要,利用私钥盘算签名,将签名和开辟者证誊写到CERT.RSA。
以是META-INFO文件夹中这三个文件就能包管apk不会被修改。
但是缺点也很显着,META-INFO文件夹不会被签名,以是美团针对这种签名方式计划了一种多渠道打包方案:
利用pythone在META-INFO文件夹中创建一个文件,其名称就是渠道名,然后用java去读取文件名获取渠道。
Android7.0之后,推出了v2签名,为相识决v1签名速率慢以及签名不完备的题目。
apk本质上是一个压缩包,而压缩包文件格式一样平常分为三块:文件数据区,中央目次效果,中央目次竣事节。
而v2要做的就是,在文件中插入一个APK签名分块,位于中央目次部分之前。
Android 9 推出了v3签名方案,和v2签名方式根本雷同,差异的是在v3签名分块中添加了有关受支持的sdk版本和新旧签名信息,可以用作签名更换升级。
Android 11 推出了v4签名方案。
v4 签名基于根据 APK 的全部字节盘算得出的 Merkle 哈希树。它完全遵照 fs-verity 哈希树的结构,将签名存储在单独的.apk.idsig 文件中。
来自:https://www.androidos.net.cn/doc/2021/7/12/529.html
|