AAB 扶正!APK 将退出历史舞台
Google 自8月起要求 Google Play 上架的应用必须采用 AAB 的新格式,对我来说这并非新闻,早在去年12月份官方就提前做了通知:
https://android-developers.googleblog.com/2020/11/new-android-app-bundle-and-target-api.html
令我惊讶的是,这样一条“旧闻”最近却被炒得沸沸扬扬,原来竟还是因为蹭了鸿蒙的热度:
要知道 AAB 的首次亮相是在2018年的 GoogleI/O 上,难道彼时谷歌就遇见到鸿蒙的出现了?
不过客观来说,AAB 虽然早已出现,但在国内很少被提及,因此造成部分媒体的错误解读也有情可原。那么本文就为大家做一个关于 AAB 的科普,打消鸿蒙支持者们的顾虑。
Android App Bundle
Android App Bundle(简称AAB) 是 Google 2018年推出的一种动态化的打包方式。当应用程序以 AAB 的格式上传 Google Play(或其他支持 AAB 的应用市场)后,可以根据不同用户实现 features 或者 resources 的按需下发。Google Play (简称GP) 目前提供的动态化服务都是基于 AAB 实现的(不少文章说这些服务是 AAB 的,这种说法不严谨,准确的说是 GP 的)
- Play Feature Delivery(PFD) :借助 AAB 实现 Feature 的按需动态加载,这类似于国内流行的“插件化”技术
- Play Asset Delivery (PAD) :借助 AAB 实现一些资源素材的按需动态下载,这特别适合一些游戏类APP,无需为了适配所有机型保留全部游戏素材
除了游戏资源以外,对于常规资源,AAB 也可以做到按需下发。例如无需同时存在 hdpi、xhdpi 等多套图片,不少 APP 因此在包大小方面有显著提高:
更小的包体积意味着更高的装机率,这在用户推广成本激高的今天至关重要:
App Bundle 文件格式
我们先来看一下 AAB 的文件格式,与传统的 APK 有何不同
解压后的 AAB 中的内容和 APK 很相似,但又有不少区别:
aab fiels | descriptions |
---|---|
base/feature1/feature2 | base 是应用的基本功能,feature 承载各 DynamicFeature 的内容(后文介绍) |
manifest.xml | APK 中只有一个 manifest 且是二进制格式,AAB 会存在于每个模块中e中,且使用 ProtoBuf(pb)格式,便于处理 |
dex | 与 APK 不同,AAB 将每个模块的 dex 文件存储在各自目录中 |
res/assets/libs | 该目录与 APK 中相同,当上传 AAB 时,GP 会检查这些目录并仅打包满足目标设备需要的最小文件 |
resources.pb | 类似于 resource.arsc 文件,是一个资源索引表,其中描述了应用程序内部存在的资源和目标的细节,可用于 GP 针对不同设备配置 APK。 |
assets.pb | 相当于应用程序 assets 的资源表,可用于 GP 针对不同设备配置 APK。例如将 assets 资源放到 assets/languages#lang_xx 或 assets/i18n#lang_xx 路径下,则会根据语言配置下发 assets 资源。 |
native.pb | 这相当于native库的资源表,可用于 GP 针对不同设备配置 APK |
后三个.bp
文件是 AAB 格式的重要部分,它们描述了 APP 的不同服务目标,动态下发根据这些目标从 drawable/hdpi
、lib/armeabi-v7a
或者 values/es
等路径中组织不同资源进行下发。
Split APKs
Split APKs 机制是 AAB 实现动态下发的基础,它允许将一个庞大的 APK 按不同维度拆分成独立的 APK,当用户在 GP 下载应用时,Android Framework 通过 IPC 与 GP 通信,为当前设备匹配并下载最小构成的 APK, 这只在 Android 5.0 以上的设备才有效。
AAB 上传后,GP 通过分析找出所有设备的共同资源, 生成一个 Base APK,当用户下载应用时,Base APK 将被首先安装。
GP 又根据language
、density
、abi
等三个维度,分别生成 Configuration APKs(Splits), Splits 与 Base 共享 versionCode 、packageName等,在进程管理器中以一个应用的形式存在。
当用户从市场下载应用时,GP 根据设备类型,为其下发不同的 Splits,实现最小化下发。
如下图,针对三种不同设备下发不同 Splits
当用户的设备发生 Configuration Changed (比如切换了系统语言)时,GP 会下发新的 Splits 到手机,如果此时手机不在线会等待下次上线时自动下发。
Split APKs 的这种动态下发只能用于 Android 5.0 以上设备,对于更旧的设备,AAB 会根据 这些 Splits 的矩阵生成多个 Standalone 的 APK,虽然缺少了动态下发的能力必须一次安装到位,但是相对于传统 APK 仍然减小了一定包大小。
作为开发者,我们无需关心这些具体的下发策略,只需要向市场上传一个 AAB ,后续就交给 FW 和 GP 去处理了。
创建 App Bundle
打包 AAB
使用 Android Studio 可以方便地打包 AAB
此外,也可以使用 Gradle 命令打包,这更适用于一些 CI 流程中。
如下使用 gradle 打包一个 debug 版的 AAB
./gradlew :base:bundleDebug
如果要生成 release 的 AAB 需要配置签名,与 APK 的配置方式是一样的。
AAB 默认会为三种 Configurations 都生成 Splits,当然你可以根据需求自己配置:
bundle {language {enableSplit = false}density {enableSplit = true}abi {enableSplit = true}}
上传应用市场
生成 AAB 后就可以上传应用市场了,GP 中上传 AAB 和 APK 的入口在一起,当然 8 月以后就没有 APK 的上传入口了。
AAB 上传后,通过后台可以查看其详细信息
例如可以查看 AAB 支持的屏幕密度,以及包体积的减少等信息
Bundle Tool
AAB 是无法直接安装到手机的,如果想本地对 AAB 做测试,需要将 AAB 转成 APK,这需要使用 Google 官方提供的 Bundletool 工具
Bundletool 可以获取当前设备信息
bundletool get-device-spec --output=/tmp/device-spec.json
设备的 Configurations 信息输出到指定 json 中
{"supportedAbis": ["arm64-v8a", "armeabi-v7a", "armeabi"],"supportedLocales": ["zh-CN"],"deviceFeatures" : // ..."screenDensity": 480,"sdkVersion": 28
}
Bundletool 根据 json 生成 .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
--device-spec=file:device-spec.json
apks 的产物分为 splits
和 standalones
两个目录,splits 是按照 Configuration维度拆分的 Split APKs,必须依赖 base.apk 一起安装;standalone 必须独立安装,这是为了兼容 Android 5.0 以下的版本。
toc.pb
是 apks 的存档清单,包含 APK 集合信息的描述文件
然后再根据 json 文件,从 apks 中提取 apk :
bundletool extract-apks
--apks=${apksPath}
--device-spec={deviceSpecJsonPath}
--output-dir={outputDirPath}
最后,通过 Bundletool 将 apk 安装到手机上。 注意该命令实际安装 apk 并非 apks
bundletool install-apks --apks=/MyApp/my_app.apks
总结一下 Bundletool 生成 APK 的整体流程:
创建 Dynamic Feature
除了下发 Configuration APKs,还可以以业务模块为单元“插件化”地动态下发,也就是所谓的 Dynamic Features(简称 DF)
IDE 中选择 New 一个 DF 的 Module:
点击 next,选择 DF 的安装时机,例如一次安装到位或是按需安装
创建好的 DynamicFeature Module, 目录和一个普通的 Gadle Module 类似
但是 build.gradle 中 plugin 有所不同:com.android.dynamic-feature
plugins {id 'com.android.dynamic-feature'id 'kotlin-android'
}
build.gradle 中也无需配置 versionCode
、versionName
、signConfig
等,DF 本质上也是 Split APKs,所以共享 Base APK 的这些信息。
此时再打开 app/ 的 build.gradle,会发现多了如下配置
dynamicFeatures = [':dynamicfeature']
这是 APP 当前支持的所有 DF 的声明
最后,DF 的 Manifeset 也发生了变化:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:dist="http://schemas.android.com/apk/distribution"package="com.github.dynamicfeature"><dist:moduledist:instant="false"dist:title="@string/title_dynamicfeature"><dist:delivery><dist:on-demand /></dist:delivery><dist:fusing dist:include="true" /></dist:module>
</manifest>
- dist:delivery: 在创建 Dynmaic Feature 的 Module 时选择的下发方式, onDemand 表示方式为按需下发
- title:当用户确认下载 Module 时,标识相关名称
- fusing include:设为 ture,意味着 5.0 以下的设备可以以 multi-APK 的形式安装此 Feature,此时必须设置为 onDemand 方式。
安装 Dynamic Feature
当应用支持 DF 之后,我可以按需的请求并安装这些 Features,这需要集成 Play Core SDK
implementation 'com.google.android.play:core:$latest_version'
Play Core 允许用户通过交互的方式请求 DF 的下载安装,并监听下载状态
发起下载请求
DF 的下载需要借助 SplitInstallManager
SplitInstallManager splitInstallManager =SplitInstallManagerFactory.create(context);
创建 SplitInstallRequest
, 请求下载 Module
//动态请求模块
SplitInstallRequest request =SplitInstallRequest.newBuilder().addModule("someDynamicModule").build();
addModule()
可以多次调用,添加多个请求的 DF
使用 SplitInstallManager
启动 Request 进行请求,并设置回调监听为下载状态
splitInstallManager.startInstall(request).addOnSuccessListener { }.addOnFailureListener { }.addOnCompleteListener { }
startInstall()
调用后会立即发起请求。另外还可以使用 deferredInstall
延迟请求, 当应用切到后台启动时才开始请求。
splitInstallManager.deferredInstall(Arrays.asList("someDynamicModule"));
除了请求指定 DF 以外,也可以请求指定的资源,比如安装语言资源
SplitInstallRequest request =SplitInstallRequest.newBuilder().addLanguage(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION))).build();
发起请求后,会返回一个 Int
值作为 session ID,通过调用 cancelInstall(Int)
, 可以取消当前的下载。
发起请求后,可能无法正常建立链接,此时会返回错误信息如下
Error | Descriptions |
---|---|
ACCESS_DENIED | 鉴于当前设备的某些原因,无法下载 |
ACTIVE_SESSIONS_LIMIT_EXCEEDED | 当前应用的请求 session 太多 |
API_NOT_AVAILABLE | 请求 API 目前无法使用 |
INCOMPATIBLE_WITH_EXISTING_SESSION | 请求的 session 中包含了已经请求中的 DF |
INTERNAL_ERROR | 内部错误 |
INVALID_REQUEST | 无效请求 |
MODULE_UNAVAILABLE | 请求的 DF 不存在 |
NETWORK_ERROR | 网络错误 |
NO_ERROR | 无法获得错误信息 |
SERVICE_DIED | 服务无响应 |
SESSION_NOT_FOUND | 无法获取被请求的 session |
下载安装
成功建立了连接后,便进入下载、安装阶段。使用 SplitInstallStateUpdatedListener
能够监听下载安装的状态,可以根据这些状态为对下载进度等进行用户提示
val stateListener = SplitInstallStateUpdatedListener { state ->when (state.status()) {PENDING -> { }DOWNLOADING -> { }DOWNLOADED -> { }INSTALLED -> { }INSTALLING -> { }REQUIRES_USER_CONFIRMATION -> { }FAILED -> { }CANCELING -> { }CANCELED -> { }}
}
splitInstallManager.registerListener(stateListener)
State | Description |
---|---|
CANCELED | 下载被取消 |
CANCELING | 下载取消中 |
DOWNLOADED | 下载完成,但是尚未安装 |
DOWNLOADING | 下载即将完成 |
FAILED | 下载或安装失败 |
INSTALLED | 成功安装 |
INSTALLING | 安装中 |
PENDING | 下载等待中 |
REQUIRES_USER_CONFIRMATION | 等待用户确认 |
UNKNOWN | 未知 |
卸载模块
成功安装后,通过 getInstalledModules
可以获取所有已安装的 Module
val installedModules = splitInstallManager.installedModules
另外,通过 deferredUninstall
可以对 DF 进行指定卸载
splitInstallManager.deferredUninstall(listOf("someDynamicModule")).addOnSuccessListener { }.addOnFailureListener { }.addOnCompleteListener { }
AAB 使用效果
根据 Google 官方的数据,AAB 比 APK 的包大小平均会减小 20% ,这同时意味着节省了 20% 的下载流量。 以 Twitter
为例,采用 AAB 之后
- language 相关资源节省 95%
- density 相关的 Splits 节省 45%
- abi 相关资源节省 20%
除了包大小方面的优势以外,使用 AAB 在开发效率上也有收益,无需再针对不同目标点设备,配置多个 Flavor、生成多个 APK 并分别上传,只要上传一个 AAB,剩下的事情交由应用市场去做就好了。
国内的 AAB 使用
Qigsaw 是爱奇艺提供的一套基于 Android App Bundle 的动态化方案,无需 Google Play Service 即可在国内体验 Android App Bundle 开发工具。它支持动态下发插件 APK,采用单类加载器方式,让应用能够在不重新安装的情况下实现动态安装插件。
此外,华为应用市场也早就支持了 AAB 的上传和动态下发,所以不要再说 AAB 是打压华为的产物了
AAB 扶正!APK 将退出历史舞台相关推荐
- PHP即将退出,PHP4即将退出历史舞台
http://www.phpeye.com/bbs/viewthread.php?tid=92 PHP官方团队在php.net上发布了一则公告: Today it is exactly three y ...
- 互联网晚报 | 04月05日 星期二 | 考研调剂系统明日开通;微软Edge浏览器宣布10天后退出历史舞台...
微软Edge浏览器宣布10天后退出历史舞台 4月5日消息,在愚人节当天,微软Edge浏览器官方微博发布重要通知:"对所有 Edge浏览器的使用者.爱好者说一声抱歉" .IE浏览器将 ...
- 感谢3G,告别3G:开启移动互联网时代的“功臣”退出历史舞台
整理 | 章雨铭 责编 | 屠敏 出品 | CSDN(ID:CSDNnews) 移动互联网时代由3G开始,时过境迁,5G时代已经到来,3G这位开启移动互联网时代的功臣将要退出历史舞台. 图源视觉中国 ...
- 为什么说 Web 开发永远不会退出历史舞台?
早在 PC 崛起之际,Web 从蹒跚学步一路走到了主导市场的地位,但是随着移动互联网时代的来临,业界曾有不少人猜测,"Web 应该被杀死,App 才是未来".不过时间是检验真理的唯 ...
- Windows 7 彻底退出历史舞台
整理 | 禾木木 出品 | CSDN(ID:CSDNnews) 今日一条 #Windows7将彻底退出历史舞台#的话题冲上了热搜,引发了网友们的热议. 1 月 10 日开始,微软将不再为专业版和企业版 ...
- 雷军卸任小米软件技术公司董事长;微软洽谈向 OpenAI 投资 100 亿美元;Windows 7 彻底退出历史舞台|极客头条
「极客头条」-- 技术人员的新闻圈! CSDN 的读者朋友们早上好哇,「极客头条」来啦,快来看今天都有哪些值得我们技术人关注的重要新闻吧. 整理 | 梦依丹 出品 | CSDN(ID:CSDNnews ...
- 2G、3G要退出历史舞台了?为何3G比2G淘汰更快?
进入2020年,我国加速推进5G网络商用,加上4G的快速普及,2G.3G网络现在到了该被淘汰的时候了吗?全国超过3亿的2G.3G用户怎么办? 我国5G商用牌照发布已满一周年.一年以来,我国5G用户规模 ...
- 腾讯soso退出历史舞台,搜索结果全部由搜狗提供
腾讯soso退出历史舞台,搜索结果全部由搜狗提供 前段时间腾讯4.48亿美元战略投资搜狗搜搜注入搜狗,现在soso的搜索结果已经全部由搜狗提供了,上图哈:
- 时隔27年,微软IE浏览器正式退出历史舞台,一个时代的结束
关注并星标 从此不迷路 计算机视觉研究院 公众号ID|ComputerVisionGzq 学习群|扫码在主页获取加入方式 计算机视觉研究院专栏 作者:Edison_G 很遗憾「目睹」IE 浏览器退出历 ...
最新文章
- 宝塔的服务忽然挂掉解决方法
- delphi中checkcombobox最大长度_并行光信号传输中的信道间传播时间偏差
- 荣耀v30pro搭载鸿蒙吗,荣耀麒麟30pro+,可以搭载鸿蒙的顶级荣耀,你买了吗?...
- [Go] 正则表达式 示例
- SAP BRF+ Interpretation模式与Generation模式的区别
- 断言、触发器、存储过程
- 工业交换机的几大“择机”标准,你学会了吗?
- Win32ASM学习[13]:移位指令SHL,SHR,SAL,SAR,ROL,ROR,RCL,RCR,SHLD,SHRD
- 云版 Android 系统来了?
- IBM将剥离传统IT基础设施部门;迅雷前CEO陈磊涉嫌职务侵占罪被调查 ;Python 3.9发布|极客头条
- Windows Mobile获取通话记录 C#
- 基于Chrome内核(WebKit.net)定制开发DoNet浏览器
- 从我那句名言“系统上线之日,需求开始之时”谈大型信息化系统建设的那些坑
- ChatGPT|微信快速接入ChatGPT
- 带你入门NoSQL(真的是太全了)
- ROS IDE —— RoboWare Studio
- mac设计师系列 Adobe “全家桶” 15款设计软件 值得收藏!
- 什么是JAVA中的强制类型转换
- 五、MUX-VLAN QinQ技术
- 普通石粉的用途_地面铺水泥,用石粉做底层有什么作用
热门文章
- 【CSS】987- 十几个 CSS 高级技巧汇总
- 激活window10专业版的方法。
- unity 加载关卡_Unity5.0_Application.isLoadingLevel 正在加载关卡_软件教程_资源库
- 前端之vue3的setup和setup的2个的形参、响应式页面
- 在线版区间众数 hzw的代码。。
- NXP Nfc模块Framework层移植遇到的坑【二】
- 2010年IT十大人物猜想
- Docker入门详解
- python访问陌生人qq空间_使用Python+Selenium模拟登录QQ空间
- 【评测】无血清细胞冻存液