欢迎投稿

今日深度:

Android进阶——从源文件到APK背后的所有主要流程小结,Android进阶

Android进阶——从源文件到APK背后的所有主要流程小结,Android进阶


文章大纲)

  • 引言
  • 一、Gradle构建系统概述
    • 1、 源集SourceSet
    • 2、构建类型BuildType
    • 3、产品风格ProductFlavor
    • 4、构建变体Build Variant
    • 5、签署
    • 6、ProGuard
    • 7、 Android App Bundles(AAB)
    • 8、Split APKs
  • 二、APK打包概述
    • 1、源代码文件打包
      • 1.1、 aapt打包资源文件,生成R.java和resources.arsc文件
      • 1.2、 处理aidl文件,生成相应的Java层接口文件
      • 1.3、javac编译项目源代码,生成class文件
      • 1.4、dx/d8转换所有的class文件,生成classes.dex文件
      • 1.5、apkbuilder打包生成APK文件
      • 1.6、apksigner对APK文件进行签名
      • 1.7. zipalign对签名后的APK文件进行对齐处理
    • 2、资源打包
    • 3、 资源文件的分类:
    • 4、R.java
    • 5、资源索引表resources.arsc
      • 5.1、文件头
      • 5.2、全局字符串池
      • 5.3、资源包

引言

作为一个Android 开发者,对于大部分人来说APK可能是既熟悉又陌生,熟悉的是所有的源文件最终的成品都是APK,陌生的是很多人可能不知道背后发生的一些流程,简单来说就是如何从项目中的源文件经由构建系统编译打包最终输出APK。

一、Gradle构建系统概述

Google 基于IntelliJIdea 开发了Android Studio (当然如果不使用 Android Studio,可以通过命令行执行指令来构建和运行您的应用。 ),为了降低开发者的开发难度和开发成本,提供了Android 插件,同时采用了Gradle 构建系统来编译应用资源和源文件,并将它们打包成可快速测试、部署、签署和分发的 APK,得益于Gradle 强大的构建机制,除了默认的配置之外,还支持根据需求去自定义构建配置信息,其中每个构建配置均可自行定义一组代码和资源(即源集SourceSet),同时对所有应用版本公有部分的重复利用。要想充分了解Gradle构建系统以下的一些名词你需要掌握:

1、 源集SourceSet

Android Studio 按照逻辑关系将每个模块的源代码文件资源(包含assets、JNI、清单文件),Android Studio 默认就为我们创建了main 源集main源集包含的源代码文件和资源,可供其他源集共享使用。源集还和构建变体有关系,在您配置新的构建变体时,除了可以使用main源集还可以自己创建对应构建变体的源集,创建源集的操作见Gradle脚本语法详解

  • src/main/——main源集包括所有构建变体所公有的代码和资源

  • src/buildType/——创建此格式源集可加入特定构建类型专用的代码和资源

  • src/productFlavor/——创建此格式源集可加入特定产品风格专用的代码和资源,如果配置构建以组合多个产品风格,则可为风格维度间产品风格的各个组合创建源集目录: src/productFlavor1ProductFlavor2/

  • src/productFlavorBuildType/——创建此格式源集可加入特定构建变体专用的代码和资源

注意:当您在 Android Studio 中使用 File > New 菜单选项新建文件或目录时,可以针对特定源集进行创建, 可供您选择的源集取决于您的构建配置,如果所需目录尚不存在,Android Studio 会自动创建,而且以上的buildType、productFlavor、productFlavor1ProductFlavor2、productFlavorBuildType皆不是意味着目录名称,只是表示目录名称的格式

如果不同源集均包含同一文件的不同版本,Gradle 在进行编译时将按以下优先顺序决定使用哪一个文件(左侧源集替换右侧源集的文件和设置):构建变体 > 构建类型 > 产品风格 > 主源集 > 库依赖项;而对于清单文件则是采取合并的方式处理,在合并多个清单时,Gradle 也使用同一优先顺序

2、构建类型BuildType

