1、概述
想当初在第一次拜读《Android艺术开发探索》时,深感真的是一本很“艺术”的书(因为当初菜的看不懂…),随着自己的成长和多次阅读,从开始的完全不懂到现在的有所理解、使用和总结,才体会到其中探索的奥妙,现在跟着安卓高级开发的学习路线,进一步学习、总结和梳理知识。
多进程作为Android开发者迈向高级开发者的第一关,也使许多初级开发者望而却步,这也是每个开发者必经阶段,正好笔者在公司的开发项目中也一直使用了多进程,之前只是在使用阶段、和平时零散的知识点,而对Binder的原理和理解并不深入,本文结合最近所看的文章和实际使用,从开发者的角度总结多进程和Binder的使用,即为自己梳理知识也希望帮助有需要的人。
- 定义
提到多进程就会想到多线程,这也是很多初级的面试问题,二者对比着可能更好理解:
- 线程:线程是CPU最小的调度单元,是有限的系统资源,也是处理任务的地方
- 进程:是一个执行单元,一般指设备上的一个程序或一个应用
- 理解:进程和线程是包含和被包含的关系;一个进程可以包含多个线程
- 开启方式
Android开启多进程只有一个方式:注册清单文件中,在Android四大组件中指定process属性,命名方式如下:
- 以“:”命名方式:最终的进程名为在当前的命名前面添加默认的包名
- 完整命名方式:最终的进程名就为设定的名称
android:process=":consume"
android:process="com.alex.kotlin.myapplication.consume"
- 多进程问题
因为进程开启时Application都会重新创建,所以很多数据和对象都会产生副本,因此在多进程模式下数据共享就会变得不稳定,多进程模式下会造成如下的问题:
- 静态成员和单例模式完全失效
- 线程同步机制完全失效
- SharePreference可靠性下降
- Application会多次创建
进程间通信
关于进程间的通信首先想到的是Binder机制,当然开发中如果使用多进程,那Binder自当是首当其冲要了解和学习的,下文也会重点介绍Binder,在此之前来看看处理Binder 之外,我们实际开发中使用的一些可以实现跨进程的方法;
- 序列化
- Serializable
Serializable序列的使用很简单,只需要实现在Java类中实现Serializable接口,设置serialVersionUID即可;
public class Book implements Serializable {
private static final long serialVersionUID = 871136882801008L;
String name;
int age;
public Book(String name, int age) {
this.name = name;
this.age = age;
}
}
在储存数据时只需将对象序列化在磁盘中,在需要使用的地方反序列化即可获取Java实例,使用过程如下:
//序列化
val book = Book("Android",20) // 创建对象
val file = File(cacheDir,"f.txt") //实例化保存的文件
val out = ObjectOutputStream(FileOutputStream(file))
out.writeObject(book)
out.close()
//反序列化
val file = File(cacheDir,"f.txt")
val input = ObjectInputStream(FileInputStream(file))
val book: Book = input.readObject() as Book // 读取序列化信息并转换对象
input.close()
针对上面的serialVersionUID可能有的认为不设置也可以使用,确实如果不设置serialVersionUID值,Java对象同样可以序列化,但是当Java类改变时,这时如果去反序列化的化就会报错,因为你不指定serialVersionUID时,系统会默认使用当前类的Hash值最为UID,当java对象改变时其Hash值也改变了,所以反序列化时就找不到对应的Java类了,因此serialVersionUID是辅助序列化和反序列化的,只有两者的serialVersionUID一致才可实现反序列化;
- Parcelable
Parcelable也是一个接口,他是专为Android提供的在内存中更高效的序列化方式,使用方法是实现接口,重写其中方法即可,当然也可使用插件自动生成。
- 二者对比
对于Parcelable和Serializable的选择使用:Serializable是Java的序列化接口,使用时开销大,需要大量的IO操作,Parcelable是Android提供的序列化接口,适合Android效率更高,对于两者的选择可以参考以下标准,如果只是在内存上序列化使用Parcelable,如果需要在磁盘上序列化使用Serializable。
Binder
在网上看了需对关于Binder的文章,有的深入Binder源码和底层去分析Binder的源码和实现,当然这里面的代码我是看不懂,本文主要从Android开发的角度,对Binder的通信的模型和方式做一个简单的介绍,毕竟自己的了解和使用的不是那么深入;
- Binder模型
Binder框架定义了四个角色:Server,Client,ServiceManager(简称SMgr)以及Binder驱动,其中Server,Client,SMgr运行于用户空间,Binder驱动运行于内核空间;
- Server:服务的真正提供者,它会先向ServiceManager注册自己Binder表明自己可以提供服务,驱动会为这个Binder创建位于内核中的实体Binder和ServiceManager中的引用,并将名字以及新建的实体引用打包传给 ServiceManager,ServiceManger 将其填入查找表;
- Client:服务的需求者和使用者,它向ServiceManager申请需要的服务;ServiceManager将表中的引用Binder返回Client,Client拿到服务后即可调用服务中的方法;
- ServiceManager:Binder实体和引用的中转站,保存并分发Binder的引用,负责整个系统Binder的调度;
- Binder驱动:Binder驱动默默无闻付出,却是通信的核心,驱动负责进程之间Binder通信的建立,实现在进程之间的传递和引用计数管理、数据包在进程之间的传递和交互等一系列底层支持,借用网上的一张图片展示Binder的通信模型;
如果上面的四个功能难以理解,我们以打电话为例,将整个电话系统的程序比做Binder驱动,通讯录比作ServiceManager,你本人为Client,现在你要打电话给叫Server人求助,执行逻辑如下:
- Server:Server表示你要打电话找的人,首先它要给你留一个手机号,你为了可以找到他,将号码保存到通讯录中,通讯录相当于ServiceManager(Server向ServiceManager注册服务);
- client:相当于你本人发起打电话请求;
- ServiceManager:通讯录保存电话号码,你需要的时候首先向通讯录去查找号码,它会返回Server手机号给你;
- Binder驱动:打电话的系统,你获的查询到的号码后使用设备打电话,那内部如何发起呼叫、如何通话都开电话硬件和通信硬件调度;
对于Binder的通信模型如上述所述,简单的说就是Server先注册并登记表示可以提供服务功能,当有需求时向登记处查找可以提供服务的Service,登记处会给你详细的地址,然后你就可以和服务商之间合作,只是整个过程在Binder驱动作用下完成;
- Binder代理机制
通过上面的Binder通信机制的理解,相信已经了解Binder是如何跨进程通信的,可是具体的数据和对象都存在不同的进程中,那么进程间是如何相互获取的呢?比如A进程要获取B进程中的对象,它是如何实现的呢?此时就需要Binder的代理机制,可以说Binder因为自己有金刚钻才敢接这瓷器活;
当Binder收到A进程的请求后,Binder驱动并不会真的把 object 返回给 A,而是返回了一个跟 object 看起来一模一样的代理对象 objectProxy,这个 objectProxy 具有和 object 一样的方法,但是这些方法并没有 B 进程中 object 对象那些方法的能力,这些方法只需要把把请求参数交给驱动即可;
而对于进程A却傻傻不知道它以为拿到了B 进程中 object 对象,所以直接调用了Object的方法,当 Binder 驱动接收到 A 进程的消息后,发现这是个 objectProxy 就去查询自己维护的表单,一查发现这是 B 进程 object 的代理对象。于是就会去通知 B 进程调用 object 的方法,并要求 B 进程把返回结果发给自己。当驱动拿到 B 进程的返回结果后就会转发给 A 进程,一次通信就完成了,所以中间的代理就只是一个面具和传输的媒介。
- Binder使用
Messenger
一种轻量级的IPC方案,它的底层实现是AIDL,Messenger通过对AIDL的封装是我们可以更简单的使用进程通信,它的构造函数如下,从构造函数中看出Message提供了两种创建的方式,分别传入Handler和IBinder对象,其实这也正对应这消息的接收和发送;
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.