【Android 修炼手册】经常使用技术篇 -- 聊聊 Android 的打包

这是【Android 修炼手册】系列第 10 篇文章,若是尚未看过前面系列文章,欢迎点击 这里 查看~html

预备知识

  1. 了解 android 基本开发

看完本文能够达到什么程度

  1. 了解 Android APK 打包的关键过程
  2. 了解多渠道打包的原理
  3. 了解 Multiple APK,Split APKs,App Bundle 的衍生的打包方式

阅读前准备工做

  1. clone CommonTec 项目,其中 simpleapk 是本文用到的示例。

文章概览

summary

咱们如今大部分开发都是基于 Android Studio 进行的,在 AS 中,咱们只须要点击 Run 按钮,AS 自动会打包 Apk 安装至设备中并运行。对于咱们来讲,其中的打包过程就是一个黑盒,咱们所知道的就是 Sources -> AS Compiler -> APK。这篇文章咱们就分析一下中间的打包过程,以及打包相关的一些问题。java

咱们先了解一下 APK 内部的结构。android

1、Android APK 包结构

接下来咱们看看一个正常的 APK 的结构。
一个 APK 打包完以后,一般有下面几个目录,用来存放不一样的文件。
assets
原生资源文件,不会被压缩或者处理
classes.dex
java 代码经过 javac 转化成 class 文件,再经过 dx 文件转化成 dex 文件。若是有多个 dex 文件,其命名会是这样的:
classes.dex classes2.dex classes3.dex ...
在其中保存了类信息。
lib/
保存了 native 库 .so 文件,其中会根据 cpu 型号划分不一样的目录,好比 ARM,x86 等等。
res/
保存了处理后的二进制资源文件。
resources.arsc
保存了资源 id 名称以及资源对应的值/路径的映射。
META-INF/
用来验证 APK 签名,其中有三个重要的文件 MANIFEST.MT,CERT.SF,CERT.RSA。
MANIFEST.MF 保存了全部文件对应的摘要,部份内容以下:git

Manifest-Version: 1.0
Built-By: Generated-by-ADT
Created-By: Android Gradle 3.4.0

Name: AndroidManifest.xml
SHA-256-Digest: QxJh66y6ssDSNFgZSlf5jIWXfRdWnqL1c3BSwSDUYLQ=

Name: META-INF/android.arch.core_runtime.version
SHA-256-Digest: zFL2eISLgUNzdXtGA4O/YZYOSUPCA3Na3eCjULPlCYk=
复制代码

CERT.SF 保存了MANIFEST.MF 中每条信息的摘要,部份内容以下:github

Signature-Version: 1.0
Created-By: 1.0 (Android)
SHA-256-Digest-Manifest: j8YGFgHsujCHud09pT6Igh21XQKSnG+Gqy8VUE55u+g=
X-Android-APK-Signed: 2

Name: AndroidManifest.xml
SHA-256-Digest: qLofC3g32qJ5LmbjO/qeccx2Ie/PPpWSEPBIUPrlKlY=

Name: META-INF/android.arch.core_runtime.version
SHA-256-Digest: I65bgli5vdqHKel7MD74YlSuuyCR/5NDrXr2kf5FigA=
复制代码

CERT.RSA 包含了对 CERT.SF 文件的签名以及签名用到的证书。web

AndroidManifest.xml
这个文件你们都很熟悉了,全局配置文件,不过这里是编译处理过的二进制的文件。shell

2、APK 打包流程

2.1 原始打包流程

咱们在上面看了一个完整 APK 的结构,APK 打包的过程其实就是生成上述文件的过程。这里放一张网上流传比较广的流程图。
api

android_build_detail

主要有下面几个步骤:bash

  1. 使用 AAPT/AAPT2 编译资源文件生成 resources.arsc 以及 R.java
  2. 使用 aidl 处理 aidl 文件,生成 java 文件
  3. 使用 JAVAC 编译 java 文件,生成 classes 文件
  4. 使用 DX/D8 处理 class 文件,生成最终须要的 dex 文件
  5. 使用 Android NDK 处理 native 代码生成 .so 文件
  6. 使用 apkbuilder 生成 未签名的 APK
  7. 使用 apksigner 对 Apk 进行签名,生成最终的 APK