构建类型定义 Gradle 在构建和打包您的应用时使用的某些属性(Android Studio 默认在build.gradle脚本中buildTypes节点下会创建子节点debug调试和发布release构建类型),针对开发生命周期的不同阶段进行配置,例如调试构建类型支持调试选项,使用调试密钥签署 APK;而发布构建类型则可压缩、混淆 APK 以及使用发布密钥签署要分发的 APK,必须至少定义一个构建类型才能构建应用。

3、产品风格ProductFlavor

产品风格代表可以向用户发布的不同版本的应用(例如免费和付费版本的应用) 也可以将产品风格自定义为使用不同的代码和资源,同时对所有应用版本共有的部分加以共享和重复利用。 不过产品风格是不是必选项,但是如果需要就必须手动创建。

4、构建变体Build Variant

构建变体是构建类型与产品风格的交叉产物,是 Gradle 在构建应用时使用的配置。 您可以利用构建变体在开发时构建调试版本的产品风格,或者构建要分发的已签署发布版产品风格。 您不是直接配置构建变体,而是配置组成变体的构建类型和产品风格。 创建附加构建类型或产品风格也会创建附加构建变体。

5、签署

构建系统支持在构建配置中指定签署设置,并可在构建过程中自动签署您的 APK。 构建系统通过使用已知凭据的默认密钥和证书签署调试版本,以避免在构建时提示密码。 除非您为此构建明确定义签署配置,否则,构建系统不会签署发布版本。

6、ProGuard

构建系统支持为每个构建变体指定不同的 ProGuard 规则文件,在构建时运行 ProGuard 对类进行压缩和混淆处理。

7、 Android App Bundles(AAB)

在 2018 年的 Google I/O 大会上,Google 向 Android 引入了新 App 动态化框架AAB(即 Android App Bundle)。Android App Bundle 是一种包含编译后代码和资源文件的新的上传格式(.aab),它推迟了 APK 的生成和签名,由 Google play 来完成,是Google Play 推出新 app 交付模式,叫做动态交付 (Dynamic Delivery),它会根据每个用户的设备信息,使用开发者上传的 app bundle 来生成对应的 apk 文件,

注意: 使用aab文件会将apk的大小限制增加到500MB,这个限制不是指aab文件的大小,而是下载apk时的大小,每个 APP bundle 对应一个独立的 app 或 applicationID。因此,如果你使用了 product flavors 来创建不同版本的 app,并且每个版本 app 都对应一个不同的 applicationID,那你就需要为每个 app 构建一个 app bundle

其中蓝色方框区域就是configuration apks支持的配置项,也可以在 app bundle 中添加 dynamic feature modules,这些模块可以包含新的功能和资源。开发者可以决定用户第一次安装时需不需要下载这些资源构建一个Android App Bundle,只需要几次点击,但是添加dynamic feature模块,可能需要重构整个项目,因为Android App Bundle != APK,而App Bundle 纯粹是为了上传设计的文件,用户无法直接安装和使用它。它仅是一个 zip 文件,Google Play 从中生成优化的 APK 并将其提供给设备进行安装,从 APK 切换到 App Bundle 是一个无缝过程。构建 app bundles 和支持动态交付的主要条件和步骤:

  • Android Studio 3.2 or higher
  • 支持动态交付,需要包含一个 base module,重新组织代码和资源,生成 configuration APKs,此外可以添加 dynamic feature 模块
  • 生成 aab 文件(使用 AS 或 命令行工具)
  • 使用 bundletool 来测试 aab 生成 apk 或发布到测试机上
  • 使用 Google Play 应用签名,要使用推荐的应用发布格式 Android App Bundle,您需要先注册 Google Play 应用签名,然后才能在 Play Console 上传您的 app bundle 文件。
  • 上传 Play Console

8、Split APKs

