插件化这个词相信读者并不陌生,比如,我们的IDE(eclipse,As等)都具有支持插件的能力,如果了解Windows开发的读者对DLL这种机制也并不陌生,包括一些Web应用服务器(jboss等),他们也都实现了osgi的模块化的规范,可以实现类似插件的功能,在无需停止服务器的前提下就可以上线一些需求或动态的替换组件和模块的实现等。 但是在Android系统中是不支持这种开发模式,需要我们的开发人员自己去实现这么一套支持插件化的框架系统。
我们先看下插件化的研发流程是怎样的!
面临的问题?
如果读者阅读了“多进程化”的那篇文章,应该了解在功能需求单向递增这种情况下,不仅要面临内存带来的稳定性问题,同时也面临系统本身和团队协作带来的种种问题:
1.方法数的限制
由于dex文件格式的限制,一个dex文件中方法个数存储是short类型不能超过65536,如果超过这个数将不能打包。 另外,方法数过大,会导致dexopt崩溃,dexopt是使用LinearAlloc来存储应用的方法信息,如果一旦超出这个缓冲区的大小,即使能够打包成功,那么可能在一些机器上也是无法进行安装的。
2.安装包体积
功能需求单向递增之后,最直观看到的就是安装包体积变大。安装包体积变大,其实不是一件小事情。最直接影响的就是升级转化,由于安装包比之前大很多,在用户下载的过程中失败的概率也会增加,这样老版本升级成新版本的成功率也会下降。而且,很多用户也会比较在意安装包的大小。他可能会认为安装包小的在运行时会更流畅等等。
3.团队耦合严重
功能需求单向递增之后,团队之间的耦合度越来越高,依赖更强。我们知道要发布一个产品是以APK的形式发出的,APK是我们大家统一把代码打包成DEX文件,然后把DEX文件通过APKbuilder打包成APK的。 那么既然要统一打包,大家需要将修改的问题都全部修改完成并且提交到SCM上才可以进行打包。 但是这种方式,在模块量很大的情况下,会出现互相等待的情况。 比如:A,B,C三个模块的研发负责人,A,B已经修改完成并自测通过提交了代码,C还在修改中,那么就不能进行打包, 而且C的进度可能还会影响A,B的下一步计划。 这是非常糟糕的,而且非常影响产品的迭代速度。 这三个人就像是绑在一起的蚂蚱,谁也蹦不远,跑不快。
4.编译打包慢
功能需求增加,意味着代码量与资源数的增加,那么就会拉低整个APK的打包速度,如果在遇到垃圾的服务器配置,那么几分钟,10几分钟的打包时间并不稀奇。 这是非常痛苦的,通常产品在发布前,研发会进行功能冒烟自测和稳定性的自动化测试,如果在测试过程中发现异常,需要在来一轮打包和自测。 我的天呢, 崩溃了。
5.灵活性差
在一些电商应用中,某个活动日,要支持一些业务上线,如果要求用户升级应用才能支持,那么这是很影响业务的(等用户全部替换成新的版本,活动时间早以过期),在或者某个组件模块发生了严重问题,如何解决? 升级解决?
如果以上问题继续持续下去,产品很难继续保持和其他竞品的优势或追赶其他产品,所以必须对现有产品软件架构进行调整,对现有研发流程进行调整。
分析
还记得我们解决内存问题时的思路吗?“分离”,“分割”,“分解”。 他们就像是“应急锦囊”一样,能帮助我们分析问题。 回想我们在解决内存问题时,核心的问题是“N个模块争抢同一个进程内存”导致了很多稳定性问题,而我们就将不同的模块分解到不同的进程中,达到不同模块拥有自己的内存空间,这样就不存在争抢,稳定性问题也会得到改善。 而从上面4个问题总结下来就是“N个模块争抢同一个DEX”的问题,N个模块同时被打包到同一个DEX,那么DEX大了之后, 影响打包速度,影响升级转化,影响版本迭代等等。 所有的问题,均指向了将所有的代码打包到同一个DEX中导致的。 那么按照解决内存问题的思路,去分解。将N个模块分解到不同的DEX中,是不是就可以解决?
什么是插件化?
插件化简单讲就是将各个模块拆分成不同的包(DEX,JAR,APK等),而这些包“无需安装”就可以被主应用加载,提供给用户使用。
为什么要实现插件化?
按照上面的分析,我们要把各个模块分解到不同的APK中,每个APK对应一个或多个独立的模块,主APK中只包含核心的功能模块,其余功能模块,均根据用户在使用时按需下载。
这样主APK不会出现方法数限制,安装包体积自然下降,主APK与子APK独立开发,独立升级维护,如果有依赖也仅是接口的依赖,这样耦合会降低,各个APK间没有代码的依赖,编译打包也会有很大改善,同时插件化带来的灵活性和动态性是每一个产品经理所渴望的,如获至宝。
既然插件化是如此美的一个东西,那么我们怎样才能拥有她。 美的东西,通常很难得到,需要几番周折,花大把时间和精力。即使得到之后,你的研发流程可能会有所调整。就像我们得到了一个至宝,我们可能需要给她买个保险箱,可能还需要有专门的人负责看护一样,那么和你之前的生活流程完全不同。 没有办法,拥有美的东西,是要付出代价的。
如何拆分出插件?
我认为基本和多进程的拆分原则相同,即按照职责驱动设计模式进行拆分,同时参考OSGI模块化规范。
插件拆分的原则,请参考:
1.插件间仅接口依赖(双方协议);
2.隔离性(插件间实现的变化互不影响);
3.可替代性(可任意时刻对插件的实现进行替换,同时不影响其他插件的运行);
还是要注意一点,拆分出来的插件尽量要保证能够独立的运行,能够作为一个独立的业务功能运行。即可以作为插件被加载到主应用中,也可以作为独立的应用对外发布。这样的话,我们产品的灵活性是很高的,就像拥有了一个插件化的仓库,随意组装,拼接,就能发布一个产品,岂不是妙哉。
插件化框架如何选择?
在上面我们也提到了,android目前的系统是不支持插件化的,如果应用要使用插件化的软件架构,则需要移动端开发人员完成插件化框架系统的实现。 要实现这么一个系统,并不是件易事,团队中还是要有牛逼的开发人员,并且对andoird系统有一定的了解。否则很难将这样一个系统应用到产品中。 插件化的框架实现,目前也有很多开源的,比如:DroidPlugin,Replugin等等,实现的技术方法也有所不同。读者如果要引入插件化,还是要参考下这些框架的实现,或者直接使用他们。
向大家推荐以下文章,请参考:
DroidPlugin:
Github:https://github.com/DroidPluginTeam/DroidPlugin
源码分析:http://blog.csdn.net/turkeycock/article/details/50959786
Replugin
Github:https://github.com/Qihoo360/RePlugin
源码分析:https://www.jianshu.com/p/5994c2db1557
笔者都有阅读过上面两个框架的代码,本人还是比较看好Replugin的。DroidPlugin更多的是在于对系统的Hook,稳定性是很大的问题。而Replugin只Hook了系统一个点,就是LoadedApk中的PathClassloader的hook,所以稳定性大大提高。
下面这篇文章由笔者编写,是对这两款插件的不同实现之处的总结,请做为参考:
DroidPlugin与Replugin实现比较:http://blog.csdn.net/degwei/article/details/79043165
另外,在向大家推荐一篇文章
http://www.infoq.com/cn/articles/android-plug-ins-from-entry-to-give-up
这篇文档介绍了插件的发家史和一些框架的技术实现轮廓。
写到这里,不知道读者是不是已经对插件化有了一些了解。如果读者在拆分和实现的过程中有任何问题,我们可以一起讨论。