使用Messenger进程间通信

前言

Android 进程间通信的方式之一就是使用Messenger进行,本文详细介绍如何使用Messenger的方式进行进程间通信

效果

Messenger是什么

官网中是这么说的:

引用处理程序,其他人可以使用该处理程序向其发送消息。通过在一个进程中创建一个指向处理程序的Messenger并将该Messenger移交给另一个进程,可以实现跨进程的基于消息的通信。
注意:底下的实现只是Binder用于执行通信的简单包装器。这意味着从语义上讲您应该这样对待:此类不会影响流程生命周期管理(您必须使用一些更高级别的组件来告诉系统您的流程需要继续运行),如果流程消失,连接将断开由于任何原因等

说人话就是:使用Messenger通过Handler将Message发送到另一个进程,实现了进程间通信,底层依然是使用了Binder机制

如何使用

关于使用鸿洋在2015年的时候写了一个Android 基于Message的进程间通信 Messenger完全解析,在其文章中也介绍了相应的源码。本文说一下其使用方式

这里使用两个应用作为不同的进程进行处理

客户端如何写

1
2
3
4
5
6
7
//step1:新建类继承Handler
class ClientHandler : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
//获取到服务端发过来的数据
}
}
1
2
//step2:创建信使
private val clientMessenger: Messenger = Messenger(ClientHandler())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//step3:创建连接接口
private var serverMessenger: Messenger? = null

private val connect = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
//获取到服务端的Messenger
serverMessenger = Messenger(service)
}

override fun onServiceDisconnected(name: ComponentName?) {

}
}

//step4:使用bindService连接
val intent = Intent()
intent.component =
//参数1:appid
//参数2:对应Service的路劲
ComponentName("com.allens.intent", "com.starot.lib_intent.messenger.MessengerService")
//参数1:当前的intent意图。
//参数2:连接监听器
//参数3:类型 BIND_AUTO_CREATE:只要绑定存在,就自动创建服务
context.bindService(intent, connect, BIND_AUTO_CREATE)
1
2
3
4
5
6
7
8
9
10
11
//step5:发送消息
//创建Message
val message: Message = Message.obtain(null, 10001)
val bundle = Bundle()
bundle.putString("data", info)
bundle.putInt("index", index)
message.obj = bundle
//将客户端的Messenger发给服务端
message.replyTo = clientMessenger
//使用send方法发送
serverMessenger?.send(message)

服务端如何写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//step1:继承Service
class MessengerService : Service() {
//step2:新建类继承Handler
class MessengerHandler : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
//接受客户端发过来的数据
}
}

//step3:创建信使
private val mMessenger = Messenger(MessengerHandler())

override fun onBind(intent: Intent?): IBinder? {
//step4:将Messenger对象的Binder返回给客户端
return mMessenger.binder
}
}
1
2
3
4
5
6
7
<application
<!--step5:注册服务-->
<service
android:name="com.starot.lib_intent.messenger.MessengerService"
android:enabled="true"
android:exported="true"/>
</application>

当服务端从Handler中获取了客户端发过来的Message,通过replyTo拿到客户端的Messenger,在使用此Messenger回信给客户端。

1
2
3
4
5
6
7
8
9
10
//step6:服务端向客户端发送数据
val messenger = msg.replyTo as Messenger
//创建Message
val message: Message = Message.obtain(null, 10001)
val bundle = Bundle()
bundle.putString("data", "我是服务端 index:$index")
bundle.putInt("index", index)
message.obj = bundle
//获取客户端传递过来的Messenger,通过这个Messenger回传消息给客户端
messenger.send(message)

注意事项

绑定服务

在网上也发现不少人使用隐式意图的方式去bindservice,在官网中也明确说明了

注意:如果您使用意图绑定到 Service,请使用显式 意图确保您的应用程序安全 。使用隐式意图启动服务会带来安全隐患,因为您无法确定哪个服务将对意图做出响应,并且用户无法看到哪个服务会启动。从Android 5.0(API级别21)开始,如果您bindService() 使用隐式意图进行调用,系统将引发异常。

所有示例中并没有加入 action,和 category,而是指定bind具体哪个service

指定process

因为示例使用的是两个不同的apk,所以不在需要设置不同进程,如果是在同一个应用,需要对servcie 设置 android:process=":进程名"

指定exported

其次,示例中对service 设置了android:exported="true" 的处理,exported属性表示其他应用程序的组件是否可以调用该服务或与之交互 true可以,false不可以

其默认值取决于服务是否包含意图过滤器 。没有任何过滤器意味着只能通过指定其确切的类名称来调用它。这意味着该服务仅供应用程序内部使用(因为其他人将不知道类名)。因此,在这种情况下,默认值为“ false”。另一方面,如果存在至少一个过滤器,则意味着该服务是供外部使用的,因此默认值为“ true

官网有此参数详细的介绍

使用permission

当我设置了android:exported="true" 以后,提示我👇的信息 Exported service does not require permission 翻译过来就是 导出的服务不需要许可

官网中也有对其的介绍

实体必须具有的权限名称才能启动服务或绑定到该服务。如果主叫方 startService(), bindService()或者 stopService(),没有被授予这个权限,该方法将无法正常工作,并意图对象将不会被传递到服务。如果未设置此属性,则由元素的 permission 属性设置的权限 适用于服务。如果未设置任何属性,则该服务不受权限保护。

为了消除隐患,就需要专门设置一个permission 然后在使用的进程中去注册

1
2
3
4
5
6
7
8
<application
<!-- 在注册service 的xml 中添加一个权限名称,任意即可 -->
<service
android:name="com.starot.lib_intent.messenger.MessengerService"
android:enabled="true"
android:permission="com.starot.lib_intent.CUSTOME_PERMISSION"
android:exported="true"/>
</application>

对照文档,然后在使用其服务的进程中 注册权限

1
<uses-permission android:name="com.starot.lib_intent.CUSTOME_PERMISSION"/>

也可以使用 addPermission 方法动态添加

⚠️⚠️⚠️但是当我按照官网提示,去做了这些处理,反而出现了异常

1
java.lang.SecurityException: Not allowed to bind to service Intent { cmp=com.allens.intent/com.starot.lib_intent.messenger.MessengerService }

最终在 凯哥的提醒下发现了自己的错误原因

参考 官网 声明一个权限 在service注册的 manifest 中申明一下,这里使用到就是普通权限,

1
2
3
4
<permission android:name="com.starot.lib_intent.CUSTOME_PERMISSION"
android:description="@string/permission_des"
android:protectionLevel="signature"
/>

传递自定义参数类型

如果需要传递的是自定义的参数类型,需要在同样的目录位置下放置相同的代码

如👇所示,进程A 的User对象,目录位置在 com.starot.model_interprocess_communication.bean.User

对应进程B的User对象,也需要将User放在同样的目录下,如👇所示

android 11

在Android11 上面会出现

1
2
2020-11-17 19:40:56.612 549-2864/system_process I/AppsFilter: interaction: PackageSetting{36c65ad com.allens.demo/10159} -> PackageSetting{79692c4 com.allens.intent/10158} BLOCKED
2020-11-17 19:40:56.612 549-2864/system_process W/ActivityManager: Unable to start service Intent { cmp=com.allens.intent/com.starot.lib_intent.messenger.MessengerService } U=0: not found

目前还不知道原因

总结

Messenger作为进程间通信的方案,可以实现数据的传输,却不能做到方法的调用与执行,实际使用上并不多。