我们于2019年6月26号正式开源Qigsaw。 Qigsaw是爱奇艺自主研发的动态化框架,其核心优势如下:
Android动态化方案,在国内已蓬勃发展数年之久,其核心目的是减少应用包体积,提升应用安装率。Google在减少应用包体积上的探索也从未停息,下面我们一起来看看Google在这方面的努力。 Google减少应用包体积方案演进回首Android第一个10年,其应用发布方式如下。 从应用开发到上传应用商店,最后再到用户下载环节,参与产物都是APK。 您的应用将包含所有CPU架构so文件、所有屏幕分辨率资源文件以及所有语言资源文件,那么存在如下两个问题。
在国内,开发者一般都只会放一种CPU架构的so文件和一种屏幕分辨率资源文件,以此来减少包体积,但这种方式一定程度上会影响用户体验。 根据Google官方数据统计,从2012年至今,应用包体积平均增长了5倍左右,爱奇艺也不例外。 经过七年发展,爱奇艺越来越"膨胀"。 Google意识到包体积问题的严峻性,于Android 5.0推出Multiple APK,旨在减少安装包体积。 Multiple APKMultiple APK是Google Play提供一个功能,它允许您的应用针对不同的设备配置发布不同的APK。通过一张图来了解下其工作流程。 图中左边手机是nexus 5,右边手机是nexus 6p,它们的CPU架构、屏幕分辨率均不同,因此Google Play会根据当前设备配置下载对应APK。 Google提供打包配置选项,让开发者根据不同设备配置生成不同APK文件。
通过 上图中生成的产物,通过文件名我们可以很清楚知道该APK作用于何种配置的设备。 Android设备的多样性,导致Multiple APK并未朝着Google期待的方向发展。因为您有可能为每个版本构建数百个APKs,大大降低迭代效率。国外开发者对此也并不感冒,这也成为Google的一块心病。 Android App BundleAndroid App Bundle是一种全新的应用上传格式(.aab),它包含所有编译代码和资源。当您上传aab文件至Google Play后,Google Play将aab文件拆分成一系列APKs并签名。 此外,您也可以在应用项目中添加dynamic feature模块,这些模块并不需要在应用首次安装时一起被下载安装。您可以通过使用Play Core Libray在应用运行过程中动态安装dynamic feature。dynamic feature类似国内插件化提供的能力,但dynamic feature功能更强大。 通过上图,可以看到dynamic feature可以基于设备配置选取对应的Configuration Split APKs,如此可以进一步减小dynamic feature安装包体积。 更多关于Android App Bundle细节,请阅读官方文档,本文不再赘述。 Android App Bundle之所以能够支持应用运行期间安装dynamic feature,得益于Android 5.0推出的Split APKs功能。 Split APKsSplit APKs是Android 5.0引入的一种全新应用安装机制,其目的是为解决APK体积日益增大问题。Split APK可以将一个完整庞大的APK按照CPU架构、屏幕密度等维度拆分成多个独立APKs。当应用APK下载更新时,依据当前设备配置选取对应配置APKs安装即可。 Android 5.0之前,一个APK代表一个应用。在Split APKs问世之后,一个应用可能对应多个APKs。所有Split APKs拥有相同包名和签名。 Android提供两种方式安装Split APKs。
这里我们重点介绍第二种安装方式,Android 5.0提供PackageInstaller用于安装Base APK和Split APKs。 当第三方应用通过PackageInstaller在应用运行期安装Split APKs时,系统会启动安装器界面供用户选择是否安装此次更新。 在用户选择 在我们实际测试过程中,某些国产手机对PackageInstaller有改动,导致无法正常安装Split APKs。 系统应用可以静默安装Split APKs,且当Split APKs安装完成后,可以决定是否“杀死“应用进程。 public static class SessionParams implements Parcelable { ... /** {@hide} */ @SystemApi public void setDontKillApp(boolean dontKillApp) { if (dontKillApp) { installFlags |= PackageManager.INSTALL_DONT_KILL_APP; } else { installFlags &= ~PackageManager.INSTALL_DONT_KILL_APP; } } ... ... } SessionParams是PackageInstaller内部类, Split APKs加载应用进程所使用到的ClassLoader和Resources均在 通过Android 9.0 ClassLoader创建。 通过
该方法指明,应用进程是可以动态加载Split APKs代码。 Resources创建。 通过 关于更多Split APKs加载原理细节,请阅读相关Android源码。 Play Core Library文章开始介绍Qigsaw核心优势有提到,Qigsaw"山寨"Play Core Library公开接口实现,开发者阅读其官方文档即可开发。因此,在此主要介绍下Play Core Library工作流程。 当爱奇艺App在运行过程中,用户需要使用游戏插件,会经历以下过程。
在Android 7.0版本之前,当Split APK安装完成之后,应用无法立即使用Split APK。因此Play Core Library提供SplitCompat模式让App可立即使用Split APK。 爱奇艺动态化框架Qigsaw在2018年上半年,我们就进行动态组件化方案的调研。起初方案是基于Instant App方案实现,当整体功能基本实现后,Google于2018年Google IO大会上推出Android App Bundle。在调研Android App Bundle之后,我们发现Android App Bundle完全符合最初的需求。 依据我们最初设计初衷和Android App Bundle特点,总结出Qigsaw应满足以下核心特点。
关于私有Api访问应该是大家比较关心的,最近一段时间某大厂开源了号称零反射插件化框架,但是通过阅读其源码,我们发现它还是做了PathClassLoader的parent ClassLoader反射替换。另外它也调用了Resources构造方法创建Resources实例,虽然这样做并没有任何私有Api访问,但是通过查看Resources构造方法源码,我们可知该方法属于过时方法,且注释写明第三方应用不应该创建Resources实例。
所以插件化框架不应该仅仅以是否零反射为目标,我们应该从开发流程及产品形态选取合适方案,助力开发效率。 Qigsaw开发体验在开发阶段,开发者使用Android App Bundle原生开发套件即可开发调试Split APKs。 Android App Bundle为dynamic feature提供全新插件
在发布阶段,Qigsaw提供打包插件让开发者享受一条龙服务,开发者不必关心dynamic feature的上传分发。 Qigsaw打包插件支持内置dynamic feature,所有内置dynamic feature都会被拷贝至base apk的assets目录。对于非内置dynamic feature,Qigsaw打包插件会将其上传至CDN服务器,解决业务方后顾之忧。 Qigsaw原理Qigsaw借助Android App Bundle开发套件完成dynamic feature的打包,大大降低Qigsaw开发维护成本。因此Qigsaw关心的重点落在如果安装加载dynamic feature生成apk上。 第三方应用利用PackageInstaller安装split APKs体验极其不友好,且某些国产手机对split APKs功能支持不完善,所以我们最终还是按照一般插件化方式安装加载split APKs。 依据上图,如果需要动态加载split APKs,需要解决代码、资源以及四大组件的加载。 Split APKs代码加载针对splits代码加载,Qigsaw采用单类加载器方式,即base APK和split APKs采用同一ClassLoader加载。 在DexPathList中,为每个split创建对应的 Split APKs资源加载。Splits资源加载相较于代码加载会复杂,因为不同系统版本或不同手机厂商都会存在一些兼容性问题。 Android Gradle Plugin在资源打包时,会对 Id前两位 Id中间两位 Id后四位 所有第三方应用base APK资源Package Id均为7F,Android App Bundle对splits资源打包时会基于7F依次递减分配Package Id。因此,即使我们将split APKs资源添加到当前应用Resources实例中,也不会出现资源冲突问题,splits访问base资源也更加方便。
通过Android App Bundle解决splits资源打包问题,那么splits资源如何加载呢?我们来看一段代码。 Qigsaw提供 Split APKs四大组件加载Android App Bundle在Manifest文件合并过程中,会将split APKs manifest文件内容合并至base APK中。因此,所有split APKs四大组件信息都是已经声明在base APK中。 Android App Bundle这种处理方式不支持Manifest更新,例如新增四大组件,所以Qigsaw也不支持新增四大组件。在正常开发迭代过程中,动态新增splits四大组件需求极少,所以Qigsaw与Android App Bundle特性保持一致。 Split APKs安装过程前文我们介绍了Play Core Library是如何安装、加载split APKs,Qigsaw安装、加载split APKs与Play Core Library类似。首先,通过一张图来了解。 在爱奇艺App运行过程中,当
Qigsaw下载、安装split APKs均在主进程处理,split APKs的加载则发生在 Qigsaw拓展功能在实际开发过程中,Android App Bundle所支持的功能特性并不满足我们需求。因此,Qigsaw在Android App Bundle基础上拓展了几个功能。
在此,我们首先介绍Qigsaw多进程功能。以下图场景为例。 依据Qigsaw安装、加载split APKs原则,当游戏APK安装完成后,就会在主进程完成加载。在游戏APK中有两个Activity,他们所处进程不同。当启动 为解决这类问题,Qigsaw提供了如下解决方案。
第一种方案解决的场景是 第二种方案解决的场景是 Hook PathClassLoader具体做了如下事情。
国内很多App都接入Tinker用于修复线上bug,爱奇艺同样也接入。Qigsaw本身提供热更新能力,但在实际开发过程中发现,Qigsaw能借助Tinker Patch热更新split APKs,提升开发效率。 Qigsaw在打包过程中会生成关于包含split信息的 json文件记录的内容如下。
该文件记录着splits版本号以及下载地址,如果Tinker开启资源修复,我们就可以通过tinker patch更新该json文件,以此达到热更新splits目的。 Qigsaw的未来希望有你参与Qigsaw 在 2019 年 1 月正式在爱奇艺 App 上线,半年间经过数亿用户验证,由 Qigsaw 引起的崩溃率占总崩溃率已不足千分之一。在爱奇艺 App 中,小程序以及小游戏框架均由 Qigsaw 动态加载,目前已推广至全公司五个业务线团队使用。2019 年 6 月 26 日,Qigsaw 正式对外开源,我们希望有志之士能为 Qigsaw 贡献一己之力,共同完善 Qigsaw 生态,让国内更多开发者体验到 Android App Bundle 的快感。 最后,如果您认可Qigsaw,欢迎大家献上自己的小星星,star关注我们吧! |