- 第一个是:从 2019 年 8 月 1 日 开始,在 Google Play 上发布的应用必现支持 64 位架构,这个意味着上架的应用要包含多份架构的 SO 包,导致应用大小大幅度增加,比如项目中以前只有 armeabi-v7a 一种 CPU 架构,但是为支持 64 位,增加 arm64-v8a 架构,会导致 APK 增加 13M
- 更新应用的时候,如果还是使用 APK 格式,则会有未优化的警告,强烈提议使用 App Bundle 格式更新应用;
- 什么是AAB
- 使用 App Bundle 格式对项目配置有哪些要求;
- 如何在Google Play 开启支持 App Bundle 格式发布应用;
- 采用 App Bundle 格式之后,如何使发包流程自动化;
- 如何对aab包进行本地验证;
- App Bundle 的注意事项。
首先大家可以这么理解:Google 官方的插件化方案。Android App Bundle = Apk 动态打包,动态组件化的技术,与 Instant App 不同,AAB 是借助 Split Apk 完成动态加载,使用AAB动态下发方式,可以大幅度减少应用体积,总结下特性,就是2个:
- 资源选择性加载
- 动态下发 lib
资源选择性加载
此前的android项目因为要适配各种不同硬件和配置,我们有不同 dp 密度的图片文件,语言文件,不同指令集的 so 等,现在 Android App Bundle 来了可以优化这个问题,当然只限于 GooglePlay,在 google 市场我们下载 apk 时,市场先会检测我们手机的特性,然后选择最合适的资源打成 apk 再发给我们,比如我的手机只需要 arm-v7 的 so,hdpi 的图片,剩下的不会下发我们,这样可以大幅度减少 apk 的体积,尤其是对于 so 来说,一个高德地图,不同的 so 包加起来有小 10M 了,这比我们之前自己干的 apk 优化压缩可强多了,可惜Google 服务国内用不了,不知道后续国内会不会跟进,哭脸.jpg。
动态下发库
其实就是Google用了此前国人的动态加载技术,App Bundles 将一个 apk 拆分成多个 apk,我们的 apk 一般会被拆分为如下几个部分:
- Base Apk:首次安装的apk,公共代码和资源,所以其他的模块都基于Base Apk;
- Configuration APKs:native libraries 和适配当前手机屏幕分辨率的资源;
- Dynamic feature APKs:不需要在首次安装就加载的模块。
当然啊注意 AAB 并不是一个插件化框架,它利用的是 Android Framework 提供的 split apks 技术来完成的,而所有安装 split apk 工作均是通过 IPC 交由 google play 完成,而不是国内插件化技术的反射代理 hook, 想了解更多关于split apks的内容,请看 官方文档
项目配置有哪些要求:- Android Studio 3.2 以上版本
- gradle 版本要大于:3.2.1,比如:
classpath
'com.android.tools.build:gradle:3.2.1'
可使用Android studio提供的工具制作aab包
Android App Bundle 与 APK 不同,App Bundle 纯粹是为了上传文件而设计的,用户无法直接安装和使用它,.aab 只是一个 zip 文件,Google Play 从中生成优化的 APK 并将其提供给设备进行安装。咱们可以看看 .abb 中的内容:
如果有新的可动态部署的库,会以 apk 的形式添加进来,具体请看 google 官方号:如何获得更小的应用文件尺寸?来了解下 Android App Bundle
在Google Play 开启支持 App Bundle 格式发布应用(对此不感兴趣的可以跳过此内容) 在 Google Play 后台为应用开启应用签名由于 App Bundle 实际是采用独有的 AAB 后缀格式【实际也是一个 ZIP 包】,当用户实际从 Google Play Store 点击安装应用的时候,再根据用户的设备信息 GP 后台服务器使用 bundletool 工具生成符合这个设备的 *.apks 文件,流程如下:
GP 安装应用流程
从 Google Play 后台的某个应用页面的菜单:版本管理 --> 应用签名,可以查看一个应用是否已经开启应用签名,如下是未开启的状态:
应用签名入口
当然开启方式也是蛮简单的,推荐使用第一种方式:上传从 Android Studio 导出的密钥,这种方式操作极其简单,步骤如下:
- 选择 build --> Generate Signed Bundle or APK --> 勾选 Android App Bundle,然后下一步;
- 下一步之后,填好你的签名密钥信息之后,勾选:Export encrypted key for enrolling published app in Google App Signing,选择好输出路径,点击 `Next` 打包完成之后即可获得一个:private_key.pepk 文件
- 把这个文件上传到 GP 后台即可开启成功
导出应用签名私钥
上传应用签名私钥
已经成功开启的状态则如下:
通常我们通过Jenkins打出apk包,对于Android App Bundle除了使用Android studio打出aab包,也可通过运行Gradle task执行bundleRelease,获取生成的aab文件
从网上找到现成的task代码,可使流程简化,并支持资源混淆功能
/**
* 构建 aab 格式内部测试包,含环境入口切换,完成后会拷贝到根目录下的 buildOutTemp 文件夹中
*/
task buildAabAlpha(dependsOn: [
'bundleAlphaRelease'
]) {
doLast {
this
.findAabAndCopy()
}
}
/**
* 构建 aab 格式生产环境包,不含测试入口,完成后会拷贝到根目录下的 buildOutTemp 文件夹中
*/
task buildAabRelease(dependsOn: [
'bundleRelease'
]) {
doLast {
this
.findAabAndCopy()
}
}
/**
* 查找打包输出的 aab 文件,并拷贝到项目根目录下的 buildOutTemp 文件夹
*
* @return
*/
def findAabAndCopy() {
// 1. 获取 aab 文件输出文件夹
def bundleOutPutDir =
new
File(
"${buildFile.getParent()}/build/outputs/bundle"
)
printlnInfo(
"打包出包目录:$bundleOutPutDir"
)
if
(!bundleOutPutDir.exists()) {
printlnInfo(
"打包出包目录不存在"
)
return
}
// 2. 查找文件夹下的 aab 安装包
List aabFileList = findAabWithDir(bundleOutPutDir)
// 3. 拷贝 aab 文件到目标文件夹
copyAabToDir(aabFileList)
}
/**
* 拷贝 aab 文件到目标文件夹
*
* @param aabFileList
*/
def copyAabToDir(List aabFileList) {
if
(aabFileList ==
null
|| aabFileList.isEmpty()) {
printlnInfo(
"没有找到需要拷贝的 aab 安装包"
)
return
}
String needCopyAabFileName = getGuardAabFileName()
File sourceAabFile =
null
aabFileList.each {
if
(it.getName().endsWith(needCopyAabFileName)) {
sourceAabFile = it
return
}
}
if
(sourceAabFile ==
null
) {
return
}
printlnInfo(
"需要拷贝的安装包文件:${sourceAabFile}"
)
def outTempDir =
new
File(
"${rootProject.getProjectDir().getAbsolutePath()}/PackageOutTemp/"
)
def outAabFileName = getFinalOutPackageName()
// 如果文件夹存在,则先删除
outTempDir.deleteDir()
copy {
from sourceAabFile.getAbsolutePath()
into outTempDir.getAbsolutePath()
rename {
outAabFileName.replace(
"*.aab"
, outAabFileName)
}
}
def copyAabFilePath =
new
File(
"${outTempDir.getAbsolutePath()}/${outAabFileName}"
)
if
(!copyAabFilePath.exists()) {
throwError(
"aab 文件拷贝失败,请检查原因!"
)
return
}
printlnInfo(
"aab 文件拷贝成功:${copyAabFilePath}"
)
}
/**
* 获取最终输出的安装包的文件名
*
* @return
*/
def getFinalOutPackageName() {
def versionInfo =
"${android.defaultConfig.versionName}_${android.defaultConfig.versionCode}"
return
"app_${versionInfo}_${currentTime()}_release.aab"
}
/**
* 获取指定文件夹下的 aab 安装包文件,包含子文件夹
*
* @param targetDir
*/
def findAabWithDir(File targetDir) {
List aabFileList = []
targetDir.listFiles().each {
if
(it.isDirectory()) {
List childAabFileList = findAabWithDir(it)
if
(childAabFileList !=
null
&& !childAabFileList.isEmpty()) {
aabFileList.addAll(childAabFileList)
}
}
else
if
(it.getName().endsWith(
'.aab'
)) {
aabFileList.add(it)
}
}
return
aabFileList
}
def currentTime() {
return
new
Date().format(
"yyyyMMdd_HHmm"
)
}
def getGuardAabFileName() {
if
(isEnableAabResGuardPlugin()) {
return
"duplicated-app.aab"
}
return
"aab"
}
// 判断是否开启 aab 资源混淆插件,开关在根项目的 gradle.properties 中配置
def isEnableAabResGuardPlugin() {
def mapPros = rootProject.getProperties()
if
(mapPros ==
null
|| mapPros.size() ==
0
) {
return
false
}
def key =
"enableAabResGuardPlugin"
return
mapPros.containsKey(key) && mapPros.get(key) ==
"true"
}
if
(!isEnableAabResGuardPlugin()) {
printlnInfo(
"目前没有开启 aab 资源混淆插件"
)
return
}
printlnInfo(
"目前已经开启 aab 资源混淆插件"
)
def throwError(String content) {
throw
new
RuntimeException(
"[abb 包拷贝处理异常]:${content}"
)
}
def printlnInfo(Object value) {
println(
"[abb 包拷贝处理]:$value"
)
}
// 引入字节跳动的 aab 资源混淆插件
apply plugin:
"com.bytedance.android.aabResGuard"
aabResGuard {
//mappingFile = file("mapping.txt").toPath() // 用于增量混淆的 mapping 文件
/**
* 白名单列表
* 1、Google 相关的配置字符串;
* 2、私聊页面,表情面板相关的资源
*/
whiteList = [
"*.R.raw.*"
,
"*.R.drawable.icon"
]
obfuscatedBundleFileName = getGuardAabFileName()
// 混淆后的文件名称,必须以 `.aab` 结尾
mergeDuplicatedRes =
true
// 是否允许去除重复资源
enableFilterFiles =
true
// 是否允许过滤文件
filterList = [
// 文件过滤规则
"META-INF/*"
]
enableFilterStrings =
false
// 过滤文案
unusedStringPath = file(
"unused.txt"
).toPath()
// 过滤文案列表路径 默认在mapping同目录查找
languageWhiteList = [
"en"
,
"ar"
,
"de"
,
"es"
,
"fr"
,
"in"
,
"ja"
,
"ko"
,
"ms"
,
"pt"
,
"ru"
,
"th"
,
"tr"
,
"vi"
,
"zh-rCN"
,
"zh-rTW"
,
"hi"
]
// 保留en,en-xx,zh,zh-xx等语言,其余均删除
}
可通过bundletool工具来测试你的.aab文件
bundletool-all-0.6.0.jar build-apks --bundle=/MyApp/my_app.aab --output=/MyApp/my_app.apks
这个命令构建出来的是没有签名的文件,要有签名的,需要如下:
bundletool build-apks --bundle=/MyApp/my_app.aab --output=/MyApp/my_app.apks
--ks=/MyApp/keystore.jks
--ks-pass=file:/MyApp/keystore.pwd
--ks-key-alias=MyKeyAlias
--key-pass=file:/MyApp/key.pwd
关于App Bundle 的一些注意事项
当打开on-demand(按需加载)时,必须开启Fusing(熔断操作)才能正常的让Api21以下的手机使用module
一般情况下,动态模块下发之后需要重启App才能加载成功,但是如果你使用SplitCompat library,就可以立即生效,Access code and resources from downloaded modules
如果下载的模块太大,需要用户确认,GP要求大于10MB需要用户确认
国际上可以使用Google Play的Play Core Lib直接从gp后台下载我们上传好的dynamic module.
module中的AndroidManifest中定义的Activity不能有exported:true因为别的app不知道你何时安装好模块从而会引发问题
proguard文件在生效的时候会merge base module和所有的dynamic module中的文件,所以在编写proguard的时候要注意这个问题。
另外以下内容来自谷歌官方公众号的对App Bundle 常见问题解答
Q: 如何构建 Android App Bundle?
Gradle 和 Buck 现在都支持 App Bundle。您可以构建 App Bundle 的系统包括 Android Studio 3.2+ (而且操作非常简单: 只需从 Android Studio 的 Build 菜单中选择 Generate Signed Bundle 即可)、Unity 2018.3 和 2017.4.17 或更高版本,以及 Cocos Creator 2.0.9 或更高版本。
Q: 如何测试由 Android App Bundle 生成的应用?
在日常的新功能开发以及测试/修复工作中,您可以像以前一样继续在本地构建和安装 APK。如果您有兴趣具体测试分发到每台设备中的内容的话,请使用内部应用分享功能 (internal app sharing) 或任何 Play 测试轨道,从而使用 Play 来分发需要测试的应用。简而言之,内部应用分享可以让您快速轻松地分享测试版本: 您只需将应用上传到 Play,获取可以共享的 URL,然后使用该 URL 下载并测试应用即可。您无需担心版本代码或签名密钥,而且您的测试应用是可以调试的。
-
Internal app sharing
https://support.google.com/googleplay/android-developer/answer/9303479
Q: 通过尚未支持 App Bundle 的非 Play 渠道分发应用的最佳方法是什么?
您可以在 Android Studio 的 App Bundle 功能中本地生成已签名的通用 APK,或使用 bundletool 将应用发布在非 Play Store 分发渠道,从而确保其可以安装在任何配置的 Android 设备上。请注意,像 Google 的 Files Go、ShareIt 和 Xender 这样的 P2P 共享应用支持共享 App Bundle 发布的应用。我们正在与其他发行渠道合作,确保其能正确安装 App Bundle。
Q: 我是否必须选择通过 Google Play 进行应用签名?
是的。App Bundle 是 Google Play 里的一种发布格式,用于生成实际传送到设备的内容。与其他一些主要应用商店一样,Google Play 会保留签名密钥,从而给实际分发给设备的内容进行签名。Google 将应用签名密钥存储在用于保护 Google 自己密钥的高度安全的服务器中。所有这些都可以节省您的时间,并为您带来 App Bundle 的诸多好处——您只需将一个 App Bundle 上传,其余的事情交给 Play。
Q: 如何模块化我的应用并使用自定义交付选项?
您可以在我们的动态交付文档中了解有关模块化应用的更多信息,其中包括代码实验室和示例。您可以选择在安装时交付模块,并在不需要时卸载它们;也可以根据设备功能、用户所在国家/地区或 SDK 版本等条件,在安装时提供功能。您还可以按需提供功能,并在需要时或后台运行时安装它们。按需提供功能可以作为应用的长期策略的一部分,从而防止在添加新功能时增加应用体积。
参考文章:
新姿势来啦 - Android App Bundle 新姿势来啦 - Android App Bundle - 知乎
应用支持谷歌官方 App Bundle 格式探索之路 应用支持谷歌官方 App Bundle 格式探索之路 - 知乎
16 个使用 App Bundle 发布应用和游戏的理由 16 个使用 App Bundle 发布应用和游戏的理由