2.2 如何手动使用命令行打包

平时的开发都是使用 gradle 构建,下面咱们不依赖 gradle,直接用官方提供的各个阶段的打包工具,手动用命令行打一个 APK,能够更好更详细的了解其中的过程。 这里直接用 simpleapk 工程作示例。
官方的打包工具在 android_sdk/build-tools/version/ 目录下。markdown

开始以前,咱们先建立一个 tmp 目录用来存放中间产物。建立 tmp/final 存放最终产物。

1. AAPT2 compile 资源

使用 AAPT2 处理资源须要两步,compile 和 link,首先执行 compile 操做。执行下面的命令。

/Users/zy/android-sdk-mac_x86/build-tools/28.0.2/aapt2 compile -o tmp/res --dir src/main/res/ 
复制代码

使用 aapt2 对单个资源处理,会生成 xxx.flat 文件,是 aapt2 的中间产物,能够用于后面的资源增量编译。咱们这里经过 --dir 直接指定了资源的目录,产物 res 是一个压缩包,里面包含了全部资源处理后的 xxx.flat。
这里咱们再把 res 这个压缩包解压一下。执行下面的命令。

unzip -u tmp/res -d tmp/aapt2_res
复制代码

这一步结束之后,目录是这个样子的。

tmp/
├── aapt2_res
│   └── xxx.flat
├── final
└── res
复制代码
2. AAPT2 link 资源

AAPT2 link 是把上一步 compile 处理后的 xxx.flat 资源连接,生成一个完整的 resource.arsc,二进制资源和 R.java。执行下面的命令。

/Users/zy/android-sdk-mac_x86/build-tools/28.0.2/aapt2 link -o tmp/res.apk  -I /Users/zy/android-sdk-mac_x86/platforms/android-28/android.jar --manifest src/main/AndroidManifest.xml  --java tmp -R tmp/aapt2_res/drawable-hdpi_ic_launcher_foreground.xml.flat  -R tmp/aapt2_res/mipmap-anydpi-v26_ic_launcher_round.xml.flat   -R tmp/aapt2_res/mipmap-xhdpi_ic_launcher_round.png.flat  -R tmp/aapt2_res/values_colors.arsc.flat  -R tmp/aapt2_res/drawable-hdpi_ic_launcher_foreground1.xml.flat -R tmp/aapt2_res/mipmap-hdpi_ic_launcher.png.flat    -R tmp/aapt2_res/mipmap-xxhdpi_ic_launcher.png.flat  -R tmp/aapt2_res/values_strings.arsc.flat  -R tmp/aapt2_res/drawable-mdpi_ic_launcher_foreground.xml.flat  -R tmp/aapt2_res/mipmap-hdpi_ic_launcher_round.png.flat   -R tmp/aapt2_res/mipmap-xxhdpi_ic_launcher_round.png.flat  -R tmp/aapt2_res/values_styles.arsc.flat  -R tmp/aapt2_res/drawable_ic_launcher_background.xml.flat  -R tmp/aapt2_res/mipmap-mdpi_ic_launcher.png.flat    -R tmp/aapt2_res/mipmap-xxxhdpi_ic_launcher.png.flat  -R tmp/aapt2_res/layout_activity_main.xml.flat    -R tmp/aapt2_res/mipmap-mdpi_ic_launcher_round.png.flat   -R tmp/aapt2_res/mipmap-xxxhdpi_ic_launcher_round.png.flat  -R tmp/aapt2_res/mipmap-anydpi-v26_ic_launcher.xml.flat   -R tmp/aapt2_res/mipmap-xhdpi_ic_launcher.png.flat --auto-add-overlay
复制代码

执行命令后,会生成 res.apk,里面就是 resource.arsc,处理后的 AndroidManifest.xml 以及 处理后的二进制资源。咱们这里也把他解压出来,后面最终打包的时候使用。执行命令以下。

unzip -u tmp/res.apk -d tmp/final
复制代码

这一步结束之后,目录状态以下。

