前言

学习清单:

  • IPC的基础概念
  • 多进程和多线程的概念
  • Android中的序列化机制和Binder
  • Android中的IPC方式
  • Binder连接池的概念及运用
  • 各种IPC的优缺点

一.为什么要学习IPC

IPCInter-Process Communication的缩写,含义是进程间通信,是指两个进程之间进行数据交换的过程。

  • 进程:是资源分配的最小单位,一般指一个执行单元,在PC和移动设备上指一个程序应用
  • 线程:CPU调度的最小单位,线程是一种有限的系统资源。

IPC不是Android所特有的,Android中最有特色的IPC方式是Binder。而日常开发中涉及到的知识:AIDL,插件化,组件化等等,都离不开Binder。由此可见,IPC是挺重要的。

二.核心知识点归纳

2.1 Android中的多进程模式

Q1:开启多线程的方式

  • (常用)在AndroidMenifest中给四大组件指定属性android:process
  • (不常用)通过JNI在native层fork一个新的进程。

Q2:多进程模式的运行机制

Andoird为每个进程分配了一个独立的虚拟机,不同虚拟机在内存分配上有不同的地址空间,这也导致了不同虚拟机中访问同一个对象会产生多份副本

2.2 IPC基础概念

2.2.1 什么是序列化

  • 含义:序列化表示将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。
  • 使用场景:需要通过IntentBinder等传输类对象就必须完成对象的序列化过程。
  • 两种方式:实现Serializable/Parcelable接口。

2.2.2 Serializable接口

//Serializable Demo
public class Person implements Serializable{
private static final long serialVersionUID = 7382351359868556980L;
private String name;
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

2.2.3 Parcelable接口


public class User implements Parcelable { public int userId;
public String userName;
public boolean isMale; public Book book; public User() {
} public User(int userId, String userName, boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
} //返回内容描述 return 0 即可
public int describeContents() {
return 0;
} //序列化
public void writeToParcel(Parcel out, int flags) {
out.writeInt(userId);
out.writeString(userName);
out.writeInt(isMale ? 1 : 0);
out.writeParcelable(book, 0);
} //反序列化
public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
//从序列化的对象中创建原始对象
public User createFromParcel(Parcel in) {
return new User(in);
} public User[] newArray(int size) {
return new User[size];
}
}; //从序列化的对象中创建原始对象
private User(Parcel in) {
userId = in.readInt();
userName = in.readString();
isMale = in.readInt() == 1;
book = in.readParcelable(Thread.currentThread().getContextClassLoader());
} @Override
public String toString() {
return String.format("User:{userId:%s, userName:%s, isMale:%s}, with child:{%s}",
userId, userName, isMale, book);
} }

2.2.4 Serializable和Parcelable接口的比较

平台JavaAndorid
序列化原理将一个对象转换成可存储或者可传输的状态将对象进行分解,且分解后的每一部分都是传递可支持的数据类型
优缺点优点:使用简单 缺点:开销大(因为需要进行大量的IO操作)优点:高效 缺点:使用麻烦
使用场景将对象序列化到存储设备或者通过网络传输主要用在内存序列化上

2.2.5 Binder

Q1:Binder是什么

  • 从API角度:是一个类,实现IBinder接口。
  • 从IPC角度:是Android中的一种跨进程通信方式。
  • 从Framework角度:是ServiceManager,连接各种Manager和相应ManagerService的桥梁。
  • 从应用层:是客户端和服务端进行通信的媒介。客户端通过它可获取服务端提供的服务或者数据。

Q2:Android是基于Linux内核基础上设计的,却没有把管道/消息队列/共享内存/信号量/Socket等一些IPC通信手段作为Android的主要IPC方式,而是新增了Binder机制,其优点有:

A1:传输效率高、可操作性强

Binder1简易
消息队列2简易
Socket2简易
管道2简易
共享内存0复杂

从Android进程架构角度分析:对于消息队列、Socket和管道来说,数据先从发送方的缓存区拷贝到内核开辟的缓存区中,再从内核缓存区拷贝到接收方的缓存区,一共两次拷贝,如图:

进阶之路 | 奇妙的IPC之旅-LMLPHP

对Binder来说:数据从发送方的缓存区拷贝到内核的缓存区,而接收方的缓存区与内核的缓存区是映射到同一块物理地址的,节省了一次数据拷贝的过程

A2:实现C/S架构方便

A3:安全性高

Q3:Binder框架定义了哪四个角色呢?

A1:Server&Client

A2:ServiceManager:

进阶之路 | 奇妙的IPC之旅-LMLPHP

