IPC
进程间通信或跨进程通信,是指两个进程之间数据交换的过程。
多进程
一个应用存在多个进程的情况
使用多进程的目的
-
自身需要。
-
向其他应用获取数据。
安卓开启多进程的方式
给四大组件指定android:process
私有进程
android:process=":remote"(完整写法android:process="com.lwj.ipctest:remote")
可通过修改ShareUID 、签名和它跑在同一进程的全局进程
android:process="com.lwj.ipctest.remote"
多进程造成的影响
-
静态成员和单例模式完全失效。(每个进程都会分配一个独立的虚拟机,不同虚拟机在内存分配上有不同的地址空间,导致不同虚拟机中访问同一个类对象会产生多份副本。)
-
线程同步机制完全失效。(原因同1)
-
SharedPreferences的可靠性下降。(底层通过读写xml文件实现,,两个线程并发写会导致一定几率的数据丢失。)
-
Application会多次创建。(当一个组件跑在一个新进程中时,系统要创建新的进程同时分配独立的虚拟机,这个过程其实是启动一个应用的过程,把应用重新启动了一遍,会创建新的application。)
在多进程中,不同进程的组件会拥有独立的虚拟机、application以及内存空间。
IPC基础
Serializable
java提供的一个序列化接口,空接口,为对象提供标准的序列化和反序列化操作
作用
Intent、Binder传输数据,通过对象持久化到存储设备上。
使用方式
-
implements Serializable
-
声明一个serialVersionUID(用于辅助序列化和反序列化,序列化后的数据中的serialVersionUID和当前类的serialVersionUID相同才能正常的被反序列化。该值应手动指定。)
序列化
将实现了Serializable接口的对象通过ObjectOutputStream写进文件中。
反序列化
将文件通过ObjectInputStream读出来。
序列化前的对象和反序列化后的对象内容一样,但不是同一个对象。
注:
-
静态成员变量属于类不属于对象,不会参与序列化过程。
-
用transient关键字标记的成员变量不参与序列化过程。
-
系统默认序列化过程可用通过重写writeObject方法和readObject方法改变。
Parcelable
作用
Intent、Binder传输数据,内存序列化(序列化后存到存储设备中或者通过网络传输实现比较复杂,建议用Serializable)。
使用方式
实现Parcelable接口
可实现功能
序列化、反序列化、内容描述
-
序列化由writeToParcel方法完成,最终由write方法完成。
-
反序列化由CREATOR完成,最终由read方法完成。
-
内容描述由describeContents方法完成,大部分情况下应返回0。
Serializable 与Parcelable 比较
Serializable是java的序列化接口,使用简单但开销大,序列化和反序列化需要大量IO操作。
Parcelable是安卓中的序列化方式,使用于android平台,使用麻烦,效率高。
Binder
一个类,实现IBinder接口,可用于跨进程通信。主要用于Service中,包括AIDL和Messenger(Messager底层是Binder)
变量、方法含义
DESCRIPTOR
Binder唯一标识,一般用当前类目表示,比如"com.lwj.ipctest.IBookManager"。
asInterface(IBinder obj)
用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象。(这种转换区分进程,如果客户端和服务端位于同一进程,方法返回服务端的Stub对象本身,否则返回系统封装后的Stub.proxy对象)
asBinder
返回当前Binder对象。
onTransact
该方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求通过系统底层封装后交由此方法处理。(可以做权限验证,限定能远程调用服务的进程)
Proxy#getBookList
运行再客户端。
Proxy#addBook
运行在客户端。
Binder的工作机制图如下图所示。

