本文作者:CodingBlock 文章链接:https://cloud.tencent.com/developer/article/1351736
在Android中进程间通信的实现方式有多种,包括:Bundle、文件共享、ContentProvider、Messenger、AIDL、Socket等等,其各有各的优缺点,接下来就分别介绍一下上述各种进程间的通信方式及实现。
Activity、Service、Receiver都支持Intent中传递Bundle数据,由于Bundle实现了Parcelable接口,所以它可以方便的在不同的进程间传输。
传输的数据必须能够被序列化:
文件共享作为进程间通讯时,无法解决并发读写时所带来的问题,所以只适合在对数据同步要求不高的进程间通讯。
其实SharedPreferences也属于文件共享方式的一种,sp是android中提供的一种轻量级存储方案,通过键值对的方式来存储数据,底层用xml文件来存储键值对。每个应用的sp文件放在当前包所在的data目录下,位于/data/data/package_name/shared_prefs目录下。由于系统对它的读写有一定的缓存策略,即在内存中会有一份sp文件的缓存,因此在多进程模式下,它变得不可靠。
ContentProvider在前面介绍四大组件时就已经介绍过了,这里就不多说了,详见《Android查缺补漏--ContentProvider的使用》
通过Messenger可以很方便的在不同的进程之间传递Messager对象,在Messager对象中就可以放入我们需要传递的数据。我们知道,跨进程传输数据有很多种方式,其中AIDL最为强大也最为常用,而Messenger即相当于AIDL的简化版,其底层也是采用AIDL实现,是一种轻量级的跨进程传输方案。
所谓简化版常常功能有限,Messenger也不例外,相对于AIDL它的功能确实弱化了不少,在方便使用的同时它一次只能处理一个请求。
实现一个Messenger需要在两个进程中做以下操作:
为了避免歧义,我们提前约定,以下所说的服务端若不做特别说明默认指Service所在的进程。
示例代码如下:
/**
* Created by liuwei on 18/1/29.
*/
public class MessengerService extends Service {
private static final String TAG = MessengerHandler.class.getSimpleName();
// 创建一个Handler,处理消息用
private class MessengerHandler extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case AppConstants.MSG_FROM_CLIENT:
// 接收客户端发来的消息
Log.i(TAG, "handleMessage: MSG_FROM_CLIENT:" + msg.getData().getString("msg"));
break;
default: super.handleMessage(msg);
}
}
}
// 创建Messenger
private Messenger messenger = new Messenger(new MessengerHandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
// 返回Messenger的Binder
return messenger.getBinder();
}
}
这里要想让Service跑在另外一个进程中需要在AndroidManifest文件中添加process节点:
<service
android:name=".messager.MessengerService"
android:process=":remote" />
示例代码如下:
public class MessengerActivity extends AppCompatActivity {
private final static String TAG = MessengerActivity.class.getSimpleName();
Button btn_bind_messenger;
Intent intent;
Messenger messenger;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
btn_bind_messenger = ViewUtils.findAndOnClick(this, R.id.btn_bind_messenger, mOnClickListener);
intent = new Intent(this, MessengerService.class);
}
private View.OnClickListener mOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_bind_messenger:
// 绑定Service
bindService(intent, connection, BIND_AUTO_CREATE);
break;
}
}
};
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 链接messenger成功,利用service创建一个Messenger
messenger = new Messenger(service);
// 向服务端发送一条消息
Message message = Message.obtain(null, AppConstants.MSG_FROM_CLIENT);
Bundle bundle = new Bundle();
bundle.putString("msg", "client bind messenger succeed!");
message.setData(bundle);
try {
messenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
messenger = null;
}
};
@Override
protected void onDestroy() {
unbindService(connection);
super.onDestroy();
}
}
上面代码中ViewUtils.findAndOnClick()是博主封装的一个方法,功能只是绑定控件并为控件添加onClick监听器,无需重点关注,接下来运行工程点击按钮,在Android Monitor中将切换到remote进程,查看log如下:
.../cn.codingblock.ipc:remote I/MessengerHandler: handleMessage: MSG_FROM_CLIENT:client bind messenger succeed!
到上面这一步一个简单的Messenger通讯就完成了,接下在MessengerActivity中我们就可以使用Messenger对象向服务端发送数据了,但是如何才能得到服务端的回应呢,或者服务端想向客户端发送数据怎么办?
这个其实也很简单,我们只需要在客户端这里也创建一个Messenger,然后再向服务端发送数据时在Message的replyTo指向客户单的Messenger对象即可,如下:
message.replyTo = clientMessenger;
然后再服务端通过获取Message.replyTo,就可以获取到客户端的Messenger:
Messenger client = msg.replyTo;
具体实现如下:
// 新增一个MessengerHandler
private class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case AppConstants.MSG_FROM_SERVICE:
Log.i(TAG, "handleMessage: MSG_FROM_SERVICE:" + msg.getData().getString("msg"));
break;
default:super.handleMessage(msg);
}
}
}
// 创建一个clientMessenger
private Messenger clientMessenger = new Messenger(new MessengerHandler());
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 链接messenger成功,利用service创建一个Messenger
messenger = new Messenger(service);
// 向服务端发送一条消息
Message message = Message.obtain(null, AppConstants.MSG_FROM_CLIENT);
Bundle bundle = new Bundle();
bundle.putString("msg", "client bind messenger succeed!");
message.setData(bundle);
// 关键点,告诉服务器接收回复的messenger
message.replyTo = clientMessenger;
try {
messenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
messenger = null;
}
};
private class MessengerHandler extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case AppConstants.MSG_FROM_CLIENT:
// 接收客户端发来的消息
Log.i(TAG, "handleMessage: MSG_FROM_CLIENT:" + msg.getData().getString("msg"));
// 服务端收到消息后,给客户端发送回应
Messenger client = msg.replyTo;
Message replyMsg = Message.obtain(null, AppConstants.MSG_FROM_SERVICE);
Bundle bundle = new Bundle();
bundle.putString("msg", "ok,I will reply you soon!");
replyMsg.setData(bundle);
try {
client.send(replyMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default: super.handleMessage(msg);
}
}
}
再次运行工程,点击按钮,将进程切换到主进程后log如下:
.../cn.codingblock.ipc I/MessengerActivity: handleMessage: MSG_FROM_SERVICE:ok,I will reply you soon!
至此,通过Messenger实现的一个完整的两个进程之间的交互过程完成了,上面的两个进程虽然都在同一个App中,但其效果同在两个App中几乎一致,只是如果是两个App的话在需要稍微修改一下绑定Service时的Intent并将Service的export属性设为true。
接下来试验一下将两个进程中放入两个App中:
intent = new Intent();
intent.setComponent(new ComponentName("cn.codingblock.ipc", "cn.codingblock.ipc.messager.MessengerService"));
为了能够更加方便的区分是哪个工程传到Service端的消息,在发送消息时加入如下信息:
bundle.putString("msg", "client bind messenger succeed!(from ipc_client)");
其他代码不变。
<service
android:name=".messager.MessengerService"
android:exported="true"
android:process=":remote" />
此时,工程结构如下:
运行IpcClient工程,点击按钮,首先在Android Monitor中切换到cn.codingblock.ipc:remote进程查看log如下:
.../cn.codingblock.ipc:remote I/MessengerHandler: handleMessage: MSG_FROM_CLIENT:client bind messenger succeed!(from ipc_client)
然后将进程切换到cn.codingblock.ipcclient中log如下:
.../cn.codingblock.ipcclient I/MessengerActivity: handleMessage: MSG_FROM_SERVICE:ok,I will reply you soon!
简单来说,Messenger可以传输Message可承载的数据类型,而Message中能使用的载体有:what、arg1、arg2、Bundle和replyTo,其实Message中还有一个Object类型的载体,这个载体在同一个进程中非常使用,但是在Android2.2之前object字段不支持跨进程传输,在2.2之后也仅支持系统提供的实现Parcelable接口的对象。所以总结起来,Messenger在跨进程时可传递的类型如下:
本篇介绍了四种比较简单的跨进程通信方式,这四种实现起来相对方便,但功能也非常有限,在后续的博文中将介绍AIDL和Socket的使用。下面的表格为以上四种跨进程通信方式的比较:
名称 | 优点 | 缺点 | 场景 |
---|---|---|---|
Bundle | 使用简单 | 1、只能传输Bundle支持的类型 2、不支持RPC | 四大组件间的通信 |
文件共享 | 使用简单 | 1、不适合并发 2、做不到即时通信 | 无并发访问、不要求实时通信的场景 |
ContentProvider | 1、在数据源访问方面功能强大 2、支持一对多 3、可通过call方法扩展其他操作 | 受约束的AIDL、主要提供数据的CRUD操作 | 一对多的进程间数据共享 |
Messenger | 1、支持一对多串行通信 2、支持实时通信 | 1、只能串行通信 2、只能传输Bundle支持的类型 3、不支持RPC | 低并发一对多即时通信、无RPC需求 |
最后想说的是,本系列文章为博主对Android知识进行再次梳理,查缺补漏的学习过程,一方面是对自己遗忘的东西加以复习重新掌握,另一方面相信在重新学习的过程中定会有巨大的新收获,如果你也有跟我同样的想法,不妨关注我一起学习,互相探讨,共同进步!
参考文献:
本文作者:CodingBlock 文章链接:https://cloud.tencent.com/developer/article/1351736