tmp/
├── aapt2_res
│   └── xxx.flat
├── com
│   └── zy
│       └── simpleapk
│           └── R.java
├── final
│   ├── AndroidManifest.xml
│   ├── res
│   │   ├── drawable
│   │   │   └── ic_launcher_background.xml
│   │   ├── xxx 资源目录
│   └── resources.arsc
├── res
└── res.apk
复制代码
3. javac 生成 class 文件

这一步咱们须要处理 java 文件,生成 class 文件。要用到上一步生成的 R.java 文件。执行下面的命令。

javac -d tmp src/main/java/com/zy/simpleapk/MainActivity.java  tmp/com/zy/simpleapk/R.java -cp /Users/zy/android-sdk-mac_x86/platforms/android-28/android.jar
复制代码

这一步结束之后,目录状态以下。

tmp/
├── aapt2_res
│   └── xxx.flat
├── com
│   └── zy
│       └── simpleapk
│           ├── MainActivity.class
│           ├── R$color.class
│           ├── R$drawable.class
│           ├── R$layout.class
│           ├── R$mipmap.class
│           ├── R$string.class
│           ├── R$style.class
│           ├── R.class
│           └── R.java
├── final
│   ├── AndroidManifest.xml
│   ├── res
│   │   ├── drawable
│   │   │   └── ic_launcher_background.xml
│   │   ├── xxx 资源目录
│   └── resources.arsc
├── res
└── res.apk
复制代码
4. d8 编译 dex

这一步是把上一步生成的 class 文件编译为 dex 文件,须要用到 d8 或者 dx,这里用 d8。执行下面的命令。

