IPC(中)
1 Android中IPC方式
在第一篇IPC(上)中我们已经介绍了IPC的基础知识:序列化和Binder,本篇将详细介绍各种跨进程通讯方式.具体有如下几种:
Intent中extras传递
共享文件
Binder
ContentProvider
Socket
1.1 Bundle
四大组件中的三大组件(Activity,Service,Receiver)都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口,所以他可以方便在不同进程间传输,所以在我们开启另一个进程的Activity,Service,Receiver时候,就可以使用Bundle的方式来,但是有一点需要注意,我们在Bundle中的数据必须可以被序列化,比如基本数据类型,实现了Parcelable接口的对象,实现了Serializable接口的对象等等,具体支持类型如下
如果是Bundle不支持的类型我们无法通过它在进程间通讯.但有的时候可以适当改变下实现方式来解决问题,比如A进程进行计算,得到结果后给到B进程,但是结果的数据类型Bundle不支持传递,那么这个时候我们可以将计算过程放在B进程的后台服务中,然后当需要计算的时候A进程通过Intent告知B进程的Service开始计算了,由于Service在B进程所以可以很方便的拿到数据,这样就成功避免了进程间通讯的问题.
1.2 使用文件共享
共享文件也是一种进程间通讯的方式,两个进程通过读/写同一个文件来交换数据,交换信息除了文本信息外,还可以序列化对象到文件在从另一个进程中读取这个对象,但是有一点需要注意,Android基于Linux,并发读/写文件没有限制,当两个线程同时写文件的时候可能会出现问题,这里尤其需要注意.下面是序列化对象到文件共享数据的栗子
这次我们在MainActivity中,序列化一个User对象到文件,在另一个进程运行的SecondActivity中反序列化,看对象的属性值是否相同.
// MainActivity
public void serialize(View v) {
User user = new User("zhuliyuan", 22);
try {
File file = new File(getCacheDir(), "user.txt");
FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(user);
fos.close();
oos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
//SecondActivity
public void deserialize(View v) {
File file = new File(getCacheDir(), "user.txt");
if (file.exists()) {
try {
FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis);
User user = (User) ois.readObject();
fis.close();
ois.close();
Log.i("yyjun", user.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
下面看下日志
可以发现SecondActivity成功恢复了User数据,这里虽然数据相同但是和之前MainActivity的User对象并不是同一个.
通过文本共享这种方式共享数据对文本格式没有要求,只要双方按约定好格式即可.但是也有局限性当并发读/写的时候读取的文件可能不是最新的,如果并发写就更加的严重了,要尽量避免这种情况或者使用线程同步来限制并发.通过上面分析我们可以知道,文件共享方式适合在对数据同步要求不高的进程间进行通信,并且需要妥善处理并发问题.
当然SharedPreferences是个特例,它通过键值对方式存储数据,在底层上采用xml文件来存储键值对,一般情况每个应用的SharedPreferences文件目录位于/data/data/package name/shared_prefs目录下.从本质上来说SharedPreferences也是属于文件的一种,但是由于系统对它的读写有一定的缓存策略,所以内存中会有一份SharedPreferences缓存,而在多进程模式下,系统对他读写变得不可靠,当高并发时候有很大几率丢失数据,因为,在多进程通讯的时候最好不要使用SharedPreferences.
api文档中也明确指出了这一点
1.3 使用Messenger
Messenger可以翻译为信使,通过它可以在不同进程中传递Message对象,在Message中放 入我们想传递的数据,就可以实现数据的进程间传递了.Messenger底层实现是AIDL,这个可以通过构造方法初见端倪
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
是不是可以明显看出AIDL的痕迹,既然这么像那我们就通过源码来分析下,车总是要发的不过且慢,我觉得咱们先看下Messenger的栗子热热身,在来分析更好
实现步骤如下
服务端进程
首先在服务端创建一个Service来处理客户端的连接请求,同时创建一个Handle并通过它来创建一个Messenger对象,然后在Service中的onBind中返回这个Messenger对象底层的Binder即可
客户端进程
客户端进程中,首先需要绑定服务端service,绑定成功后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger向服务端发送消息,发送的消息类型为Message对象,如果需要服务端能够回应客户端,就必须和服务端一样,创建一个Handle并创建一个新的Messenger,并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端就可以通过replyTo参数回应客户端.
下面先来个简单的栗子,此栗中服务端无法回应客户端
在service中创建一个handle,然后new一个Messenger将Handle作为参数传入,再在onBind方法中返回Messenger底层的Binder
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 Constants.TYPE_MSG_FROM_CLIENT:
Log.i(TAG, "receiver client msg " + msg.getData().getString("msg"));
break;
}
}
}
private Messenger mMessenger = new Messenger(new MessengerHandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
在注册service让其在单独的进程
<service android:name=".MessengerService"
android:process=":remote"/>
接下来客户端实现,先绑定MessengerService服务,在根据服务端返回的Binder对象创建Messenger并使用此对象向服务端发送消息.
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private Messenger mMessenger;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mMessenger = new Messenger(service);
Message msg = Message.obtain(null, Constants.TYPE_MSG_FROM_CLIENT);
Bundle bundle = new Bundle();
bundle.putString("msg", "this is client msg");
msg.setData(bundle);
try {
mMessenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindService(new Intent(this, MessengerService.class), mConnection, BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConnection);
}
}
最后运行,看一下日志,很显然服务端收到了客户端的消息
01-18 09:49:01.823 31013-31013/? I/MessengerService: receiver client msg this is client msg
通过上面栗子可以看出,Messenger中进行数据传输必须将数据放入Message中,而Messenger和Message都实现了Parcelable接口,因此可以跨进程传输.简单来说Message中所支持的数据类型就是Messenger支持的数据类型,而Message中能作为载体传送数据的只有what,arg1,arg2,obj,replyTo,而obj在同一进程中是很实用的,但是进程间通讯的时候,在Android2.2以前obj不支持跨进程传递,2.2以后仅仅支持系统实现的Parcelable接口的对象才能通过它来传递,也就等于我们自定义的类即使实现了parcelable也无法通过obj传递,但是不要方,我们还有Bundle可以支持大量的数据传递.
具体在Message的obj字段的注释可以窥探一二.
可以看到在跨进程传递的时候,obj只支持非空的系统实现Parcelable接口的数据,要想传递其他数据使用setData,也就是Bundle方式,Bundle中可以支持大量的数据类型.
上面只能客户端向服务端发送信息,但有的时候我们还需要能够回应客户端,下面就介绍如何实现这种效果.还是上面的栗子只是稍微改下,当服务端接受到客户端消息后回复客户端接受成功.
首先我们修改下客户端,为了接受服务端发送消息,客户端也需要一个接受消息的Messenger和Handler
private Messenger clientMessenger = new Messenger(new ClientHandler());
private static class ClientHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case Constants.TYPE_MSG_FROM_SERVICE:
Log.i(TAG, msg.getData().getString("reply"));
break;
}
}
}
还有一点就是客户端发送消息的时候,需要把接受服务端回复的messenger通过message的reply带到服务端