本文是《Android开发艺术探索》读书笔记系列第二章–IPC机制
一、IPC基础
Android IPC简介
Inter-Porcess Communication,含义为进程间通信或者跨进程通信,是指两个进程间进行数据交换的过程。Android间线程的通信是通过消息处理机制(Handler)实现的。在各种操作系统中都需要IPC机制。对于Android,虽然是基于Linux内核的移动操作系统,但是它有自己独有的进程间通信方式:Binder。关于Binder的介绍,后面会有说明。
Android中的多进程场景
Android中的多进程有以下两种场景
1. 第一种情况是一个应用因为某些原因自身需要采用多进程模式来实现,(比如:有些模块需要运行在额外的单独进程中;为了一个应用通过多进程来获得更大的可使用内存)
2. 当前应用需要向其他应用获取数据。
单应用开启多进程
在单个应用中,使用多进程只有一种方法, 那就是给四大组件在AndroidManifest中指定android:process属性,除此之外没有其他办法。也就是说我们无法给一个线程或者一个实体类指定其运行时所在的进程。(还有一种通过JNI在native层去fork新进程,属于特殊方法,暂不考虑)
多进程运行机制
Android为每一个应用,或者说每一个进程分配了一个独立的虚拟机,不同的虚拟机在内存分配上由不同的地址空间,这就导致在不同的虚拟机中访问同一个类对象会产生不同副本。
一般来说,使用多进程会造成以下问题:
1. 静态成员和单例模式完全失效
2. 线程同步机制完全失效
3. ShraredPreferences的可靠性下降
4. Application会多次创建
当一个组件跑在一个新的进程,由于系统要在创建新的进程同时分配独立的虚拟机,所以这个过程其实就是启动一个新的应用的过程。运行在同一个进程的组件是属于同一个虚拟机和同一个Application,同理运行在不同进程中的组件是属于两个不同的虚拟机和Application。
Serializable接口 和 Parcelable接口
Serializable接口 和 Parcelable接口可以完成对象的序列化过程,当需要用Intent和Binder传输数据时就需要将对象序列化才可以进行传输。有的时候需要对象持久化到存储设备上或者通过网络传输给其他客户端,这个时候也需要使用Serializable来完成持久化。
Serializable接口
Serializable是Java提供的一个序列化接口,是一个空接口,使用时只要在类生命中impletments Serializable即可(也可以在实现后指定一个serialVersionUID,如下所示)
public class User implements Serializable {
private static final long serialVersionUID = 871136882820L;
...
...
}
这个serialVersionUID 不是必须的,不声明也可以实现序列化。它是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的serialVersionUID 只有和当前类的serialVersionUID 相同才能够正常地被反序列化。它的工作机制是序列化的时候系统会把当前类的serialVersionUID 写入序列化文件中,当反serialVersionUID 系统回去检测文件中的serialVersionUID,看是否和当前的serialVersionUID 一致。
一般来说,我们要手动指定serialVersionUID,防止我们在修改类的成员变量后序列化失败。如果在手动指定后修改了成员变量结构,但是serialVersionUID没变,系统会最大限度的帮我们反序列化。要注意的是,静态成员变量属于类不属于对象,所以不参加序列化过程,其次用transient关键子标记的成员变量不参与序列化过程。
Parcelable接口
Parcelable也是一个接口,Parcelable的使用方法比Serializable复杂一点,是Android特有的序列化方式。实例一个典型用法。
public class User implements Parcelable{
public int userId;
public String userName;
public boolean isMale;
public User() {
}
public User(int userId, String userName, boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel out, int flags) {
out.writeInt(userId);
out.writeString(userName);
out.writeInt(isMale ? 1 : 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;
}
@Override
public String toString() {
return String.format(
"User:{userId:%s, userName:%s, isMale:%s}",
userId, userName, isMale);
}
}
对上述用法作一个解释。在序列化过程中需要实现的功能有序列化、反序列化和内容描述。
1. 序列化功能由WriteToParcel方法来完成,最终通过Parcel中的一系列write方法来完成
2. 反序列化功能由CREATOR来完成,其内部标明了如何创建序列化对象和数组,并通过Parcel的read方法完成反序列化。
3. 内容描述功能describeContents完成,几乎所有情况都返回0,仅当当前对象中存在文件描述符时返回1。
系统为我们提供了许多实现了Parcelable接口的类,如Intent、Bundle、Bitmap。
Serializable和Parcelable应用场景对比
Serializable:使用简单,但开销大,需要大量IO操作,一般在吧对象序列化到存储设备上或者将对象序列化后通过网络传输时使用Serializable(这两种情况使用Parcelable会稍显复杂)
Parcelable:Android的序列化方式,使用稍复杂但效率高,一般情况下首选Parcelable。
二、Binder
BInder是一个很深入的话题,作为一篇读书笔记,只作总结性的介绍,具体的使用还要大家多参考搜索引擎和源码。
Binder简介
直观来说,BInder是Andorid中的一个类,它实现了IBinder接口。从IPC角度来说Binder是Android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在Linux中没有;从Android Framework角度,Binder是ServiceManager连接各种Manager的桥梁。从Android应用层来说,BInder是Android服务端和客户端通信的媒介。
使用AIDL了解Binder
Android开发中,Binder主要用在Service中,包括AIDL和Messenger,Messenger的底层其实是AIDL,所以下面用AIDL的方式分析Binder工作机制。首先创建AIDL实例,在工程中新建三个文件Book.java,Book.aidl,IBookManager.aidl。Book.java在上面,不重复贴了
//Book.aidl,是Book类在AIDL中的声明
parcelable Book;
//IBookManager.aidl,我们定义的一个接口,包含两个方法,作为服务端的方法,被客户端调用
import com.ryg.chapter_2.aidl.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
创建完了之后,我们使用Android Studio Rebuild Project,会发现多了一个IBookManager.java类,这是系统为我们自动生成的Binder类,看一下这个Binder类来分析BInderd工作原理。代码比较长。后面会有对各个变量和方法的解释。
// Declare any non-default types here with import statements
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.conglingcao.yishutansuo.aidl.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.conglingcao.yishutansuo.aidl.IBookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.conglingcao.yishutansuo.aidl.IBookManager interface,
* generating a proxy if needed.
*/
public static com.conglingcao.yishutansuo.aidl.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.conglingcao.yishutansuo.aidl.IBookManager))) {
return ((com.conglingcao.yishutansuo.aidl.IBookManager) iin);
}
return new com.conglingcao.yishutansuo.aidl.IBookManager.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.conglingcao.yishutansuo.aidl.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.conglingcao.yishutansuo.aidl.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.conglingcao.yishutansuo.aidl.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.conglingcao.yishutansuo.aidl.IBookManager {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public java.util.List<com.conglingcao.yishutansuo.aidl.Book> getBookList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.conglingcao.yishutansuo.aidl.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.conglingcao.yishutansuo.aidl.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.conglingcao.yishutansuo.aidl.Book book) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book != null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.util.List<com.conglingcao.yishutansuo.aidl.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.conglingcao.yishutansuo.aidl.Book book) throws android.os.RemoteException;
}
可以看到根据IBookManager.aidl系统为我们生成了IBookManager.java这个类,它集成了IInterface这个借口,同时它自己也是个借口,所有可以再Binder中传输的接口都需要集成IInterface接口。下面详细介绍各个成员和方法的含义
内部类Stub
一个Binder类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程,而当两者位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub的内部代理Proxy来完成。
DESCRIPTOR
Binder的唯一标识,一般用当前Binder的类名表示。
asInterface(android.os.IBinder obj)
用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象。转换区分进程,如果客户端和服务端位于同一进程,那么返回的是服务端的Stub对象本身,否则返还的是系统分装后的Stub.Proxy对象。
asBinder()
返回当前Binder
Proxy#getBookList
这个方法运行在客户端,当客户端远程调用此方法时,它的内部实现是:
1. 创建该方法需要的输入型Parcel对象_data、输出型对象Parcel对象_reply、返回值对象List
2. 将参数信息写入_data(如果有)
3. 调用transact方法发起远程过程调用请求,同时当前线程挂起
4. 服务端的onTransact方法会被调用直到远程过程调用结束,结束后当前线程继续执行
5. 从_reply中取出返回的结果,return
Proxy#addBook
和上面的Proxy#getBookList一样,区别是没有返回值。
对各个方法讲解后,有两个地方需要注意:首先,当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果远程方法是耗时的,就不能再UI线程中调用它;其次。由于服务端的Binder方法运行在Binder线程池中,所以Binder方法不管是否耗时,都使用同步方式去实现。
如下是Binder的工作机制图
从上述分析过程来看,我们完全可以不提供AIDL文件即可实现Binder,之所以提供AIDL是为了方便系统为我们生成代码,这里比较建议大家手动去写一个,对理解AIDL和Binder机制有帮助(笔者曾在面试中被要求解释AIDL生成的各个方法和调用流程,如果有手动写过的话,说起来肯定非常轻松)
手动写Binder流程
1. 先声明好AIDL性质的接口(上面是IBookManager),继承IInterface
2. 写一个AIDL接口的实现类BookManagerImpl,继承了Binder(这个类就相当于是上面的Stub类),在里面写asInterface,重写onTransact、asBinder,实现内部类Proxy和里面的方法。
三、Android中的IPC方式
Android中除了Binder外,还有很多方法可以实现跨进程通信,下面简单的介绍各种Android中的IPC方式
1. 使用Bundle
四大组件中的三大组件(Activity、Service、Receiver)都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口,所以它可以方便地在不同的进程间传输。
当我们在一个进程中启动了另一个进程的Activity、Service或Receiver,我们就可以在Bundle中附加我们需要传输给远程进程的信息并通过Intent中发送出去(放到Intent里的数据必须是序列化的),Bundle不支持的类型我们无法进行传递,这是一种最简单的进程间通信方式
2. 使用文件共享
这种通信方式就是两个不同进程通过读/写同一个文件来交换数据。通过这种方式来共享数据对文件格式是没有要求的,可以是XML、文本文件或者是其他双方约定好的文件格式。但是SharedPreferences是特例,原因在于系统对它的读写有一定的缓存策略,即在内存中会有一份SharedPreferences文件的缓存,因此在多进程模式下,系统对它的读写就变得不可靠。
使用文件共享的缺点是要处理好多进程的并发读写问题。
3. 使用Messenger
通过Messenger可以再不同进程中传递Message对象,在Message中放入我们要传递的对象。Messenger是一种轻量级的IPC防范,它的底层实现是AIDL。由于它一次只处理一个请求,所以在服务端不会存在并发执行的情形,因此在服务端不用考虑线程的同步。
使用Messenger有以下几个步骤:
服务端:
a. 服务端作为一个Service,在里边创建了一个Handler,该Handler负责处理客户端传过来的Message。
b. 然后将这个Handler作为参数去创建一个Messenger。客户端:
a. 首先绑定Service拿到IBinder对象,用这个IBinder对象创建一个Messenger
b. 现在我们就可以用这Messenger的send方法向服务端发送各种消息了!可选项:服务端回复:上面两步走完就已经是一个可用的Messenger通信了,如果服务端Service收到客户端消息需要回复怎么办?
a. 首先,客户端在内部自己创建一个自己的Handler,作处理回复消息用。
b. 用这个Handler创建一个回复用的Messenger。
c. 客户端在发送message消息时,在meesege.reply = replyMessenger,设置回复Messenger,后面服务端端收到消息时就可以拿到这个回复Messenger用send方法作回复了。
这样干巴巴的文字说明不太容易懂,我们来看看代码
服务端代码
public class MessengerService extends Service {
private static final String TAG = "MessengerService";
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MyConstants.MSG_FROM_CLIENT:
Log.i(TAG, "receive msg from Client:" + msg.getData().getString("msg"));
Messenger client = msg.replyTo;
Message relpyMessage = Message.obtain(null, MyConstants.MSG_FROM_SERVICE);
Bundle bundle = new Bundle();
bundle.putString("reply", "嗯,你的消息我已经收到,稍后会回复你。");
relpyMessage.setData(bundle);
try {
client.send(relpyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
private final Messenger mMessenger = new Messenger(new MessengerHandler());
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
}
客户端代码
public class MessengerActivity extends Activity {
private static final String TAG = "MessengerActivity";
private Messenger mService;
private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MyConstants.MSG_FROM_SERVICE:
Log.i(TAG, "receive msg from Service:" + msg.getData().getString("reply"));
break;
default:
super.handleMessage(msg);
}
}
}
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mService = new Messenger(service);
Log.d(TAG, "bind service");
Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString("msg", "hello, this is client.");
msg.setData(data);
msg.replyTo = mGetReplyMessenger;
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName className) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
Intent intent = new Intent("com.ryg.MessengerService.launch");
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
unbindService(mConnection);
super.onDestroy();
}
}
优缺点
使用Messenger的优点是比较简便、轻量级,缺点是它在服务端处理消息是串行的,并且客户端只能够传递消息给服务端,不能够像Binder一样执行服务端的方法。
4. 使用AIDL
在前面介绍Binder的时候,我们就用AIDL去自动创建Binder,并分析了工作机制,这几就不多赘述。那创建了AIDL文件后生成了IInteface后,怎么去使用它呢?
服务端
- 在服务端Service中,通过这个IInteface中的内部类Stub创建一个Binder
- 在onBInd方法中返回这个Binder
客户端
- 连接sevice拿到binder
- 将binder作为stub.asInterface的参数传入,获取IInteface实现类(此类可以调用远程service的各种方法)
需要注意 ,因为Binder会把客户端传递过来的对象重新转化并生成一个新的对象,对象的跨进程传输本质上都是反序列化的过程,所以这两个对象并不是同一个对象,只是内容相同。
AIDL的权限验证
有两种方式:一是在onBind()方法中验证,至于验证的方式有很多种,比如permission验证,二是在服务端的onTransact方法中进行权限验证,如果验证失败则直接返回false
Binder的死亡重连
Binder是可能意外死亡的,这往往是因为服务端的进程出了问题,这是需要重新连接服务。有两种方法:一是给Binder设置DeathRecipient监听(在Binder线程池中被回调,所以不能访问UI)。二是在onServiceDisconnected中重连远程服务(运行在UI线程)
使用ContentProvider
ContentProvider是Android中提供的转梦用于不同应用间进行数据共享的方式,它的底层实现也是Binder。系统预置了许多ContentProvider,比如通讯录信日程表信息、音乐多媒体信息等。要跨进程访问这些信息,只需要通过ContentResolver的CRUD方法即可。
我们也可以自己实现一个ContentProvider,只要继承ContentProvider类并实现六个抽象方法即可
未完待续,持续更新