Split APKs 是 Android 5.0 开始提供多 apk 构建机制,是 Dynamic Delivery 功能的最基本组件。Split APKs 将原来一个 APK 中多个模块共享同一份资源的模型,分离成多个 APK 使用各自的资源,并且可以继承 Base APK 中的资源,多个 APK 有相同的 data,cache 目录,多个 dex 文件,相同的进程,在 Settings.apk 中只显示一个 APK,并且使用相同的包名。Split APK 可以将一个庞大的 APK,按屏幕密度,ABI 等形式拆分成多个独立的 APK,在应用程序更新时,不必下载整个 APK,只需单独下载某个模块即可安装更新。

  • Base apks—— 包含了一些基本功能,和其他 split apks 公用的资源和代码。Google play 从你的项目的 app module 来生成 base apk。当用户请求下载完整 apk 时,base apk 是第一个被下载和安装的。 如果你想减少初始下载的大小,那就需要减少你的 app module 的代码
  • Configuration apks——包括不同的 native 代码,特定屏幕尺寸,CPU 架构或者多语言。每当用户要下载这个应用的时候,只会下载真正对应其设备信息的 apk。
    配置 apk 不用单独的新建 module,google play 会自动生成对应 apk
  • Dynamic feature apks—— 每个 apk 包含一个特性,对应一个 module。Play Core Library 负责为用户按需安装这些特性。
    这个 Dynamic feature 不是第一次安装需要的,Google play 从 Dynamic feature module 来生成动态特性 apk 文件

对了,以上第7、8需要Google Play的支持,所以选择性了解。

二、APK打包概述

Android的包文件APK本质上是包含代码资源的一个压缩包,解压之后通常会有以下主要的目录:

文件内容 说明
classess.dex Java 源码被编译后生成的Dalvik 虚拟机字节码文件,即Dalvik虚拟机的可执行文件
lib 其子目录 armeabi 存放的是 so 文件,若需要使用 JNI调用 C/C++,则so 文件放到该目录下。
AndroidManifest.xml 程序的全局配置文件。此文件在每个应用中都必须被定义和包含,它描述了应用的名称、开放权限、引用的库文件、版本号等重要信息。
resources.arsc 经过编译的二进制资源文件,Id与真实文件路径的映射表。
res 存放资源文件的,包括程序使用的布局文件、图片和字符串常量等资源。
META-INF META-INF 目录下保存的则是应用中的签名信息,签名信息可以一定程度上验证原始apk 的完整性。
assets 存放的原生资源文件,android不为/assets下的文件生成ID,也不会进行编译。如果使用/assets下的文件,需要通过指定文件的路径和文件名

打包的简要流程可以概括为:aapt打包资源——>处理aidl——>javac 编译java源文件——>dx 把classes文件转为.dex——>apkbuilder脚本打包apk——>签名——>对齐

详细的流程如下图所示:


引自infoQ

1、源代码文件打包

1.1、 aapt打包资源文件,生成R.java和resources.arsc文件

aapt(The Android Asset Packaing Tool)位于android-sdk/build-tools目录下,负责把项目中的AndroidManifest.xml和res资源文件(除了assets、图片文件和raw目录之外)编译成二进制xml文件,最后生成相应的R.java和resources.arsc文件,其中R.java主要存放的是资源id,而resources.arsc则保存放了资源id与文件路径之间的映射关系

  • 只有那些类型为res/animator、res/anim、res/color、res/drawable(非Bitmap文件,即非.png、.9.png、.jpg、.gif文件)、res/layout、res/menu、res/values和res/xml的资源文件均会从文本格式的XML文件编译成二进制格式的XML文件。
  • assets资源它里面保存一些原始的文件,可以以任何方式来进行组织,这些文件最终会原封不动的 地被打包进APK文件中。
  • res资源放在主工程的res目录下,这类资源一般都会在编译阶段生成一个资源ID供我们使用。

1.2、 处理aidl文件,生成相应的Java层接口文件

aidl(Android Interface Definition Language)位于android-sdk/build-tools目录下,aidl工具解析接口定义文件然后生成相应的Java代码接口供程序调用,如果在项目没有使用到aidl文件,则会跳过这一步。

1.3、javac编译项目源代码,生成class文件

通过javac编译R.java、Java接口文件、Java源文件等项目中所有的Java代码编译成.class文件

javac -source 1.7 \ -target 1.7 \ -cp /xxx/AndroidSDK/sdk/platforms/android-25/android.jar \ ./src/com/crazymo/demo/MainActivity.java ./src/com/crazymo/demo/R.java \ -d ./gen/classes

1.4、dx/d8转换所有的class文件,生成classes.dex文件

