简介
IPC方式其实有很多,比如在Intent中附加extras来传递信息,或者通过共享文件的方式来共享数据,还可以采用Binder方式来跨进程通信,另外ContentProvider天生就是支持跨进程访问的,还有网络通信,用Socket实现IPC,但它们在使用和侧重点上有很大区别,下面来详细逐个介绍一下。
Bundle
由于Bundle实现了Parcelable接口,所以可以很方便的在不同进程间传输。在四大组件中,Activity、Service、Receiver 这三大组件都支持在Intent中传递Bundle数据。
由于Bundle中需要添加数据,所以这些数据,必须是能够被序列化的数据,如基本类型、实现了Parcelable或Serializable接口的对象、以及一些Android支持的特殊对象,具体支持哪些类型,可以查看Bundle类源码。
如果某计算结果无法被序列化,导致在Bundle中无法添加,那么可以尝试用Bundle先向目标进程传递计算前的数据,在目标进程中再去计算结果。
使用文件共享
共享文件方式就是两个进程通过读写同一个文件数据,来实现进程间通信。
在Windows上,一个文件被加入了排斥锁,将会导致其它进程无法对其进行访问。但是由于Android基于Linux,使其读写可以并发操作且无限制,所以共享文件其实有操作风险。
在文件中,除了可以交换文本信息,也可以将对象序列化保存到文件中,然后在另一个进程中反序列化恢复这个对象。如下示例:
我们在MainActiivty的onResume()中调用如下方法:
private void persistToFile() {
File file = new File("data/data/com.example.onsaveinstancestateteset/SaveFileTest.txt");
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
new Thread(new Runnable() {
@Override
public void run() {
try {
FileOutputStream fileStream = new FileOutputStream(file);
User user = new User(1, "hello word", true);
ObjectOutputStream outputStream = new ObjectOutputStream(fileStream);
outputStream.writeObject(user);
fileStream.close();
outputStream.close();
Log.e("MainActivity", "user:" + user.toString());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
然后在SencondActivity中的onResume()调用如下方法:
private void recoverFromFile() {
new Thread(new Runnable() {
@Override
public void run() {
User user = null;
File file = new File("data/data/com.example.onsaveinstancestateteset/SaveFileTest.txt");
if (file.exists()) {
try {
ObjectInputStream input = new ObjectInputStream(new FileInputStream(file));
user = (User) input.readObject();
Log.e("MainActivity2", "recover user:" + user.toString());
input.close();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}).start();
打印结果如下
2021-03-31 14:46:29.615 19511-19555/com.example.onsaveinstancestateteset E/yanjianyang: user:id = 1 userName = hello word isMale =true
2021-03-31 14:46:31.612 19511-19564/com.example.onsaveinstancestateteset E/yanjianyang: recover user:id = 1 userName = hello word isMale =true
通过文件共享这种方式来进行IPC是没有具体文件格式要求的,可以是txt,也可以是XML,只要读/写双方约定好数据格式即可。
通过文件共享这种方式也是有局限性的,比如读/写并发的问题,如果读写同时进行,读出的内容可能不是最新的了,如果同时写入,并发问题就更严重了,所以我们应该使用这种方式的同时,考虑线程同步的问题。
当然SharedPreferences是个特例,它是Android中提供的轻量级缓存方案,它通过键值对的方式来存储数据,在底层使用的是XML文件来存储,默认文件位置在/data/data/package name/shared_prefs目录下,其中package name表示的是当前的包名。系统对它的读/写有一定的缓存策略,即在内存中会有一份SharedPreferences文件的缓存,因此在多进程模式下,系统对它的读写变的不再可靠,并发高时很有可能丢失数据,因此,不建议在IPC中使用SharedPreferences。
使用Messenger
Messenger是一种轻量级的IPC方案,它的底层实现是AIDL,比如它的构造方法中这样写到:
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
从IMessenger、Stub.asInterface()等部分都可以表明,它的底层就是AIDL。
Messenger对AIDL做了封装,我们可以很简便的使用它。同时由于它一次就处理一个请求,因此在服务端我们不用考虑线程同步的问题,这是因为在服务端中不存在并发执行的情景。接下来我们讲如何实现:首先分为客户端和服务端
- 服务端
首先我们在服务端要创建一个Service来处理客户端的请求,同时创建一个Handler,并通过它来创建一个Messenger对象,然后在Service的onBind()方法中返回这个Messenger对象底层的Binder即可。示例代码如下:
public class MessageService extends Service {
private static final String TAG = "MessageService";
private static class Messagehandler extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case 1:
Bundle message = msg.getData();
String msgData = message.getString("msg");
Log.i(TAG, "receive msg from Client:" + msgData);
break;
default:
super.handleMessage(msg);
}
}
}
private final Messenger mMessenger = new Messenger(new Messagehandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
然后将Service注册为单独的线程中:
<service
android:name=".MessageService"
android:process=":remote" />
- 客户端
客户端先要绑定服务端Service,绑定成功后,用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息了,发送消息的类型为Message对象。
如果需要服务端能够响应客户端,那么客户端就像服务端一样,我们也还需要创建一个Handler并创建一个新的Messenger,并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端通过这个参数就可以回应客户端。
下面我们创建客户端,绑定服务端的MessengerService之后,根据服务端返回的binder对象创建Messenger对象,并利用这个对象向服务器发送消息,示例代码如下:
public class MessengerActivity extends AppCompatActivity {
private static final String TAG = "MessengerActivity";
private Messenger mService;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = new Messenger(service);
Message msg = Message.obtain(null, 1);
Bundle data = new Bundle();
data.putString("msg", "hello,this is from client.");
msg.setData(data);
try {
mService.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_messenger);
Intent intent = new Intent(this, MessageService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConnection);
}
}
运行之后我们得到如下log内容:
I/MessageService: receive msg from Client:hello,this is from client.
如上表示我们的服务端已经成功接收到了客户端发来的消息。
通过上面的例子我们能看出,Messenger想要进行数据传递必须通过Message,而Messenger和Message都实现了Parcelable接口,因此都可以进行跨进程传输,简单来说Message传输的类型就是Messenger传输的类型。
实际上,通过Messenger来传输Message,Message中能使用的载体只有what、arg1、arg2、Bundle以及replyTo。
Message中的另一个字段object在同一个进程中是很实用的,但在进程间通信的时候,Android2.2以前,object字段是不支持跨进程传输的,2.2之后系统才支持了Parcelable接口,才可以传输。这意味着我们自定义的Parcelable对象是无法通过object字段来传输的。由于非系统的Parcelable对象无法通过object字段来传输,导致该字段实用性大大降低,而Bundle支持大量的数据类型,所以我们的Bundle更常用一些。
上面介绍了,客户端向服务端发送一条数据,如果服务端向客户端返回一条响应数据,我们可以这么做:
首先在服务端的Handler中做如下修改:
private static class Messagehandler extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case 1:
Bundle message = msg.getData();
String msgData = message.getString("msg");
Log.i(TAG, "receive msg from Client:" + msgData);
//如下修改
Messenger client = msg.replyTo;
Message replyMessage = Message.obtain(null, 2);
Bundle data = new Bundle();
data.putString("reply", "我收到你的消息了,稍后回复你~");
replyMessage.setData(data);
try {
client.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
而客户端为了接受服务端返回的响应,需要也添加一个接收消息的Handler和Messenger:
private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case 2:
Bundle message = msg.getData();
message.getString("reply");
Log.i(TAG, "receive msg from service: " + message);
break;
default:
super.handleMessage(msg);
}
}
}
另外,最重要的一点,即在向服务端发送Message之前,配置replyTo参数:
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
...
data.putString("msg", "hello,this is from client.");
msg.setData(data);
//下面这句是关键!
msg.replyTo = mGetReplyMessenger;
...
}
...
};
完成如上代码后,运行效果如下:
I/MessengerActivity: receive msg from service: Bundle[{reply=我收到你的消息了,稍后回复你~}]
最后贴上一副Messenger的工作原理图,以便于更好的理解Messenger:
更多Android中的IPC方式,将在下一篇中继续探讨。