在Android开发行业里,插件化已经不是一门新鲜的技术了,早在12年就已经被提出,发展到今天已经逐渐趋于成熟,各大厂商基本都有自己的一套插件化方案。
京东的插件化框架Aura起始于2014年,相比于现在Android平台上的插件化框架百花齐放的盛况,在当时插件化框架可谓是凤毛麟角,可供参考的资料比较少,而仅有的几个开源项目也有它们各自的局限性,不适合大项目使用。我们在研究了当时的一些开源的技术之后,逐渐走出了一条自己的插件化框架之路,到如今形成了比较完善的插件化框架。
插件化技术的好处是显而易见的:
模块解耦:随着业务的不断增加,代码库会逐渐臃肿,各个业务耦合情况将会越来越严重,造成开发调试的困难。插件间一般是没有耦合关系的,因此天然的支持模块解耦。
协同开发:插件可以独立仓库开发,各个模块互不影响。
提高编译速度:编译速度会极大的影响开发效率,话说编码五分钟,编译两小时,插件化后,宿主的编译速度会显著提升,并且各个插件也可以独立编译调试。
动态发布:各个插件可以脱离宿主的发版限制独立发版,既可用于解决线上bug,也可以快速上线新业务。
65536问题:插件化方案可以减少宿主的方法数。
减小包体积:包体积和下载转化率有很大关系,Google曾经统计过包体积每增加6MB就会带来转化率1%的下降,并且Google Play也有过规定:对于发布到 Google Play 的应用,其下载大小限制为不超过100MB。插件包可动态下载,是减小宿主包体积的利器。
插件化带来这么多便利的同时,也会面临不少问题:
兼容性:插件化技术或多或少会涉及hook系统的API,Google从Android 9.0开始限制私有API的调用,因此需要考虑兼容Android的各个版本,并且Android碎片化比较严重,需要兼容各大厂商的rom。
复杂性:插件化技术的整体流程是比较复杂的,不仅涉及框架sdk,还需要Gradle插件甚至修改aapt等工具来实现插件包的编译。接入插件化方案也会有一定的成本。
Android插件化的历史并不长,自12年的AndroidDynamicLoader框架开始,到15年的DroidPlugin,16年的Small,再到17年的Atlas、Replugin、VirtualAPK,可谓是日新月异,百家争鸣,至此插件化技术进入了成熟阶段。但好景不长,随着Android P的发布,Google官方推出了大杀器 -- 禁止调用私有API[1],插件化框架或多或少都会反射系统的隐藏API,Google此举似乎给插件化技术画上了句号。不过好在私有API分为黑名单、灰名单、白名单三种方式,市面上大多数插件框架都作了兼容,调用系统API最多是在灰名单里,所以目前插件化框架仍然是可用的。但这并不代表万无一失,灰名单是根据Target sdk 版本去划分的,后续的Android 系统仍会将某些API设为黑名单里,无疑给插件化技术的发展蒙上了一层阴影。这里不得不佩服Replugin团队的技术前瞻性,仅使用一处hook点,hook点越少,涉及私有API的风险也就越低。此后,插件化技术的发展似乎停滞不前,热度也越来越低。
2018年Google发布了一种新的应用发布方式:Android App Bundle,可将应用拆分为多个Feature Apk,支持动态下发。这似乎是插件化的官方版本,不过这套服务依赖Google Play,国内环境无法使用,这种方式对插件化技术带来了新的方案。
2019年,经过了一段时间的沉寂后,随着基于Android App Bundle的Qigsaw插件化方案和零反射全动态Android插件框架Shadow的开源,插件化技术似乎又活跃了起来。
Aura是京东自研的一款插件化框架,目前不仅支持京东商城70+个业务插件的落地,还支持的京东到家、泰国站、拼购等App的使用,经过线上亿级用户量的检验,逐渐沉淀出的一套稳定的、灵活的插件化方案,并且提供了一整套编译、集成、动态升级流程,极大的简化了开发流程。整体框架见下图:
系统层:hook了ClassLoader和Assetmanager用于插件类和资源的加载。
框架层:包含Aura框架和公共库,公共库包含了插件和宿主共同依赖的类和资源。Aura框架则提供了插件化能力。
插件层:基于公共库和插件框架进行开发,有业务插件和公共插件之分。
类加载:
资源加载:
2、插件工程是独立编译的,正常apk的资源packageId是0x7f,为了保证插件内部的资源不和宿主冲突,需要修改插件的packageId,保证每个插件都分配有自己唯一的packageId,packageId范围0x21~0x7E相对比较稳妥。
3、运行期间通过反射宿主AssetManager中的addAssetPath方法,将插件的路径添加到宿主的资源路径里去,这样就实现了插件资源的加载。
Aura框架hook了系统的API,因此需要紧跟Android的版本节奏进行兼容, Android 9、Android 10等系统的兼容,小米、索尼等rom的兼容,因此后续如何减少私有API的调用,以及寻求更稳健的实现方式,是我们需要关注的一个点。
Aura插件使用我们提供的Grade工具AuraPlugin进行编译,由于Grade版本和Android Gradle Plugin的版本更新速度很快,因此AuraPlugin对各个版本的Grade进行兼容也是项繁重的工作。前面提到过,Google在2018年推出了“官方动态化组件技术” App Bundle[2],虽然在国内环境无法使用,但却和插件化技术的思路不谋而合。从下图可以看出,App Bundle会包含三种类型的APK:Base APK,Configuration APK和Dynamic feature APK。这里重点关注下Dynamic feature APK,该APK的packageId是小于0x7f的,不同的Feature Apk的packageId也都是不同的,并且Dynamic feature模块的编译也会移除宿主app里的依赖。这和Aura插件的构建流程是类似的,因此兼容官方的Feature Apk构建也是一个重要的方向。
split APKs结构[3]