dx/d8工具位于android-sdk/platform-tools 目录下,把所有的第三方的libraries和.class文件都会被转换成.dex文件供Android系统Dalvik虚拟机执行的,dx/d8工具的主要工作是将Java字节码转成成Dalvik字节码、压缩常量池、消除冗余信息等。

d8 是在 AS3.1 中被设置为了默认的 dex 编译器,以前使用的是dx,d8编译速度更快,输出的 .dex 文件更小,运行时效率"更高",不过如果你使用的是 AS 3.0.+ 版本,可以直接在项目的 gradle.properties 文件中,增加 enableD8 的开关,android.enableD8 = true就可以使用d8。

dx --dex \ --verbose \ --output ./gen/dex/classes.dex ./gen/classes/

1.5、apkbuilder打包生成APK文件

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

在AndroidSDKBuildTools V17以上apkbuilder又被Google 给删除掉了,可以参考SDK自带的Ant打包脚本自己写一个。

aapt package -f \ -J ./gen \ -M ./AndroidManifest.xml \ -S ./res/ \ -I /xxx/AndroidSDK/sdk/platforms/android-25/android.jar \ -D ./output/demo_unsigned.apk

1.6、apksigner对APK文件进行签名

apkbuilder是位于 android-sdk/build-tools目录下一个脚本文件,一旦APK文件生成,它必须被签名才能被安装在设备上。在开发过程中,主要用到的就是两种签名的keystore:一种是用于调试的debug.keystore,在Android Studio中直接run以后跑在手机上的就是使用的debug.keystore;另一种就是用于发布正式版本的keystore。

apksigner sign --ks ur-release-key.jks demo.apk

1.7. zipalign对签名后的APK文件进行对齐处理

zipalign位于android-sdk/build-tools目录下,主要对APK进行对齐处理,对齐的主要过程是将APK包中所有的资源文件距离文件起始偏移为4字节整数倍,这样通过内存映射访问apk文件时的速度会更快,对齐的作用就是减少运行时内存的使用,如果要上传到Google Play必须进行对齐操作。

zipalign -v -p 4 demo_unsigned.apk demo_signed.apk

2、资源打包

Android应用程序资源的组织方式有18个维度,每一个维度都代表一个配置信息,从而可以使得应用程序能够根据设备的当前配置信息来找到最匹配的资源来展现在UI上,从而提高用户体验。为了支持Android资源管理框架快速定位最匹配资源,Android资源打包工具aapt在编译和打包资源的过程中,会执行以下两个额外的操作:赋予每一个非assets资源一个ID值并定义在R.java文件生成一个resources.arsc文件


xml编写的Android资源文件都会编译成二进制格式的xml文件,资源的打包都是由AAPT工具来完成的,资源打包主要有以下流程:

  • 解析AndroidManifest.xml,获得应用程序的包名称,创建资源表。
  • 添加被引用资源包,被添加的资源会以一种资源ID的方式定义在R.java中。
  • 资源打包工具创建一个AaptAssets对象,收集当前需要编译的资源文件,收集到的资源保存在AaptAssets对象对象中。
    -将上一步AaptAssets对象保存的资源,添加到资源表ResourceTable中去,用于最终生成资源描述文件resources.arsc。
  • 编译values类资源,这类资源包括数组、颜色、尺寸、字符串等值。
  • bag、style、array这类资源分配资源ID
  • 编译xml资源文件,首先解析xml文件 , 再赋予属性名称资源ID ,接着解析属性值 ,最后将xml文件从文本格式转换为二进制格式。
  • 生成资源索引表resources.arsc。

XML资源文件之所以要从文本格式编译成二进制格式,原因主要有:

  • 二进制格式的XML文件占用空间更小。因为所有XML元素的标签、属性名称、属性值和内容所涉及到的字符串都会被统一收集到一个字符串资源池中去,并且会去重。有了这个字符串资源池,原来使用字符串的地方就会被替换成一个索引到字符串资源池的整数值,从而可以减少文件的大小。

  • 二进制格式的XML文件解析速度更快。因为二进制格式的XML元素里面不再包含有字符串值,因此就避免了进行字符串解析,从而提高速度。

