这篇文章介绍一下Shadow的跨进程设计和插件Service的原理。一同讲这两部分是由于它们是相关的。这篇文章假设读者对于Android的Service、Binder通讯没有那么了解,所以会说起一些可能对你来讲有些简单的内容。android
在Android系统中,应用能够是多进程的。这在移动端操做系统中应该是很是高级的设计了,不少移动端操做系统、嵌入式系统都是不支持的。多进程程序给程序设计带来了颇有优势,也带来更多的复杂性。git
Android系统中的“四大组件(Activity,Service,Receiver,Provider)”全都是能够跨进程通讯的组件(下文中的组件指的都是这些组件)。每一个组件均可以在AndroidManifest中配置到一个指定的进程。Android系统其实不容许应用本身管理本身进程的生命周期。可是因为咱们只要Start一个Intent启动属于那个进程的组件,就能启动那个进程。再用Java通常的进程操做API,好比System.exit()
方法就能杀死一个进程。因为这些很容易作到的特色,让不少Android开发觉得本身能够管理进程。实际上这样理解Android进程是不对的。Android系统对进程的设计是这样的,系统收到须要用到某个组件的请求时(好比start Activity或bind Service),就会检查这个组件在AndroidManifest中注册的进程是否已经启动了。若是这个进程尚未启动,系统就会首先启动这个进程,而后构造一个应用注册的Application对象,调用Application对象的attachBaseContext()
方法。而后构造全部注册了应该在这个进程的ContentProvider,初始化它们,调用它们的onCreate()
方法。确保后面全部组件,包括尚未被调用的Application对象的onCreate()
方法都能正常使用这个进程的全部ContentProvider。而后再调用Application对象的onCreate()
方法。最后才开始初始化原本须要用到这个进程的组件。因此,进程的启动是根据组件的需求启动的。进一步的,这种设计下理应让进程的结束也根据组件的需求。实际上也是这样的,当这个进程中的全部组件都再也不被须要时,好比Activity finish了,或者Service stop了,或者没有任何bind了,都会让系统认为这个进程没有存在的必要了。这时系统就会决定回收进程、杀死进程了。固然系统还会在一些“必要”的时刻直接回收进程,好比内存不足等,或者内存不足首先回收了不在前台的Activity、Service等,进而致使进程符合了前面说的条件,再也不被须要了。所以,当咱们用System.exit()
等方法关闭进程时,或者遭遇了Crash,系统是不会认为进程再也不须要了的。大概是为了不死循环Crash,Crash的组件会被系统认为再也不须要。不过若是这个进程还有其余组件处于活跃状态,或者Activity栈中有多个Activity,最上面的Activity Crash了,它下面压着的Activity就应该露出了成为活跃的了。这种状况下,因为系统认为其余组件仍是须要这个进程的,就会将进程的建立流程从新走一遍,启动应该活跃的组件。因此,这里要理解好,组件对于系统来讲不是咱们常见的“对象”的概念,它不在本身运行的进程内存中表示和记录,而是在系统管理进程中以记录的形式记录的。这些组件中以Activity最为特殊,在编写Activity的时候,不能将Activity简单思考为一个对象。要进一步理解,Activity是有持久化状态的,这些状态就是经过savedInstanceState来表达的。因此,Activity和Service最大的区别不是Activity有界面而Service没有界面,Activity和Service最大的区别是Activity是有状态的,Service是无状态的。github
将组件放置在单独的进程中有不少优势,基本上都是围绕进程具备单独的内存资源的。对于插件框架来讲,有两点十分必要。一是插件通常都是热更新的,质量上要求可能会下降一些,一旦出现Crash不会影响其余进程的组件。好比说在宿主的主进程显示一个大厅界面,其中某个按钮跳转到插件。插件在单独进程启动后若是出现Crash,宿主的大厅界面不会受到任何影响。若是插件也在宿主的主进程,就会致使大厅界面也会因进程重启而从新建立。二是Android的JVM虚拟机不支持Native动态库反加载,因此在同一个进程中相同so库的不一样版本即不能同时加载,也不能换着加载,会形成插件和宿主存在so库冲突。框架
多进程也带来更多复杂性,就是它的缺点了。好比,跨进程调用的全部参数都必须是可序列化对象;跨进程通讯时对面的进程可能没有启动,也可能已经死了;跨进程通讯出现异常,整个跨进程调用的堆栈不会是连着的,并且异常对象一般是不能序列化跨进程传输的。如何控制插件进程退出或重启供另外一业务使用。另外,进程的启动速度也比较慢。ide
主要基于以上两点,Shadow设计的插件框架基本模型是:Manager、LoadParameters、Loader三个部分。其中Manager工做在宿主进入插件的入口界面所在进程,负责下载插件、安装插件,而后将插件信息封装在LoadParameters中控制Loader启动插件。LoadParameters是一个可序列化的结构体,能够跨进程传输。Loader工做在插件进程,负责将插件免安装的运行起来,解决插件框架的核心问题。ui
Shadow中有一个叫作PluginProcessService
的Service是跨进程设计的关键部分,咱们简称它PPS。操作系统
PPS有多个做用:插件
咱们前面复习过,进程的启动必须由一个组件触发。那么一个没有界面的Service就是一个不错的选择,由于咱们一般要对插件进行“预加载”,可能会静默启动插件的Application对象,或启动插件的Service等。还有要想让系统知道这个插件进程是有用的,就必须有活跃的组件在这个进程。咱们的插件中的组件全都是没有安装的组件,系统都不知道他们存在,确定不能靠它们了。靠插件的壳子代理组件也不行,由于咱们是一个全动态插件框架,那些壳子代理组件也是插件的一部分,尚未加载呢,因此也不能靠它们。这就须要有一个专门负责启动插件进程的Service,因此它就叫PluginProcessService
了。Service的Bind语义在这里也很正常,Manager就以Bind的方式启动这个PPS,直到宿主认为再也不须要这个插件了,再经过Manager unbind这个PPS。Manager经过Bind拿到的Binder就是PPSController,经过这个PPSController操做PPS,让宿主得以使用“插件服务”。因此PPS是一个货真价实的Service。设计
选择Service来触发启动插件进程还有一个缘由是,咱们若是想让插件进程的插件Service能像正常Service同样跨进程通讯,就必须在插件进程至少有一个真的注册在宿主中的Service。这涉及一个Binder的基本知识,就是Binder是一个中心化的跨进程通讯框架。每个Binder都分本地端和远程端,本地端实现功能,远程端供其余进程调用功能。直接实现的Binder天然就是本地端了,而远程端怎么实现呢?实际上把一个本地Binder经过另外一个已经存在远程端的Binder跨进程传输一下,就自动把这个本地Binder送到Binder的中心管理器中注册并生成远程端了,新生成的远程端就经过那个已经存在的Binder的远程端输出出来了。这里可能天然会想到第一个Binder哪里来的的问题,简单说就是第一个Binder在设计中特殊处理了,详细的设计能够自行Google一下。因此,要想插件Service能正常跨进程工做,就要把插件Service的Binder经过一个已经存在的Binder传输一次。所以,最简单的办法就是经过PPS的Binder传输一次。代理
因此,咱们将Loader自己也设计成了一个插件Service(即dynamic-loader
)。由于全动态的设计中,宿主中的代码不会直接操做Loader,真正操做Loader的是动态实现的Manager。所以Loader和Manager都是动态实现,Loader上的接口就不必在PPS上固定写死了。PPS上只保留了加载Runtime和Loader的必要方法。Loader自己的Binder先经过PPS跨进程通讯到Manager进程,从而使Loader的Binder成了跨进程的Binder。而后Loader上再暴露的bindPluginService
方法再将插件Service的Binder经过Loader的Binder跨进程传输其余进程,就是的插件Service真正能够面向其余进程工做起来了。咱们的插件Service实现就是这么简单。能够看出来Shadow的插件Service是没有单独的代理壳子Service的,只依赖一个PPS就实现了不限数量的插件Service支持。
这是由于这些Binder跨进程调用都是有可能会失败的,失败了不能粗暴的Crash。因此,PPS和dynamic-loader
的Binder都是半手工写的Binder。半手工就是用aidl先生成代码,再复制出来添加自定义可序列化Exception的能力。实现Manager跨进程操做Loader能够Catch异常。
因为全动态的设计,在一个宿主中能够有多个Manager实现。一个Manager实现也能够同时操做多个PPS,只须要继承PPS注册在不一样的进程中就能够了。因为Loader、Runtime也是动态的,因此不一样的插件进程可使用不一样版本的Loader实现。
Shadow的Sample中还有咱们本身的业务中,都对壳子代理组件指定了进程名。对咱们业务来讲,这些壳子其实是旧框架遗留在宿主中被Shadow复用的。实际上开发完Shadow的PPS,咱们就意识到,这些壳子组件应该是能够应用android:multiprocess
特性的。
根据文档: developer.android.com/guide/topic…
android:multiprocess为true时,Activity和Provider的行为是启动Intent时处于哪一个进程,就将被启动的Activity或Provider启动在哪一个进程。
既然PPS已经决定了插件进程,由PPS启动插件Activity就可使壳子工做在PPS所在进程了。这样能够在同一个宿主中应用多个插件进程时少注册一些壳子组件。
欢迎你们实验一下而后提一个PR来改进这个问题。
PS: 新注册的掘金帐号,发文章曝光量很低。选择来掘金分享也是但愿吸引更多开发者关注到Shadow。因此请你们支持一下,点个赞提升点个人掘力值,以便更好的继续分享。