图 Binder的工作机制
工作原理
-
Client 挂起:客户端通过 transact() 把参数写入 data,然后线程挂起等待结果。
-
驱动转发:Binder 驱动将请求从客户端进程拷贝到服务端进程。
-
服务端执行:服务端线程池里的线程调用 onTransact() 解析参数、执行业务逻辑,结果写入 reply。
-
返回并唤醒:驱动把 reply 传回客户端,挂起的客户端线程被唤醒,拿到结果继续执行。
总结:
整个过程对上层是同步调用,但底层是"发送 + 挂起 + 唤醒"的等待机制,由 Binder 驱动完成跨进程的数据中转。
服务端进程由于某种原因异常终止,这个时候服务端的Binder连接断裂(称之为Binder死亡),会导致远程调用失败,客户端功能受到影响。Binder提供方法linkToDeath和unLinkToDeath来解决该问题。
linkToDeath
通过该方法给Binder设置一个死亡代理,当Binder死亡时,就会回调binderDied方法,就可以移出之前绑定的binder代理并重新绑定远程服务。
unLinkToDeath
解除binder绑定的死亡代理。
IPC方式
Bundle
-
已经实现Parcelabel接口,可以在Bundle中附加需要传输给远程进程的信息并通过Intent出去。
-
支持Activity、Service、Receiver三大组件。
-
传输数据必现能够被序列化,如:基本数据类型、实现了Parcelabel接口的对象、实现了Serializable接口的对象以及一些Android支持的特殊对象。
文件共享
-
两个进程通过读写同一个文件来交换数据。
-
数据格式没有要求,可以是文件,也可以是xml。
-
需处理好并发读写,适合对数据同步要求不高的进程间进行通信。
-
SharePreferences是安卓轻量级存储方案,通过键值对的方式来存储数据,底层采用xml文件来存储键值对,目录:/data/data/package_name_shared_prefs。系统对它有缓存策略,多进程模式下,系统对它的读写不可靠,不适用于并发和进程间通信。
Messenger
通过它可以在不同进程中传递Message对象,在Message对象中放入我们需要传递的数据,实现跨进程通信。
Messenger是一种轻量级的IPC方案,底层实现是AIDL。
一次处理一个请求,服务端不存在并发执行的情况。
使用步骤:
服务端
-
创建一个Service用来处理客户端的连接请求。
-
创建一个Handler、Messager。
-
在Servie的onBind中返回这个Messager对象底层的Binder。
客户端
-
绑定服务端的Service。
-
绑定成功后用服务端返回的IBinder对象创建一个Messenger。
-
通过这个Messenger给服务端发送消息,发送消息类型为Message对象。
-
如果需要服务端可以回消息给客户端,就需要像服务端一样创建一个Handler和Messenger,把这个Messenger通过Message的replyTo参数传递给服务端,服务端通过replyTo参数就可以回应客户端了。
在Messenger中进行数据传递必须将数据放入Message中,而Messenger和Message都实现了Parcelabel接口,因此可以跨进程传输。Message支持的数据类型就是Messenger所支持的传输类型。Message中能使用的载体有what、arg1、arg2、Bundle以及replyTo。
Messenger的工作原理如下图所示。