虽然将XML资源文件从文本格式编译成二进制格式解决了空间占用以及解析效率的问题,但是对于Android资源管理框架来说,这只是完成了其中的一部分工作,Android资源管理框架的另外一个重要任务就是要根据资源ID来快速找到对应的资源,于是资源映射表应运而生。

3、 资源文件的分类:

  • assets——assets资源放在主工程assets目录下,它里面保存一些原始的文件,可以以任何方式来进行组织,这些文件最终会原封不动的地被打包进APK文件中。获取asset资源也十分简单,如下所示:InputStream is = getAssets.open(“fileName”);
  • res——res资源放在主工程的res目录下,这类资源一般都会在编译阶段生成一个资源ID供我们使用,andorid中res资源主要分为以下种类:
    • animator——保存描述属性动画的xml资源文件
    • anim——保存视图动画的xml资源文件
    • color——保存颜色资源文件
    • drawable——保存一切drawable资源文件
    • layout——保存布局资源xml文件
    • menu——保存菜单资源相关xml文件
    • raw——保存原始资源文件
    • values——保存字符串、颜色、尺寸、样式、属性等资源xml文件
    • xml——保存xml文件

上述9种类型的资源文件(除了raw类型资源以及Bitmap文件的drawable类型资源之外),这些二进制格式的XML文件分别有一个字符串资源池,用来保存文件中引用到的每一个字符串,包括XML元素标签、属性名称、属性值,以及其它的一切文本值所使用到的字符串。这样原来在文本格式的XML文件中的每一个放置字符串的地方在二进制格式的XML文件中都被替换成一个索引到字符串资源池的整数值,这写整数值统一保存在 R.java类中,R.java会和其他源文件一起编译到APK中去。

4、R.java

每个Android项目里都有一个R.java文件,用于存储资源的Id,如图所示:

每个资源项后的整数就是资源ID,资源ID是一个4字节的无符整数,其中最高字节是命名空间Package ID(用于标示资源的来源),Android系统自己定义了两个Package ID:系统资源命名空间0x01 和 应用资源命名空间:0x7f
次字节是资源类型 Type ID(用于区分anim、color、string);最低两个字节是Entry ID,表示资源在其所属资源类型中所出现的次序

5、资源索引表resources.arsc

Android加载资源的时候,会通过这个索引表根据资源ID进行资源的查找,为不同语言、不同地区、不同设备提供相对应的最佳资源(查找和通过Resources和 AssetManger来完成的)

其实resources.arsc是一个编译后的二进制文件内存中的数据结构如下:

注:整个文件都是有一系列chuck(块)构成的,chuck是整个文件的划分单位,每个模块都是一个chuck,chuck最前面是一个ResChunk_header的结构体,用来描述整个chunk的信息,更多关于索引表格式的细节,可以查阅源码文件/frameworks/base/xx/include/androidfw/ResourceTypes.h

5.1、文件头

数据结构用ResTable_header来描述,用来描述整个文件的信息,包括文件头大小,文件大小,资源包Package的数量等信息。

5.2、全局字符串池

存放所有的字符串,所以资源复用这些字符串,字符串里存放的是资源文件的路径名和资源值等信息。全局字符串池分为资源类型(type)字符串池和

5.3、资源包

资源包会有多个,比如系统资源包、应用资源包,主要由以下主要部分组成:

  • 包头——描述资源包相关信息。
  • 资源类型字符串池——存放资源的类型。
  • 资源名称字符串池——存放资源的名称。
  • 配置列表——存放语音、位置等手机配置信息,用来作为查找资源的标准。

从这里可以看到resources.arsc索引表存在很多常量池,常量池的使用目的也很明显,就是提供资源的复用率,减少resources.arsc索引表的体积,提高索引效率。

文章最后发布于: 2019-10-22 22:04:54

www.htsjk.Com true http://www.htsjk.com/shujukunews/38124.html NewsArticle Android进阶——从源文件到APK背后的所有主要流程小结,Android进阶 文章大纲) 引言 一、Gradle构建系统概述 1、 源集SourceSet 2、构建类型BuildType 3、产品风格ProductFlavor 4、构建变体Build...
相关文章
    暂无相关文章
评论暂时关闭