一、什么是组件化和插件化
组件化:
就是将一个app分成多个模块,每个模块都是一个组件(Module),开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件等,但是最终发布的时候是将这些组件合并成一个apk,这就是组件化开发。
插件化:
和组件化开发略有不用,插件化开发时将整个app拆分成很多模块,这些模块包括一个宿主和多个插件,每个模块都是一个apk(组件化的每个模块是个lib),最终打包的时候将宿主apk和插件apk分开或者联合打包。
两者区别
简单来说组件化是在编译期分模块,插件化是在运行期。一般插件化用于动态修复bug或者动态更新模块,相对来说黑科技更多一些。
一、组件化
1、android工程的组件一般分为两种:
- application组件:是指该组件本身就可以运行并打包成apk
- lib组件:是指该组件属于app的一部分,可以供其它组件使用但是本身不能打包成apk
正常一个App中可以有多个module(模块),但是一般只会有一个module是设置为application的,其他均设置为library。组件化开发就是要每个module都可以运行起来,因此在开发期间每个module均设置为application,发布时再进行合并。
2、为什么要有组件化?
Android项目中代码量达到一定程度,编译将是一件非常痛苦的事情,短则一两分钟,长则达到五六分钟。随着app业务的壮大,模块越来越多,代码量超10万是很正常的,这个时候我们会遇到以下问题:
- 稍微改动一个模块的一点代码都要编译整个工程,耗时耗力
- 公共资源、业务、模块混在一起耦合度太高
- 不方便测试
3、组件化开发的好处(优点):
组件化开发可以有效降低代码模块的耦合度,使代码架构更加清晰,同时模块化的编译可以有效减少编译时间,当然总的编译时间是不会减少的,只是App模块化之后开发某个模块时,只需要编译特定模块,可以快速编译调试。
- 业务模块分开,每个模块可以独立开发编译运行,解耦的同时也降低了项目的复杂度。
- 开发单个模块时可以共享资源和工具类。
- 可以针对单个模块测试, 开发调试时不需要对整个项目进行编译。
- 多人合作时可以只关注自己的业务模块,把某一业务当成单一项目来开发。
- 可以灵活的对业务模块进行组装和拆分。
4、组件化开发的主要思路
就是将一个Module拆分成若干个Module,由主App提供统一的入口,每个拆分后的Module都依赖共享的Common依赖库,通过相关配置,各个Module可以独立运行调试,也可以供主App依赖使用。
5、组件化开发的步骤:
- 新建一个lib组件,New → Module → Andorid Library,取名BaseUtilLib,我们将所有的公共的工具类、网络封装等类放在其中。
- 新建一个lib组件,New → Module → Andorid Library,取名BaseReslLib,我们将所有的公共资源、drawable、String等类放在其中。
- 将app按照需求划分成多个模块,比如按业务按地区等都可以,然后分别创建Module模块。
- 逐一开发某个模块,每个组件都可以引用[1][2]步骤的BaseUtilLib、BaseReslLib里的公共类和公共资源,组件可以单独开发、单独测试。
- 将工程中的开发好的组件模块,引入到工程的app中,然后就可以发布了。
6、举一个更简单的图例:
app:壳工程
module1:组件1
module2:组件2
common:第三库,公用工具、自定义 View、主题等
二、插件化
1、为什么需要插件化?
我们在项目的开发过程中,难免会遇到产品需求的变更或者出现bug。在传统的模式中,我们首先需要修改代码,然后重新打包Apk,再交给公司的运营去官网或者应用商店上线,用户在打开应用的时候就会进行更新了。但是这种模式有几个缺点:
- 上线周期长,从修改代码到用户更新需要较长的时间;
- 用户更新代价较大,1b77f每次用户更新都需要下载整个Apk包,整个Apk包括了一个应用的所有代码,要消耗用户较多的流量。
- 如果是一些重要的更新,为了确保用户都能更新到,还需要强制更新,即用户打开App后如果不更新则退出应用,这种对用户来说是极其不友好的。
- 还有另外一种情况,某些较大的App功能很多,比如支付宝、微信等,如果将这些功能全部塞到一个Apk中,那将会是一个巨型Apk,用户在安装或者更新Apk时将会经过漫长的等待时间。
基于以上几点,Android的插件化技术应运而生,插件化技术可以将整个巨型Apk按照功能模块划分,不同的功能打包成不同的Apk,然后应用的主Apk按需加载对应功能的Apk,用户只需要安装应用的主Apk即可。主Apk相当于一个壳,它会按需加载其他功能模块的Apk。通过这种模式,不仅解决了巨型Apk的问题,而且当某个功能模块需要变化时,也只需要修改对应功能的代码,打包功能Apk并更新即可,这样不仅可以让用户及时更新,而且更新的代价也很小。但是,我们知道,在Android中,没有安装的apk是不能直接运行的,那么要想实现插件化,我们就必须让宿主Apk能够加载功能Apk并运行。
2、插件化的好处:
- 宿主和插件分开编译
- 并发开发,宿主和插件都是apk,开发是互不影响的,只需要宿主给插件一个上下文
- 动态更新插件,不需要安装,下载之后就可以直接打开
- 按需下载模块
- 可以解决方法数或变量数爆棚问题
3、插件化的标准:
是不是每个app都能运行在我们的宿主app里面呢?答案肯定不是。我们必须在宿主app里面设计一套标准,让插件app满足我们这个标准才能够运行。最主要的是插件app因为没有安装,所以是没有上下文的,就需要我们的宿主将上下文传过去。插件化的activity也没有生命周期,所以我们的宿主也要对插件的生命周期进行管理,以及资源、layout等。
4、插件化技术的发展
大概从2012年开始,前前后后出现了很多插件化/组件化的框架,各种框架都有自己的特色。
AndroidDynamicLoader:2012年7月,基于Fragement实现,通过动态加载不同的Fragement来替换界面,首次使用addAssetPath来读取插件中的资源。
Altas技术沙龙分享:2014年初,阿里的一次技术沙龙分享,主要讲淘宝Alta这项技术的概念,并没有涉及详细实现,当时已经应用于淘宝客户端,未开源。
android-pluginmgr:2014年7月,主要通过操作java虚拟机字节码,通过动态生成一个插件类的子类如四大组件(需要在manifest中提前声明),达到插件化的功能(最新的已经改成hook系统的一些对象如Instrumentation、ActivityThread等)
dynamic-load-apk:2014年底,该框架使用代理的方式实现,并没有hook系统的底层对象、方法。比较有代表性,后文中会详细分析。
OpenAltas:2015年4月,后来改名为ACDD。该框架参考了淘宝App的很多经验,主要就是Hook的思想(据说是反编译代码),同时,还首次提出来通过扩展AAPT(android assert packaging tool)来解决插件与宿主的资源id冲突的问题。
Android-Plugin-Framework:2015年5月,采用hook系统底层很多方法,感觉是把能hook的对象基本都hook住了,功能相对完整,但不支持so插件,兼容性不好,目前在实际应用的实践比较少。
DroidPlugin:2015年8月,它也hook了很多Android系统的底层代码,包括本地的PMS、AMS、ActivityThread、Instrumentation、H等对象,能把任意的App都加载到宿主里运行。比较有代表性,后文也会详细分析。
DynamicAPK:2015年10月,参考了之前提到的ACDD的设计方案,并通过扩展aapt解决资源问题。
Small:2015年底,是一个比较轻巧的跨平台插件化框架,比DroidPlugin更加轻量一点,基本原理也是对底层的对象进行hook,并使用gradle脚本来解决资源冲突的问题,可以通过配置url来支持插件跳转。
TwsPluginFramework,2017年初,同样hook系统底层对象方法,并通过appt解决资源冲突的问题。
Atlas:2017年3月,它是一个动态组件化的方案,也hook了很多底层对象方法,支持so,支持增量补丁和热修复,是个单进程的框架。
VirtualAPK:2017年6月底,基本原理也是hook系统底层对象方法,支持Gradle插件来完成构建,更加适合加载耦合插件。
Replugin:2017年7月初,该框架号称只hook了一个点即classloader,但实际上大部分工作都转移到在编译期借助JavaAssist来实现,是业内首个提出”全面插件化“(全面特性、全面兼容、全面使用)的方案。后文也会详细分析。
5、插件化涉及的基础知识
主要涉及的基础知识 :
第一,Binder机制 。我们都知道Android系统多进程通信核心就是Binder,我们平时开发过程中使用的AIDL就是依赖于Binder。
第二,App的打包流程。执行一次打包操作,中途需要经历资源打包、 Dex 生成、签名等过程。其中我们最需要关注的就是资源的打包原理,即AAPT这一步,如何解决宿主和插件的资源id冲突可以依赖于AAPT来解决。
第三,App的安装流程也很重要。一个APP是如何解析的,在安装完成后,中间又生成哪些文件,文件都放在哪里等等,这些细节也要搞清楚。
第四,App的启动流程。比较常见的流程是大家熟悉的Activity启动流程,了解app和AMS等服务的交互通信过程,我们就可以在一些关键之处进行hook操作,以偷梁换柱的方式实现插件化。
第五,资源的加载机制。Android插件化需要控制两个地方,一个是是插件Dex的加载,即如何把插件Dex中的类加载到内存;另一个就是资源加载,要保证宿主和插件能够加载各自的资源而不会冲突。
第六,是Gradle构建工具。插件化过程中,宿主和插件apk都可能涉及到gradle的构建打包,这部分内容也是不可忽视的。