图 Messenger的工作原理
缺点:以串行的方式处理客户端发来的消息,如果有大量的消息同时发送到服务端,服务端只能一个一个处理。有时候需要调用服务端的方法,Messenger也无法做到。
AIDL
使用方法
服务端
-
创建一个Service来监听客户端的连接请求。
-
创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明。
-
最后在Service中实现这个接口。
客户端
-
绑定服务端的Service。
-
将服务端返回的Binder对象转成AIDL接口所属的类型。
-
调用AIDL中的方法。
支持的数据类型
-
基本数据类型(int、long、char、boolean、double等);
-
String和CharSequence;
-
List:只支持ArrayList,里面每个元素都必须能够被AIDL支持;
-
Map:只支持HashMap,里面每个元素都必须能够被AIDL支持,包括ksy和value;
-
Parcelable:所有实现了Parcelabel接口的对象;(需显示import)
-
AIDL:所有的AIDL接口本身也可以在AIDL文件中使用。(需显示import)
注:
-
AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out或者inout。in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。
-
AIDL接口只支持方法,不支持声明静态常量。
-
AIDL的包结构在服务端和客户端要保持一致,因为客户端需要反序列化服务端中和AIDL接口相关的类。
-
AIDL方法是在服务端的Binder线程池中执行的,存在多个服务端同时连接时,会存在多个线程同时访问的情形,需要在AIDL方法中处理线程同步,可以直接使用CopyOnWriteArrayList来进行自动的线程同步。
-
对象不能跨进程直接传输。
-
使用RemoteCallbackList来删除跨进程listener的接口,RemoteCallbackList是一个泛型,支持管理任意的AIDL接口。
-
RemoteCallbackList工作原理:内部有一个Map结构专门用来保存所有的AIDL回调,key是IBinder类型,value是Callback类型。Callback类型封装了真正的远程listener。当客户端注册listener时,会把这个listener信息存入mCallbacks中;当客户端解注册时,RemoteCallbackList会帮助我们遍历服务端所有的listener,找出那个和解注册listener和具有相同Binder对象的服务端listener,并把它删除掉。客户端进程终止后,它能够自动移除客户端所注册的listener,RemoteCallbackList内部实现了线程同步功能。
-
遍历RemoteCallbackList时,beginBroadcast和finishBroadcast必须要配对使用。
-
客户端调用远程服务的方法时,被调用的方法运行在服务端的Binder池中,客户端线程会被挂起,如果服务端方法执行比较耗时,就会导致客户端线程长时间阻塞在这里,如果客户端是UI线程的话,就会导致客户端ANR。如果明确知道某个远程方法比较耗时,那么就要避免在客户端的UI线程中去访问远程方法。
-
服务端方法本身就运行在服务端的Binder线程池中,服务端本身就可以执行大量耗时操作,这个时候不要再在服务端方法中开线程去执行异步操作。
Binder 意外死亡
服务端进程意外死亡,需要重连服务。有两种方法。
-
给Binder死亡时,我们会收到binderDied方法的回调,在binderDied方法中可以重连服务。
-
在onServiceDisconnected中重连服务。
区别在于onServiceDisconnected在客户端的UI线程中被调用,binderDied在客户端的Binder线程池中被回调。
添加使用权限验证功能
默认情况下,远程服务是任何人都可以连接。我们可以加入权限验证功能,权限验证失败则无法调用服务的方法。
方式
-
在onBind中使用permission进行验证。(该方法适用于Messenger)
-
在onTranscat中进行验证。
-
采用Uid和Pid来验证,通过getCallingUid和getCallingPid可以拿到客户端所属应用的Uid和Pid,通过这两个参数可以做验证工作,如:验证包名。
使用方式
在服务端的onBind方法做权限验证。
在客户端的AndroidManifest声明所需的权限。
ContentProvider
ContentProvider是安卓中提供的专门用于不同应用间进行数据共享的方式。底层实现也是Binder。
自定义ContentProvider 步骤
- 继承ContentProvider类并实现六个抽象方法。
-
onCreate:代表ContentProvider的创建,可以做一些初始化动作。(主线程)
-
getType:用来返回一个Uri请求所对应的MIME类型(媒体类型),比如:视频、图片。如果不关注这个选项,可以直接返回null或者"*/*"。(Binder线程池)
-
CRUD。(Binder线程池)
- 注册
-
android:authorities(必须是唯一的,后面通过这个唯一标识来访问该provider)
-
android:permission(添加android:authorities属性后,外界想要访问该provider的就必须添加该权限)
-
android:readPermission
-
android:writePermission
注:
-
ContentProvider主要以表格形式来组织数据,并且可以包含多个表。每个表格都具有行和列的层次性,行往往对应一条记录,列对应一条记录中的一个字段。
-
除了表格形式,ContentProvider还支持文件数据,比如图片、视频等。文件数据和表格数据的结构不同,处理这类数据时可以在ContentProvider中返回文件的句柄给外界让外界来访问ContentProvider中的文件信息。MediaStore就是文件类型的ContentProvider。虽然ContentProvider底层数据看起来很像一个SQLite数据库,但是ContentProvider对底层数据存储方式没有任何要求,我们既可以使用SQLite数据库,也可以使用普通的文件,甚至采用内存中的一个对象来进行数据存储。
-
通过SQLiteOpenHelper来管理数据的创建、升级和降级。通过Provider向外界提供上述数据库中的信息。
-
ContentProvider通过Uri来区分外界要访问的数据集合。为了知道外界要访问的是哪个表,我们需要为它们定义单独的Uri和Uri_Code,使用UriMatcher的addURI方法来关联Uri和Uri_Code。外界访问provider时就可以根据请求的Uri来得到Uri_Code,有了Uri_Code就知道访问的是哪张表了。
-
CRUD存在多线程并发,方法内部需要处理好线程同步。采用SQLite且只有一个SQLiteDatabase的连接,可以正确应对多线程的情况,因为SQLiteDatabase内部对数据的操作有同步处理,如果是多个SQLiteDatabase对象来操作数据库就无法保证线程同步。
Socket
Socket也称"套接字",分为流式套接字和用户数据报套接字两种,分别对应网络的传输控制层中的TCP和UDP协议。Socket本身支持传输任意字节流。
表. TCP、UDP对比
|-------------------------------------|----------------------|
| TCP | UDP |
| 面向连接的协议 | 无连接 |
| 提供稳定的双向通信功能 | 提供不稳定的单向通信功能(也可双向通信) |
| 连接需要经过"三次握手" | 效率高 |
| 为了提供稳定的数据传输功能,本身提供超时重传机制,因此具有很高的稳定性 | 不保证数据一定能够正确传输 |
注
- 使用Socket通信需要声明权限。
- 不能在主线程中访问网络。(网络操作可能是耗时的,放在主线程会影响程序响应效率)
使用方式
声明权限-->远程Service建立一个TCP服务-->连接TCP服务-->客户端连接生成Socket-->服务通过Socket与客户端通信-->客户端断开连接-->服务端关闭Socket并结束通话进程。
共享内存
SharedMemory
使用步骤:
服务端
创建一个Binder服务 -》新建一个SharedMemory -》通过服务的Binder将SharedMemory的文件描述符传递出去
客户端
通过Service获得Binder -》通过Binder获得ParcelFileDescriptor,将其转化为FileDescriptor -》 将该文件描述符映射到内存中 -》 读取共享内存中的数据
优点:高效,直接操作内存中的数据。
缺点:需自行同步控制,多进程访问同一份内存可能发生冲突。
注意事项
1、共享内存中的数据不进行自动同步,需要手动进行同步控制。
2、使用共享内存时需要定义好数据结构,以便在各个进程中能够准确地解析数据。
3、共享内存大小需要提前定义好,一旦创建后不能扩展,否则可能破坏数据结构
Binder连接池
AIDL衍生通信方式。
作用
将每个业务模块的Binder请求统一转发到远程Service中去执行,避免重复创建Service的过程。
解决问题
当多个业务模块都需要AIDL通信时,需要建立多个AIDL和Service,应用会很重量级,浪费系统资源。使用Binder连接池可以对Service进行复用,减少Service数量,将AIDL放在同一个Service中去管理。
工作机制
- 每个业务模块创建自己的AIDL接口并实现此接口,不同业务模块之间不能有耦合,所有实现细节要单独开。
- 向服务端提供自己唯一标识和其对应的Binder对象。
- 服务端提供一个queryBinder接口,这个接口根据业务模块特征来返回相应的Binder对象给客户端
- 不同业务模块拿到所需 的Binder对象就可以进行远程方法调用。
工作原理如下图所示:

图 Binder连接池的工作原理
选取IPC方式
|-----------------|------------------------------------------|-----------------------------------------------------------|------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 名称 | 优点 | 缺点 | 使用场景 | demo |
| Bundle | 简单易用 | 只能传输Bundle支持的数据类型 | 四大组件间的进程通信 | |
| 文件共享 | 简单易用 | 不适合高并发场景,且无法做到进程间实时通信 | 无并发访问情形,交换简单的数据是实时性不高的场景 | 代码1 代码2 |
| AIDL | 功能强大,支持一对多并发通信,支持实时通信 | 使用稍复杂,需处理好线程同步 | 一对多通信且有RPC需求 | 代码1 代码2 |
| Messenger | 功能一般,支持一对多串行通信,支持实时通信 | 不能很好处理高并发情形,不支持RPC,数据只能通过Message进行传输,因此只能传输Bundle支持的数据类型。 | 低并发的一对多即使通信,无RPC需求,或者无需要返回结果的RPC需求 | https://gitee.com/flying-guy/ds/blob/master/HelloWorldDemo/IPCTest/src/main/java/com/lwj/ipctest/MessengerActivity.java |
| ContentProvider | 在数据源访问方面功能强大,支持一对多并发数据共享,可通过Call方法拓展其他操作 | 可以理解为受约束的AIDL,主要提供数据源的CRUD操作。 | 一对多的进程间的数据共享 | https://gitee.com/flying-guy/ds/blob/master/HelloWorldDemo/IPCTest/src/main/java/com/lwj/ipctest/ProviderActivity.java |
| Socket | 功能强大,可以通过网络传输字节流,一对多并发实时通信 | 实现细节稍微繁琐,不支持直接的RPC | 网络数据交换 | https://gitee.com/flying-guy/ds/blob/master/HelloWorldDemo/IPCTest/src/main/java/com/lwj/ipctest/TCPClientActivity.java |
| Binder连接池 | 与AIDL类似,性能更好 | 实现有点复杂,需处理好线程关系 | 多个业务模块都需要使用AIDL,秀技术专享hhh | 代码1 代码2 https://gitee.com/flying-guy/ds/blob/master/HelloWorldDemo/IPCTest/src/main/java/com/lwj/ipctest/TCPClientActivity.java |