/Users/zy/android-sdk-mac_x86/build-tools/28.0.2/d8 tmp/com/zy/simpleapk/*.class --output tmp --lib /Users/zy/android-sdk-mac_x86/platforms/android-28/android.jar
复制代码

命令执行完之后,会生成 classes.dex,这就是最终 apk 里须要的 dex 文件,咱们把它拷贝到 final/ 目录下。执行以下命令。

cp tmp/classes.dex tmp/final/classes.dex
复制代码

这一步结束之后,目录状态以下。

tmp/
├── aapt2_res
│   └── xxx.flat
├── classes.dex
├── com
│   └── zy
│       └── simpleapk
│           ├── MainActivity.class
│           ├── R$color.class
│           ├── R$drawable.class
│           ├── R$layout.class
│           ├── R$mipmap.class
│           ├── R$string.class
│           ├── R$style.class
│           ├── R.class
│           └── R.java
├── final
│   ├── AndroidManifest.xml
│   ├── classes.dex
│   ├── res
│   │   ├── drawable
│   │   │   └── ic_launcher_background.xml
│   │   ├── xxx 资源目录
│   └── resources.arsc
├── res
└── res.apk
复制代码
5. 打包 apk

执行完上述的命令,打包 APK 须要的材料就都准备好了,由于 APK 自己就是 zip 格式,这里咱们直接用 zip 命令打包上述产物,生成 final.apk。执行下面的命令。

zip -r final.apk *
复制代码
6. apk 签名

上一步打包好的 APK 还不能直接安装,由于没有签名,咱们这里用 debug.keystore 给 final.apk 签名。执行下面的命令。

/Users/zy/android-sdk-mac_x86/build-tools/28.0.2/apksigner sign --ks ~/.android/debug.keystore final.apk
复制代码

这里须要输入 debug.keystore 的密码,是 android。

这样,最后的 final.apk 就是咱们手动生成的 apk 了。能够安装尝试一下了~

2.3 AS 打包过程

正常开发中,咱们大部分都是直接点击 AS 中的 Run 去编译一个 APK。
咱们先来看看在 AS 中点击 Run 作了些什么事情。咱们点击 Run 运行,而后打开 Build tab 看下具体的输出,能够发现执行的是 Gradle Task assembleDebug

run

咱们在 build.gradle 里添加一行输出,看看 AS 给 gradle task 添加的额外参数。

println "projectProperties: " + project.gradle.startParameter.projectProperties
复制代码

输出以下:

projectProperties: [android.injected.build.density:xhdpi, android.injected.build.api:26, android.injected.invoked.from.ide:true, android.injected.build.abi:x86]
复制代码

其中有一个参数 android.injected.invoked.from.ide=true 代表了是从 AS 调用的 Gradle 命令。后面就是执行 Gradle 的打包命令了。而 Gradle 的打包命令,就是执行各个 Task 生成打包须要的文件。若是对这方面不了解,能够看看以前写的 Android Gradle Plugin 主要流程分析

3、多渠道打包

上面介绍了 Android APK 的打包流程,也经过手动打 APK 体验了整个流程。
在实际的生产开发过程当中,咱们每每会把 APK 发往各个应用市场,不少时候要根据市场渠道进行一些统计,因此就须要对不一样的市场渠道进行区分。关于多渠道打包的问题,有很多解决方式。
最容易想到的就是使用 Gradle 的 Flavor,使用 Flavor 的特性,生成不一样的渠道标识。不过这种方式每生成一个渠道包都须要执行一遍构建过程,很是耗时。
另一种方式就是使用 apktool 反编译 APK,修改其中的资源,添加渠道的标识,并进行从新打包签名,这种方式省去了构建过程,反编译,打包,签名这几个步骤也比较耗时。按照美团博客的数据,打包 900 个渠道包将近三个小时。

因此须要再寻找其余的方法。在此以前,咱们先看下 Android APK 签名的方式。了解了签名方式,才能更好的去了解方法实现。

在了解 APK 签名以前,咱们先看一下 Zip 的文件格式,由于 APK 本质上是一个 Zip 文件,而多渠道打包就涉及到 Zip 的文件格式。

3.1 Zip 文件格式

APK 的本质是一个 Zip 文件,咱们能够用 file 命令看一下。是 V2.0 版本的 Zip 格式文件。

file-apk

一个 Zip 文件的格式基本以下:

zip

主要能够分为三个大区域:
数据区
核心目录(central directory)
目录结束标识(end of central directory record,EODR)

3.1.1 数据区

这个部分记录了文件压缩信息和文件原始数据,每个文件信息用一个 [local file header + file data + data descriptor] 来描述 local file header
记录了文件的压缩信息,主要内容以下:

description length content
local file header signature 4 bytes (0x04034b50) 文件头标识, 0x04034b50
version needed to extract 2 bytes 解压所须要的最低版本
general purpose bit flag 2 bytes 通用标志位
compression method 2 bytes 压缩方式
last mod file time 2 bytes 文件最后修改时间
last mod file date 2 bytes 文件最后修改日期
crc-32 4 bytes CRC-32 校验码
compressed size 4 bytes 压缩后文件大小
uncompressed size 4 bytes 未压缩文件大小
file name length 2 bytes 文件名长度
extra field length 2 bytes 扩展区长度
file name (variable size) 文件名
extra field (variable size) 扩展数据

file data
保存了文件的压缩数据

data descriptor

description length content
crc-32 4 bytes crc32 校验码
compressed size 4 bytes 压缩后文件的大小
uncompressed size 4 bytes 未压缩文件的大小

表示文件的结束,只有在 local file header 中的 general purpose bit flag 第三 bit 位为 3 的时候才会出现。通常状况下没有

3.1.2 核心目录

核心目录保存了对压缩文件更多的信息以及对 Zip64 的支持信息。 主要有两个部分 file header 和 digital signature

file header
是对文件更为具体的描述,每个 file header 都对应一个 local file header。

description length content
central file header signature 4 bytes (0x02014b50) 核心文件头标志,魔数 0x02014b50
version made by 2 bytes 压缩使用的版本
version needed to extract 2 bytes 解压须要的版本
general purpose bit flag 2 bytes 通用位标志
compression method 2 bytes 压缩方法
last mod file time 2 bytes 文件最后修改时间
last mod file date 2 bytes 文件最后修改日期
crc-32 4 bytes crc-32 校验码
compressed size 4 bytes 压缩后的文件大小
uncompressed size 4 bytes 压缩前的文件大小
file name length 2 bytes 文件名长度
extra field length 2 bytes 扩展区长度
file comment length 2 bytes 文件注释长度
disk number start 2 bytes 文件在磁盘上的起始位置
internal file attributes 2 bytes 内部文件属性
external file attributes 4 bytes 外部文件属性
relative offset of local header 4 bytes 对应的 local file header 的偏移
file name (variable size) 文件名
extra field (variable size) 扩展数据
file comment (variable size) 文件注释

Digital signature
官方文档中没有对 Digital signature 有太具体的介绍,可是有提到 核心目录是能够压缩和加密的,盲猜是用来加密核心目录的。

description length content
header signature 4 bytes (0x05054b50)
size of data 2 bytes
signature data (variable size)
3.1.3 核心目录结束标志

目录结束标志是在整个文件的结尾,用于标记压缩目录数据的结束。每一个 Zip 文件有且只有一个结束标志记录。

description length content
end of central dir signature 4 bytes (0x06054b50) 目录结束标记,魔数 0x06054b50
number of this disk 2 bytes 当前磁盘编号
number of the disk with the start of the central directory 2 bytes 核心目录起始位置的磁盘编号
total number of entries in the central directory on this disk 2 bytes 磁盘上记录的核心目录数量
total number of entries in the central directory 2 bytes 核心目录结构数量
size of the central directory 4 bytes 核心目录大小
offset of start of central directory with respect to the starting disk number 4 bytes 核心目录起始位置的偏移
.ZIP file comment length 2 bytes 注释长度
.ZIP file comment (variable size) 注释内容

上面基本上就是一个通用的 Zip 包结构了。 下面就看看 Android 的签名机制。

3.2 V1 签名

V1 签名的机制主要就在 META-INF 目录下的三个文件,MANIFEST.MF,CERT.SF,CERT.RSA,他们都是 V1 签名的产物。
MANIFEST.MF 保存了全部文件对应的摘要,部份内容以下:

Manifest-Version: 1.0
Built-By: Generated-by-ADT
Created-By: Android Gradle 3.4.0

Name: AndroidManifest.xml
SHA-256-Digest: QxJh66y6ssDSNFgZSlf5jIWXfRdWnqL1c3BSwSDUYLQ=

Name: META-INF/android.arch.core_runtime.version
SHA-256-Digest: zFL2eISLgUNzdXtGA4O/YZYOSUPCA3Na3eCjULPlCYk=
复制代码

CERT.SF 保存了MANIFEST.MF 中每条信息的摘要,部份内容以下:

Signature-Version: 1.0
Created-By: 1.0 (Android)
SHA-256-Digest-Manifest: j8YGFgHsujCHud09pT6Igh21XQKSnG+Gqy8VUE55u+g=
X-Android-APK-Signed: 2

Name: AndroidManifest.xml
SHA-256-Digest: qLofC3g32qJ5LmbjO/qeccx2Ie/PPpWSEPBIUPrlKlY=

Name: META-INF/android.arch.core_runtime.version
SHA-256-Digest: I65bgli5vdqHKel7MD74YlSuuyCR/5NDrXr2kf5FigA=
复制代码

CERT.RSA 包含了对 CERT.SF 文件的签名以及签名用到的证书。

在 APK 签名时,主要流程以下:

计算 APK 中文件摘要,保存摘要的 base64 编码到 MANIFEST.MF 文件中 ->计算 MANIFEST.MF 文件的摘要,保存其 base64 编码到 CERT.SF 文件中 -> 计算 MANIFEST.MF 文件中每一个数据块的摘要,保存其 base64 编码到 CERT.SF 文件中 -> 计算 CERT.SF 文件摘要,经过开发者私钥计算数字签名 -> 保存数字签名和开发者公钥到 CERT.RSA 文件中
复制代码

在 APK 校验时,主要流程以下:

经过CA 证书解密 CERT.RSA 中的数字证书和对 CERT.SF 的签名 -> 经过签名校验 CERT.SF 文件是否被修改 -> 经过 CERT.SF 验证 MANIFEST.MF 文件是否被修改 -> 经过 CERT.SF 验证 MANIFEST.MF 文件中数据项是否被修改 -> 经过 MANIFEST.MF 校验 APK 中的文件是否被修改    
复制代码

咱们在上面讲了 Zip 文件的结构,经过上面校验过程,咱们能够发现,在 APK 校验过程当中,只是校验了数据区的内容,剩余的两个部分没有作处理。
因此若是修改剩余两个部分,签名校验过程当中是不会发现的,要写入信息,EOCD 的注释字段是很好的选择。因此将渠道信息写入 EOCD 的注释字段,就能够达到打入渠道信息的目的。这就是腾讯 VasDolly 作的事情。
除此以外,咱们能够发现,APK 签名校验过程当中,并无对 META-INF 文件夹下的文件进行签名和校验,因此能够在 META-INF 文件夹下新增一个空文件,这样也能够携带渠道信息。这就是美团作的事情。

3.3 V2 签名

原本使用上述方案,一切都是很美好的,然而在 Android 7.0 的时候,引入新的签名方式,APK Signature Scheme v2,致使 V1 上的多渠道签名方案失效。咱们先看看 V2 签名的原理。
按照官方文档来看,V2 签名是在 数据区和核心目录区之间,新增了一个 APK Signing Block 区块,用来记录签名信息。
签名先后的结构以下。

v2

APK Signing Block 格式以下:

description length content
size of block 8 bytes 签名块的长度
ID-value 保存了一系列 ID-value
size of block 8 bytes 签名块的长度,和第一个字段同样
magic 16 bytes 签名块标记,魔数

APK Signing Block 中,会对其余三个模块都进行签名,签名信息保存在 ID-value 中 ID 为 0x7109871a 对应的 value 中。
在校验签名时,会按照下面的逻辑进行。

v2-verify

首先检查是否包含 V2 签名块,若是包含 V2 签名块,就采用 V2 签名进行验证,若是没有包含 V2 签名块,就采用 V1 签名验证。
所以,采用 V2 签名进行验证时,V1 方案中添加 EOCD 注释和 META-INF 空文件的方式就都失效了。
看到这里,咱们会发现,V2 签名验证了其余三个模块数据,可是没有对 APK Signing Block 自己进行验证,而其中的 ID-value 是一组数据,因此能够在这里添加包含渠道信息的 ID-value。这就是 V2 签名下生成多渠道包的原理。 上述原理具体的代码,能够看 github.com/Tencent/Vas…github.com/Meituan-Dia…

3.4 V3 签名

在 Android 9.0 中引入了新的签名方式 V3。为了解决签名签名过时的问题。V3 签名在 V2 的 APK Signing Block 中新增了一个签名块,保存了 supported SDK 版本以及密钥流转结构。因为对 V2 结构没有进行大的更改,因此不会对多渠道打包方案形成影响。关于 V3 签名更具体的信息,能够查看官方文档

4、其余打包方式

4.1 Multiple APKs

上面说的 APK 结构和打包,都是完整包含了全部资源,包括适配不一样的分辨率,适配不一样的 API 等等,但其实在每个用户设备上,只会用到其中的一种,其他的资源只会占用包大小。因此 Google 也提供了一些其余方法,能够根据不一样分辨率,不一样 ABI 等等来打包,从而减小包大小。
在 Android L 以后,Android 支持了 Multiple APKs,简单来讲就是能够经过不一样分辨率,不一样 CPU 架构,不一样 API level 把同一个应用打包成对应不一样设备的多个 APK,而后在不一样的设备上安装不一样的 APK,来减小体积。
文档里提供的了详细的解释,咱们这里看一下,经过配置不一样分辨率,生成的 APK 是什么样的。
咱们先在 build.gradle 中添加下面的配置。

android {
  ...
  splits {

    // Configures multiple APKs based on screen density.
    density {

      // Configures multiple APKs based on screen density.
      enable true

      // Specifies a list of screen densities Gradle should not create multiple APKs for.
      exclude "ldpi", "xxhdpi", "xxxhdpi"

      // Specifies a list of compatible screen size settings for the manifest.
      compatibleScreens 'small', 'normal', 'large', 'xlarge'
    }
  }
}
复制代码

含义很清楚了,就很少解释了。而后咱们编译,能够看到,生成的 APK 不是一个,而是四个。

multiple-apks

其中 simpleapk-universal-debug.apk 是包含了全部资源的完整 APK。其余三个 apk 分别是根据不一样分辨率生成的。其中只包含了对应分辨率须要的资源。从而减小了包大小。咱们能够对比一下两个 APK 就看出区别了。

multiple-apks-compare

4.2 Split APKs

Android L 之后提供了 Split APKs 的多 APK 构建机制,能够将一个 APK 基于 CPU 架构,屏幕密度等纬度拆分红多个 APK,当下载应用时,会依据当前的设备配置选择对应的 APK 安装。
Split APKs 打包之后,会生成一个 Base APK 和多个 Split APK
Base APK 包含了通用的资源,而 Split APK 就是用户设备特有的资源。用户安装时会安装 Base APK 以及其设备对应的 Split APK,从而减小了其余无用资源的包大小。
咱们正常开发中不会生成 Split APKs,不过咱们能够打开 Instant Run 对应用进行编译,如今的 Instant Run 在 Android 5.0 设备上也是采用 Split APKs 的原理。
使用 Instant Run 之后,在 build/intermediates/split-apk/debug/slices/ 下面,能够看到生成的 Split APKs,咱们选择其中一个,在 manifest 文件中,定义了 split 标签,表示当前的 Split APK 编号。

instant-run-splits

在 build/intermediates/instant-run-apk/debug/ 下面,是 Base APK,包含一些公共的资源。

咱们在安装 Split APKs,可使用 adb install-multiple 以及 adb shell pm install-create,adb shell pm install-write,adb shell pm install-commit 等命令来进行。具体命令能够参考这里

4.3 App Bundle

App Bundle 是 2018 年 Google I/O 大会上引入的新的 App 动态化框架,是借助 Split APKs 原理完成的动态加载。
和传统安装方式对好比下。

app-bundle

App Bundle 依赖于 .aab 文件。一个 aab 的结构以下。

app-bundle-structure

base/ 中是通用的资源,feature1/ feature2/ 中是根据特定屏幕密度,CPU 架构等区分的特定资源。

咱们以 simpleapk demo 为例看一下 App Bundle 的使用。要使用 App Bundle,须要建立一个 base module 和 feature module。
咱们的 simpleapk 是 base module,而后建立了一个 feature module。这里要注意的是 feature module 须要选择 Dynamic Feature Module。

feature
建立 feature module 之后咱们看看其 build.gradle 文件,能够看到依赖了 base module。

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation project(':simpleapk')
}
复制代码

而后在 base module 里配置了 dynamicFeatures。

android {
    bundle {
        // ...
    }
    dynamicFeatures = [":dynamic_feature"]
}
复制代码

咱们在项目中执行 bundleDebug 命令,就能够看到,在 build/outputs/bunudle/debug 目录下就生成了 xxx.aab 文件,Google Play 会根据 xxx.aab 生成用户安装所须要的 APK 了。
.aab 本质也是一个 zip 文件,咱们解压看看。包里的内容以下。

aab

咱们能够经过 Google 提供的 bundletool 将 .aab 文件生成 APK。执行下面的命令。

java -jar bundletool-all-0.10.2.jar build-apks --bundle=build/outputs/bundle/debug/simpleapk.aab --output=bundle.apks    
复制代码

生成的 bundle.apks 也是一个 zip 文件,咱们也解压看看。

bundle-apks

能够看到,bundle.apks 里包含了两个目录,一个是 splits/,下面放的是 Base APK 和 Split APK,另外一个目录是 standalones/,下面放的是根据不一样屏幕密度生成的完整的 APK,用来在不支持 App Bundle 的设备上进行安装。

固然如今 App Bundle 的使用,彻底依赖于 Google Play,因此国内仍是没法使用的。不过对其原理的了解,咱们也能知道 Google 是如何区减小包大小的。

总结

summary

参考资料

Zip 文件格式

pkware.cachefly.net/webdocs/APP… blog.csdn.net/a200710716/…

签名

source.android.com/security/ap… source.android.com/security/ap… zhuanlan.zhihu.com/p/26674427

Android Build Tools

tech.meituan.com/2014/06/13/… tech.meituan.com/2017/01/13/… developer.android.com/studio/comm…

Multiple APKs

developer.android.com/studio/buil…

Split APKs

www.infoq.cn/article/2Mg… blog.csdn.net/ximsfei/art…

App Bundle

developer.android.com/guide/app-b… developer.android.com/studio/comm…

关于我

about
相关文章
相关标签/搜索