来看看现有插件化框架的对比。
目录:
1. MulitDex 引起的问题
在应用安装到手机上的时候 dex 文件的安装是复杂的,有可能会因为第二个 dex 文件太大导致 ANR。使用了 mulitDex 的 App 有可能在 4.0(api level 14) 以前的机器上无法启动,因为 Dalvik linearAlloc bug(Issue 22586) 。使用了 mulitDex 的 App 在 runtime期间有可能因为 Dalvik linearAlloc limit (Issue 78035) Crash。该内存分配限制在 4.0 版本被增大,但是 5.0 以下的机器上的 Apps 依然会存在这个限制。
主 dex 被 dalvik 虚拟机执行时候,哪些类必须在主 dex 文件里面这个问题比较复杂。build tools 可以搞定这个问题。但是如果你代码存在反射和 native 的调用也不保证 100% 正确。
对于 davilk 和 art 虚拟机 Mulitdex 的不同: ART 模式相比原来的 Dalvik,会在安装 APK 的时候,使用 Android 系统自带的dex2oat 工具把 APK 里面的 .dex 文件转化成 OAT 文件。
这里说一下罗迪的快速加载 Mulitdex 方案:art 虚拟机对 dex 优化需要很长时间,加载插件 dex 跳过优化实现加速,跳过会影响类加载的性能。
2. 插件化需要解决的问题与方案
针对问题 1:
原理:资源 id 是在编译时生成的,其生成的规则是 0xPPTTNNNN,PP 段是用来标记 apk 的,默认情况下系统资源 PP 是01,应用程序的 PP 是 07。TT 段,是用来标记资源类型的,比如图标、布局等,相同的类型 TT 值相同,但是同一个 TT 值不代表同一种资源,例如这次编译的时候可能使用 03 作为 layout 的 TT,那下次编译的时候可能会使用 06 作为TT的值,具体使用那个值,实际上和当前 APP 使用的资源类型的个数是相关联的。NNNN 则是某种资源类型的资源 id,默认从1 开始,依次累加。
那么我们要解决资源 id 问题,就可从 TT 的值开始入手,只要将每次编译时的 TT 值固定,即可以让资源 id 达到分组的效果,从而避免重复。例如将宿主程序的 layout 资源的 TT 固定为 33,将插件程序资源的 layout 的 TT 值固定为 03 (可不对插件程序的资源 id 做任何处理,使其使用编译出来的原生的值),即可解决资源 id 重复的问题了。
固定资源 id 的 TT 值的办法非常简单,提供一份 public.xml,在 public.xml 中指定什么资源类型以什么 TT 值开头即可。比如:
public.xml:
build.gradle:
afterEvaluate {
for (variant in android.applicationVariants) {
def scope = variant.getVariantData().getScope()
String mergeTaskName = scope.getMergeResourcesTask().name
def mergeTask = tasks.getByName(mergeTaskName)
mergeTask.doLast {
copy {
int i=0
from(android.sourceSets.main.res.srcDirs) {
include 'values/public.xml'
rename 'public.xml', (i++ == 0? "public.xml": "public_${i}.xml")
}
into(mergeTask.outputDir)
}
}
}
}
这样就能使用 public.xml 固定资源 id,public.xml 用来告诉 Android 资源打包工具 aapt,将类型为 drawable 的资源 ic_notice 的id 固定为 0x7f02002b。注意,在开发应用程序时,一般是不需要用到 public.xml 文件的,因为我们的资源基本上都是在内部使用的,不会导出来给第三方应用程序使用。只在内部使用的资源,不管它的 id 如何变化,我们都可以通过 R.java 文件定义的常量来正确地引用它们。只有系统定义的资源包才会使用到 public.xml 文件,因为它定义的资源是需要提供给第三方应用程序使用的。当然还有做插件化的时候,处理资源冲突问题。
还有一个方法是通过定制过的 aapt 在编译时指定插件的 PP 段的值来实现分组,重写过的 aapt 指定 PP 段来实现 id 分组。
针对问题 2:
原理说明:主要就是 classloader 加载 dex,代理模式就是本身宿主中有 Activity,通过欺骗系统来创建 Activity,欺骗系统的部分 hook 的有深有浅 (对比 DroidPlugin 和 Small),让这个 Activity 有生命周期,而动态加载模式就是运行时动态创建并编译一个 Activity 类,需要使用动态创建类的工具实现动态字节码操作。
针对问题 3:
3. 插件化实现方案分析对比
AndroidDynamicLoader 的作者是大众点评的屠毅敏,应该是最早的动态加载实现方案了。作者在介绍这个框架时形容宿主 App就好像浏览器,但它加载的并不是网页,而是运行在 Android 系统上的插件。这个方案主要是在插件中使用 Fragment,在宿主中使用 Activity 去动态加载插件中的 Fragment。
github 链接:https://github.com/mmin18/AndroidDynamicLoader
23Code 并不是一个开源的项目,它是一个完整的 apk。在应用市场可以下载得到,它内部展示了一些有趣的动效,通过下载的方式,可以直接运行起来,这就是插件化的真实案例。
altas 是阿里的伯奎分享出来的一个插件化的方案。
分享视频链接:http://v.youku.com/v_show/id_XNTMzMjYzMzM2.html
对应的 PPT:http://club.alibabatech.org/resource_detail.htm?topicId=84
这里的分享主要是将一些思路,业务方面的场景,解决的问题。后来 github 上有了对应的开源项目叫 OpenAltas,后改名ACDD。
ACDD 的链接:https://github.com/bunnyblue/ACDD
官方对这个库的说明是非代理 Android 动态热部署框架,而且在视频介绍中,伯奎也讲到,这个方案,就是把宿主当做一个容器,动态加载对应的插件。视频中也有讲到,要做到让插件无感知地运行在这个容器中,需要 hook 很多系统层面的东西,比如:ActivityThread,LoadedApk,ContextImpl,PackageParser,ActivityManagerNative 等,即当插件需要系统的服务来提供对应功能的时候,将这个行为拦截掉,宿主就可以在插件和 Android 系统中间做些手脚了。在当时,Altas 是最为先进插件化技术了,为淘宝客户端提供了很多加载其他业务插件的能力。
Dynamic-Load-Apk 简称 DL,这个开源框架作者是任玉刚,他的实现方式是,在宿主中埋一个代理 Activity,更改 ClassLoader后找到加载插件中的 Activity,使用宿主中的 Activity 作为代理,回调给插件中 Activity 所以对应的生命周期。这个思路与AndroidDynamicLoader 有点像,都是做一个代理,只不过 Dynamic-load-apk 加载的插件中的 Activity。
项目地址:https://github.com/singwhatiwanna/dynamic-load-apk
项目说明:http://blog.csdn.net/singwhatiwanna/article/details/40283117
DroidPlugin 是张勇实现的一套插件化方案,它的原理也是 Hook 客户端一侧的系统 API,可能与 Altas 所 Hook 的点不同,细想应该是基本相同的。
项目地址:https://github.com/DroidPluginTeam/DroidPlugin
项目中 DOC 文件夹有很多关于项目的讲解说明。
张勇本人对 DroidPlugin 的讲解:http://www.infoq.com/cn/presentations/the-realization-principle-and-application-of-droidplugin?utm_campaign=rightbar_v2&utm_source=infoq&utm_medium=presentations_link&utm_content=link_text
维术对这个框架的讲解:http://weishu.me/2016/01/28/understand-plugin-framework-overview/
维术的这几篇 blog 很详细滴讲解了 DroidPlugin 是如何实现四大组件的插件化的,还有几篇有规划但是没有写的方面,希望后面能补全。
VirtualApp 作者是高中生罗迪,据说这个 Android 大牛初三的时候就开始研究双开、插件化的技术,相当了不起。项目的思路与DroidPlugin 相似,不过他没有提供 Service 的代理,而是使用 ContentProvider 来代替 Service 在宿主中作为真正的运行体。
项目地址:https://github.com/asLody/VirtualApp
DynamicAPK 是携程推出的动态加载方案。
项目说明:http://www.infoq.com/cn/articles/ctrip-android-dynamic-loading
项目地址:https://github.com/CtripMobile/DynamicAPK
与 ACDD 一样修改了aapt,使得插件与宿主的资源不会出现相同 id 的问题。
整体对比如下: