第二章: IPC机制
本章包括:
1. Android多进程概念,
2. 多进程开发注意事项,
3. 序列化机制,
4. binder(AIDL),
4. 进程间通信方式,
5. binder连接池,
6. 各种进程间通信方式的优缺点及使用场景.
IPC简介
多进程模式
IPC 基础概念
IPC 方式
Binder连接池
选择合适的IPC方式
一 . IPC 简介
线程:
CPU
调度的最小单元,是一种有限的系统资源,
进程:一个执行单元,一个进程中可以包含多个线程.
IPC(Inter-Process Communication)
: 进程间通信或者跨进程通信
一个进程中可以只有一个线程,在Android
中叫UI
线程,如果耗时任务放在主线程执行,则容易引起ANR
- 除了
Binder
,Android
还支持Socket
,Socket
可以用在两个终端间通信,也可以用在一个设备上的两个进程间通信。 Android
对单个应用所使用的最大内存做了限制,早期是16M
,不同设备有不同大小。
二. 多进程模式
开启多进程
- 添加
android:process
即可在应用内实现多进程, - 以
":"
开头,则是私有进程,其他应用的组件不可以和它跑在同一个进程中 - 不以
":"
开头,则属于全局进程,其它应用通过ShareUID方式可以和它跑在同一个进程中 - 没指定
process
属性的进程,运行在默认进程中,进程名为包名。
多进程运行机制
- 每个进程都会分配独立的虚拟机(有着不同的地址),一个类的多个副本互不干扰.
- 不同进程的组件的确会有独立的虚拟机,
Application
及内存空间, - 同一个应用间的多进程相当于两个不同的应用采用了
SharedUID
的模式. - 运行在不同进程中的四大组件,通过内存共享数据都会失败。
- 多进程造成的几个方面的影响:
- 静态成员和单例模式完全失效;
- 线程同步机制完全失效;
SharedPreferences
的可靠性下降;Application
会多次创建
三. IPC基础概念
Serializable: 序列化
Parcelable: 序列化
Binder: 传递序列化后的数据
- Serializable
- 只有
serialVersionUID相同
才能够被正常的反序列化 - 不指定则会根据当前类自动生成hash值,如果反序列化时
类结构改变则序列化失败而crash
静态成员
,transient变量 -> 不会参与序列化
- 最好手动指定
serialVersionUID
,一般都会手动指定serialVersionUID = 1L
,保证发序列化成功. - 开销很大,需要大量
I/O
操作
- 只有
- Parcelable
- Parcelable在Android上
效率更高
,建议Android上用此种方式 - Parcelable是Android推荐的序列化方式,主要用在内存序列化上
- Parcelable在Android上
Binder
Binder
实现了IBinder
接口,是Android
中的一种跨进程通信方式,也可以理解为一种物理设备(对应/dev/binder
)Binder
是ServiceManager
链接各种Manager和ManagerService
的桥梁.SDK
会自动为我们生产AIDL对应的Binder类
.所有Binder中传输的接口都需要实现IInterface
(包括Stub
).transact是垮进程的
,当客户端和服务端在同一个进程时,则不会走此方法,这个逻辑由Stub
的内部类Proxy
完成.- 可以通过
isBinderAlive来判断Binder
是否死亡 - 当
bindService
的时候,服务端会返回一个包含了服务端业务调用的 Binder 对象 - 当客户端发起远程请求时,线程会被挂起,因此如果远程方法是耗时的,则不能再UI线程发起远程请求。
binder相关方法
- asInterface
- 用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的【如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本
- 如果客户端和服务端位于同一进程,asInterface则返回的就是Stub本身,否则为Stub.proxy.
//Activity中
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
}
//...
};
//...
protected void onCreate(@Nullable Bundle savedInstanceState) {
bindService(intent,connection,Context.BIND_AUTO_CREATE);
}
- asBinder
//Stub
@Override
public android.os.IBinder asBinder() {
return this;
}
//Proxy
private android.os.IBinder mRemote;
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
从
Binder
代码中可以看出asBinder
的返回值,是通过Stub.asInterface(service);
传过去的,就是这个service
.
- onTransact(服务端接收)
参数解析:
2. code:确定客户端请求的方法
3. data:目标方法有参数的话,就从data取出
4. reply:目标方法有返回值,就向reply中写入返回值
- DESCRIPTOR
private static final java.lang.String DESCRIPTOR = "com.IBookManager";
//系统生成的,Binder的唯一标识,一般用当前Binder的类名表示。
- transact(客户端调用)
binder死亡的两种处理办法:
- 设置死亡代理,
在onServiceDisconnect中
重连服务.
设置死亡代理的步骤:
声明一个DeathRecipient(接口)对象,实现binderDied方法
- 在客户端绑定远程服务成功后,给
binder
设置死亡代理
// Activity
// binderDied在客户端binder线程池回调.
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (mIAidlCall == null)
return;
mIAidlCall.asBinder().unlinkToDeath(mDeathRecipient, 0);
mIAidlCall = null;
// 重新绑定远程服务
}
};
//onServiceDisconnect在客户端UI线程回调:
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mIAidlCall = IAidlCall.Stub.asInterface(service);
try {
service.linkToDeath(mDeathRecipient, 0);
Toast.makeText(getApplicationContext(), mIAidlCall.getName(),
Toast.LENGTH_LONG).show();
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
in,out,inout代表什么
in 表示数据只能由客户端流向服务端,
out 表示数据只能由服务端流向客户端,
而 inout 则表示数据可在服务端与客户端之间双向流通
四. IPC通信方式
名称 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
Bundle | 简单 | 只支持Bundle支持的数据类型 | 四大组件 |
文件共享 | 简单 | 不适合高并发且无法做到进程间即时通信 | 无并发访问,实时性不高的场景 |
AIDL | 强大,,一对多并发,实时通信 | 适用稍复杂 | 一对多通信且有RPC需求 |
Messenger | 功能一般,支持一对多串行,实时通信 | 不能很好处理高并发,不支持RPC,只支持Bundle支持的数据,必须将数据放进Message中 | 低并发,一对多,即时通信,无RPC需求 |
Contentprovider | 一对多,可通过Call方法扩展 | 可理解为受约束的aidl,主要提供CRUD操作 | 一对多进程间数据共享 |
Socket | 功能强大,通过网络传输字节流 | 实现细节稍微麻烦,支持RPC | 网络数据交换 |
四种通信注意事项
- Bundle中必须传递可以被序列化,Bundle本身实现了Parcelable接口.
- SharedPreferences属于文件共享来实现IPC,因为其缓存策略,将变得不可靠.
- Messenger,其内部依然使用的是Binder.必须将数据放进Message中
- Socket,分为TCP(流式套接字)和UDP(用户数据报套接字),TCP面向连接,UDP面向无连接.
- ContentProvider 除了
onCreate
运行在ui线程,其他方法运行在 binder线程池中- Socket分为TCP,UDP.
AIDL支持的数据类型:
- 基本数据类型;
- Sring 和 CharSequence;
- ArrayList,里面的对象必须也是AIDL支持的
- HashMap,里面的对象必须也是AIDL支持的
- Parcelable,(必须声明同名的AIDL,并parcelable进来)
- AIDL接口本身(需要import进来)
- 除了基本类型外,其他类型的
参数必须标上方向,in(输入)/out(输出)
- AIDL的包结构,在服务端和客户端必须一致(反序列化)
- 自定义的Parcelable对象和AIDL对象必须显示的 import进来
- 如果AIDL文件中用到了自定义的Parcelable对象,必须建立同名的AIDL文件.
AIDL的注意事项:
- AIDL接口回调需要用
RemoteCallBackList
,才能被正确的 反注册.(beginBroadcast和finishBroadcast
配对使用)
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
final int N = mListenerList.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
if (l != null) {
try {
l.onNewBookArrived(book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
mListenerList.finishBroadcast();
}
- 客户端的
onServiceConnected
和onServiceDisconnect
运行在UI线程,服务端方法运行在Binder线程池
中 - AIDL权限的验证功能,可以在
onBind中验证(验证失败返回null)
或者在服务端的onTransact中验证(验证失败返回false)
.
AIDL创建过程
- 首先创建一个Service和一个AIDL接口,
- 接着创建一个类继承自AIDL接口中的Stub类并实现Stub中的抽象方法,
- 在Service的onBind方法中返回这个类的对象,
- 然后客户端就可以绑定服务端的Service了。(编译器通过aidl文件生成一段代码,通过预先定义的接口达到两个进程内部通信进程的目的)
- AIDL定义一个接口,客户端(调用端)通过bindService来与远程服务端建立连接,该连接建立时会返回一个IBinder对象,该对象是服务端Binder的BinderProxy,客户端通过asInterface函数将该BinderProxy对象包装成本地的Proxy,并将远程服务端的BinderProxy对象赋值给Proxy类的mRemote字段,通过mRemote执行远程方法调用
五. Binder连接池
- Binder连接池是为了避免创建过多的Service,将所有的AIDL都放到一个Service中管理,提供queryBinder接口来判断不同的Binder.
- 避免创建过多的Service和AIDL文件.
- CountDownLatch类介绍:
CountDownLatch的介绍和使用
使用IPC的一个小Demo:IPCDemo