A3:Binder驱动

  • 与硬件设备没有关系,其工作方式与设备驱动程序是一样的,工作于内核态。
  • 提供open()mmap()poll()ioctl()等标准文件操作。
  • 以字符驱动设备中的misc设备注册在设备目录/dev下,用户通过/dev/binder访问该它。
  • 负责进程之间binder通信的建立,传递,计数管理以及数据的传递交互等底层支持。
  • 驱动和应用程序之间定义了一套接口协议,主要功能由ioctl()接口实现,由于ioctl()灵活、方便且能够一次调用实现先写后读以满足同步交互,因此不必分别调用write()read()接口。
  • 其代码位于linux目录的drivers/misc/binder.c中。

Q4:Binder 工作原理是什么

  • 服务器端:在服务端创建好了一个Binder对象后,内部就会开启一个线程用于接收Binder驱动发送的消息,收到消息后会执行onTranscat(),并按照参数执行不同的服务端代码。
  • Binder驱动:在服务端成功创建Binder对象后,Binder驱动也会创建一个mRemote对象(也是Binder类),客户端可借助它调用transcat()即可向服务端发送消息。
  • 客户端:客户端要想访问Binder的远程服务,就必须获取远程服务的Binder对象在Binder驱动层对应的mRemote引用。当获取到mRemote对象的引用后,就可以调用相应Binder对象的暴露给客户端的方法。

进阶之路 | 奇妙的IPC之旅-LMLPHP

Q5:当服务端进程异常终止的话,造成Binder死亡的话,怎么办?

在客户端绑定远程服务成功后,给Binder设置死亡代理,当Binder死亡的时候,我们会收到通知,从而重新发起连接请求。

private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient(){
@Override
public void binderDied(){
if(mBookManager == null){
return;
}
mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
mBookManager = null;
// TODO:这里重新绑定远程Service
}
}
mService = IBookManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient,0);

2.3 Android中的IPC方式

进阶之路 | 奇妙的IPC之旅-LMLPHP

2.3.1 Bundle

  • 原理:Bundle底层实现了Parcelable接口,它可方便的在不同的进程中传输。
  • 注意:Bundle不支持的数据类型无法在进程中被传递。
  • IntentBundle的区别与联系:

2.3.2 文件共享

  • 概念:两个进程通过读/写同一个文件来交换数据。比如A进程把数据写入文件,B进程通过读取这个文件来获取数据。
  • 适用场景:对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读/写的问题。
  • 特殊情况:SharedPreferences也是文件存储的一种,但不建议采用。因为系统对SharedPreferences的读/写有一定的缓存策略,即在内存中有一份该文件的缓存,因此在多进程模式下,其读/写会变得不可靠,甚至丢失数据。

2.3.3 AIDL

2.3.3.1 概念

AIDL(Android Interface Definition Language,Android接口定义语言):如果在一个进程中要调用另一个进程中对象的方法,可使用AIDL生成可序列化的参数,AIDL会生成一个服务端对象的代理类,通过它客户端实现间接调用服务端对象的方法。

2.3.3.2 支持的数据类型
  • 基本数据类型
  • StringCharSequence
  • ArrayListHashMap且里面的每个元素都能被AIDL支持
  • 实现Parcelable接口的对象
  • 所有AIDL接口本身
2.3.3.3 两种AIDL文件
  • 用于定义Parcelable对象,以供其他AIDL文件使用AIDL中非默认支持的数据类型的。
  • 用于定义方法接口,以供系统使用来完成跨进程通信的。
2.3.3.4 本质,关键类和方法

a:本质是系统提供了一套可快速实现Binder的工具。

b:关键类和方法是什么?

  • AIDL接口:继承IInterface
  • StubBinder的实现类,服务端通过这个类来提供服务。
  • Proxy:服务器的本地代理,客户端通过这个类调用服务器的方法。
  • asInterface():客户端调用,将服务端的返回的Binder对象,转换成客户端所需要的AIDL接口类型对象。
  • asBinder():返回代理ProxyBinder对象。
  • onTransact():运行服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。
  • transact():运行在客户端,当客户端发起远程请求的同时将当前线程挂起。之后调用服务端的onTransact()直到远程请求返回,当前线程才继续执行。

进阶之路 | 奇妙的IPC之旅-LMLPHP

2.3.3.5 实现方法

A.服务端:

  • 创建一个aidl文件
  • 创建一个Service,实现AIDL的接口函数并暴露AIDL接口。

B.客户端:

  • 通过bindService绑定服务端的Service
  • 绑定成功后,将服务端返回的Binder对象转化AIDL接口所属的类型,进而调用相应的AIDL中的方法。
2.3.3.6 可能产生ANR的情形

A.客户端:

  • 调用服务端的方法是运行在服务端的Binder线程池中,若主线程所调用的方法里执行了较耗时的任务,同时会导致客户端线程长时间阻塞,易导致客户端ANR
  • onServiceConnected()onServiceDisconnected()里直接调用服务端的耗时方法,易导致客户端ANR

B.服务端:

  • 服务端的方法本身就运行在服务端的Binder线程中,可在其中执行耗时操作,而无需再开启子线程
  • 回调客户端Listener的方法是运行在客户端的Binder线程中,若所调用的方法里执行了较耗时的任务,易导致服务端ANR
