进程间通讯篇系列文章目录:
Android 查缺补漏 (IPC 篇)-- Bundle 文件共享 ContentProviderMessenger 四种进程间通讯介绍
Android 查缺补漏 (IPC 篇)-- 款进程通讯之 AIDL 详解
Android 查缺补漏 (IPC 篇)-- 跨进程通讯之 Socket 简介及示例
在 Android 中进程间通信的实现方式有多种, 包括: Bundle 文件共享 ContentProviderMessengerAIDLSocket 等等, 其各有各的优缺点, 接下来就分别介绍一下上述各种进程间的通信方式及实现
一 Bundle
ActivityServiceReceiver 都支持 Intent 中传递 Bundle 数据, 由于 Bundle 实现了 Parcelable 接口, 所以它可以方便的在不同的进程间传输
传输的数据必须能够被序列化:
基本类型
String 类型
CharSequence
实现了 Parcelable 接口的对象
实现了 Serializable 接口的对象
List
IBinder 类型
SparseArray
二文件共享
文件共享作为进程间通讯时, 无法解决并发读写时所带来的问题, 所以只适合在对数据同步要求不高的进程间通讯
其实 SharedPreferences 也属于文件共享方式的一种, sp 是 android 中提供的一种轻量级存储方案, 通过键值对的方式来存储数据, 底层用 xml 文件来存储键值对每个应用的 sp 文件放在当前包所在的 data 目录下, 位于 / data/data/package_name/shared_prefs 目录下由于系统对它的读写有一定的缓存策略, 即在内存中会有一份 sp 文件的缓存, 因此在多进程模式下, 它变得不可靠
三 ContentProvider
ContentProvider 在前面介绍四大组件时就已经介绍过了, 这里就不多说了, 详见 Android 查缺补漏 --ContentProvider 的使用
四 Messenger
1Messenger 是什么?
通过 Messenger 可以很方便的在不同的进程之间传递 Messager 对象, 在 Messager 对象中就可以放入我们需要传递的数据我们知道, 跨进程传输数据有很多种方式, 其中 AIDL 最为强大也最为常用, 而 Messenger 即相当于 AIDL 的简化版, 其底层也是采用 AIDL 实现, 是一种轻量级的跨进程传输方案
所谓简化版常常功能有限, Messenger 也不例外, 相对于 AIDL 它的功能确实弱化了不少, 在方便使用的同时它一次只能处理一个请求
2 实现一个简单的 Messenger 通讯
实现一个 Messenger 需要在两个进程中做以下操作:
为了避免歧义, 我们提前约定, 以下所说的服务端若不做特别说明默认指 Service 所在的进程
在服务端: 首先, 创建一个 Service 充当服务端进程, 然后在 Service 中创建一个 Messenger, 因为创建 Messenger 时需要传入一个 Handler, 所以还要创建一个 Handler, 接收数据的操作就在 Handler 中, 最后在 Service 的 onBind 中返回这个 Messenger 的 Binder 即可
示例代码如下:
- /**
- * 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" />
在客户端: 就像绑定一个普通的 Service 一样, 通过 bindService 方法链接 Service 即可, 不同点是, 需要在 ServiceConnection 接口中的 onServiceConnected 方法中通过参数中的 IBinder 创建一个 Messenger 对象, 通过这个 Messenger 对象即可向服务端发送 message 消息
示例代码如下:
- 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!
3 服务端如何回应客户端?
到上面这一步一个简单的 Messenger 通讯就完成了, 接下在 MessengerActivity 中我们就可以使用 Messenger 对象向服务端发送数据了, 但是如何才能得到服务端的回应呢, 或者服务端想向客户端发送数据怎么办?
这个其实也很简单, 我们只需要在客户端这里也创建一个 Messenger, 然后再向服务端发送数据时在 Message 的 replyTo 指向客户单的 Messenger 对象即可, 如下:
message.replyTo = clientMessenger;
然后再服务端通过获取 Message.replyTo, 就可以获取到客户端的 Messenger:
Messenger client = msg.replyTo;
具体实现如下:
在上面的 MessengerActivity 中增加一个 Handler, 并通过 Handler 创建一个 Messenger, 在向服务端发送一条消息时告诉服务器接收回复的 messenger:
- // 新增一个 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;
- }
- };
在上面的 MessengerService 中获取客户端的 Messenger, 并通过获取的这个 Messenger 对象向客户端回应消息:
- 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
4 通过 Messenger 实现两个 App 通讯
接下来试验一下将两个进程中放入两个 App 中:
新建一个名为 IpcClient 的 Module, 将 MessengerActivity 拷贝过去, 在 bindService 时, 不要忘记将 Intent 稍作修改一下, 因为现在 MessengerService 在另外一个工程中, 无法直接传入到 Intent 中, 需改为如下方式:
- 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)");
其他代码不变
然后再原来的工程, 将配置文件中的 MessengerService 的 exported 属性设置为 true, 否则外界将无法调用此 Service
- <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!
5Messenger 可以传输的数据类型包括:
简单来说, Messenger 可以传输 Message 可承载的数据类型, 而 Message 中能使用的载体有: whatarg1arg2Bundle 和 replyTo, 其实 Message 中还有一个 Object 类型的载体, 这个载体在同一个进程中非常使用, 但是在 Android2.2 之前 object 字段不支持跨进程传输, 在 2.2 之后也仅支持系统提供的实现 Parcelable 接口的对象所以总结起来, Messenger 在跨进程时可传递的类型如下:
Bundle 类型
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 知识进行再次梳理, 查缺补漏的学习过程, 一方面是对自己遗忘的东西加以复习重新掌握, 另一方面相信在重新学习的过程中定会有巨大的新收获, 如果你也有跟我同样的想法, 不妨关注我一起学习, 互相探讨, 共同进步!
参考文献:
Android 开发艺术探索
源码地址: 本系列文章所对应的全部源码已同步至 github, 感兴趣的同学可以下载查看, 结合代码看文章会更好源码传送门
来源: https://www.cnblogs.com/codingblock/p/8387752.html