2.3.3.7 解注册失败的问题
  • 原因: Binder进行对象传输实际是通过序列化和反序列化进行,即Binder会把客户端传递过来的对象重新转化并生成一个新的对象,虽然在注册和解注册的过程中使用的是同一个客户端传递的对象,但经过Binder传到服务端后会生成两个不同的对象。另外,多次跨进程传输的同一个客户端对象会在服务端生成不同的对象,但它们在底层的Binder对象是相同的。
  • 解决办法:当客户端解注册的时候,遍历服务端所有的Listener,找到和解注册Listener具有相同的Binder对象的服务端Listener,删掉即可。

2.3.4 Messager

Q1.什么是Messager

A1:Messager是轻量级的IPC方案,通过它可在不同进程中传递Message对象。

Messenger.send(Message);

Q2:特点是什么

  • 底层实现是AIDL,即对AIDL进行了封装,更便于进行进程间通信。
  • 其服务端以串行的方式来处理客户端的请求,不存在并发执行的情形,故无需考虑线程同步的问题。
  • 可在不同进程中传递Message对象,Messager可支持的数据类型即Messenge可支持的数据类型。
  • 有两个构造函数,分别接收Handler对象和Binder对象。

Q3:实现的方法

A1:服务端:

  • 创建一个Service用于提供服务;
  • 其中创建一个Handler用于接收客户端进程发来的数据
  • 利用Handler创建一个Messenger对象;
  • ServiceonBind()中返回Messenger对应的Binder对象。

A2:客户端:

  • 通过bindService绑定服务端的Service

  • 通过绑定后返回的IBinder对象创建一个Messenger,进而可向服务器端进程发送Message数据。(至此只完成单向通信)

  • 在客户端创建一个Handler并由此创建一个Messenger,并通过MessagereplyTo字段传递给服务器端进程。服务端通过读取Message得到Messenger对象,进而向客户端进程传递数据。(完成双向通信)

    进阶之路 | 奇妙的IPC之旅-LMLPHP

Q4:缺点:

  • 主要作用是传递 Message,难以实现远程方法调用。
  • 以串行的方式处理客户端发来的消息的,不适合高并发的场景。

2.3.5 ContentProvider

  • 除了onCreate()运行在UI线程中,其他的query()update()insert()delete()getType()都运行在Binder线程池中。
  • CRUD四大操作存在多线程并发访问,要注意在方法内部要做好线程同步。
  • 一个SQLiteDatabase内部对数据库的操作有同步处理,但多个SQLiteDatabase之间无法同步。

2.3.6 Socket

Q1:使用类型是什么?

  • 流套接字:基于TCP协议,采用流的方式提供可靠的字节流服务。
  • 数据流套接字:基于UDP协议,采用数据报文提供数据打包发送的服务。

Q2:实现方法是什么?

A1:服务端:

  • 创建一个Service,在线程中建立TCP服务、监听相应的端口等待客户端连接请求;
  • 与客户端连接时,会生成新的Socket对象,利用它可与客户端进行数据传输;
  • 与客户端断开连接时,关闭相应的Socket并结束线程。

A2:客户端:

  • 开启一个线程、通过Socket发出连接请求;
  • 连接成功后,读取服务端消息;
  • 断开连接,关闭Socket

2.3.7 优缺点比较

Bundle简单易用只能传输Bundle支持的数据类型四大组件间的进程间通信
文件共享简单易用不适合高并发场景,无法做到进程间的即时通信无并发访问,交换简单数据且实时性不高
AIDL支持一对多并发和实时通信使用稍复杂,需要处理线程同步一对多且有RPC需求
Messenger支持一对多串行通信不能很好处理高并发,不支持RPC,只能传输Bundle支持的数据类型低并发的一对多
ContentProvider支持一对多并发数据共享可理解为受约束的AIDL一对多进程间数据共享
Socket支持一对多并发数据共享实现细节繁琐网络数据交换

2.4 Binder连接池

Q1:工作原理是什么

每个业务模块创建自己的AIDL接口并实现此接口,然后向服务端提供自己的唯一标识和其对应的Binder对象。服务端只需要一个Service,服务器提供一个queryBinder接口,它会根据业务模块的特征来返回相应的Binder对像,不同的业务模块拿到所需的Binder对象后就可进行远程方法的调用了。

进阶之路 | 奇妙的IPC之旅-LMLPHP

Q2:实现方式是什么

  • 为每个业务模块创建AIDL接口并具体实现
  • Binder连接池创建AIDL接口IBinderPool.aidl并具体实现
  • 远程服务BinderPoolService的实现,在onBind()返回实例化的IBinderPool实现类对象
  • Binder连接池的具体实现,来绑定远程服务
  • 客户端的调用

三.碎碎念


如果文章对您有一点帮助的话,希望您能点一下赞,您的点赞,是我前进的动力

本文参考链接:

05-27 08:34