使用Messenger进程间通信

前言

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

效果

Messenger是什么

官网中是这么说的:

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

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

如何使用

关于使用鸿洋在2015年的时候写了一个Android 基于Message的进程间通信 Messenger完全解析,在其文章中也介绍了相应的源码。本文说一下其使用方式。Messenger可以理解。我给远方的你写信。然后你在回信给我。这样的方式去做到进程间通信。

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

客户端代码

首先看一下客户端详细的代码,主要还是服务的绑定。数据的发送还有数据的接受。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
//step1:新建类继承Handler
class ClientHandler(private val activity: MainActivity) : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when (msg.what) {
10001 -> {
val info = (msg.obj as Bundle).get("data") as String
activity.log("收到回信:$info")
}
10002 -> {
val info = (msg.obj as Bundle).get("index") as Int
activity.log("收到回信:$info")
}
10003 -> {
val info = (msg.obj as Bundle).get("user") as User
activity.log("收到回信:$info")
}
}
}
}

//step2:创建信使
private val clientMessenger: Messenger = Messenger(ClientHandler(this))

//step3:创建连接接口
private var serverMessenger: Messenger? = null

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

override fun onServiceDisconnected(name: ComponentName?) {
log("service disconnect")
}
}


companion object {
const val PKG = "com.allens.sample_service"
const val CLS = "com.allens.sample_service.messenger.MessengerService"
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

//使用此用例之前 请先运行 sample-service
binding.linear.addView(MaterialButton(this).apply {
text = "绑定服务"
setOnClickListener {
//step4:使用bindService连接
val intent = Intent()
//参数1:appId
//参数2:对应Service的路劲
intent.component = ComponentName(PKG, CLS)
//参数1:当前的intent意图。
//参数2:连接监听器
//参数3:类型 BIND_AUTO_CREATE:只要绑定存在,就自动创建服务
val bindService = context.bindService(intent, connect, BIND_AUTO_CREATE)
if (!bindService) {
log("绑定服务失败")
}
}
})



binding.linear.addView(MaterialButton(this).apply {
text = "解除绑定"
setOnClickListener {
context.unbindService(connect)
}
})

binding.linear.addView(MaterialButton(this).apply {
text = "发送String类型"
setOnClickListener {
//创建Message
val message: Message = Message.obtain(null, 10001)
val bundle = Bundle()
bundle.putString("data", "hello 你好")
message.obj = bundle
//将客户端的Messenger发给服务端
message.replyTo = clientMessenger
//使用send方法发送
serverMessenger?.send(message)
}
})

binding.linear.addView(MaterialButton(this).apply {
text = "发送Int类型"
setOnClickListener {
//创建Message
val message: Message = Message.obtain(null, 10002)
val bundle = Bundle()
bundle.putInt("index", 22)
message.obj = bundle
//将客户端的Messenger发给服务端
message.replyTo = clientMessenger
//使用send方法发送
serverMessenger?.send(message)
}
})

binding.linear.addView(MaterialButton(this).apply {
text = "发送自定义类型"
setOnClickListener {
//创建Message
val message: Message = Message.obtain(null, 10003)
val bundle = Bundle()
bundle.putSerializable("user",User("江海洋",18))
message.obj = bundle
//将客户端的Messenger发给服务端
message.replyTo = clientMessenger
//使用send方法发送
serverMessenger?.send(message)
}
})


//添加TextView 显示日志信息
binding.linear.addView(tv)
}


private val tv by lazy { AppCompatTextView(this) }

private val handler = Handler(Looper.getMainLooper())

private fun log(info: String) {
Log.i("sample-allens", info)
handler.post {
tv.append(info)
tv.append("\n")
}
}

客户端xml

看一下xml,多了两个东西。一个是queries,一个是permission

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.allens.sample_messenger">


<!-- android 11 上需要指定需要访问的进程appId -->
<queries>
<package android:name="com.allens.sample_service" />
</queries>

<!-- 如果服务定义了权限。那么这里需要申请权限 -->
<uses-permission android:name="com.allens.lib_intent.CUSTOM_PERMISSION"/>

<application
...
</application>

</manifest>

为何使用queries字段申明需要访问的进程包名

首先解释一下为什么要指定进程的appId,在Android11 上面会出现ActivityManager: Unable to start service not found.无法访问其他的进程。原因是在Android11加入了管理软件包可见性,所以需要在请求的进程中去添加queries字段申明需要访问的进程包名

为什么使用自定义权限

这个放在服务端配置中详细说明

服务端代码

继承Service即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

//step1:继承Service
class MessengerService : Service() {

//step2:新建类继承Handler
class MessengerHandler : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)

val acceptBundle = msg.obj as Bundle
when (msg.what) {
10001 -> {
val info = acceptBundle.get("data") as String
log("服务端:收到String类型的数据:$info")

//回复信使
val messenger = msg.replyTo as Messenger
val message: Message = Message.obtain(null, msg.what)
val bundle = Bundle()
bundle.putString("data", "我是服务端,我收到了你的消息{$info}")
message.obj = bundle
messenger.send(message)
}
10002 -> {
val index = acceptBundle.get("index") as Int
log("服务端:收到Int类型的数据:$index")

//回复信使
val messenger = msg.replyTo as Messenger
val message: Message = Message.obtain(null, msg.what)
val bundle = Bundle()
bundle.putInt("index", 10086)
message.obj = bundle
messenger.send(message)
}

10003 -> {
val user = acceptBundle.get("user") as User
log("服务端:收到自定义类型的数据:$user")

//回复信使
val messenger = msg.replyTo as Messenger
val message: Message = Message.obtain(null, msg.what)
val bundle = Bundle()
bundle.putSerializable("user",User("allens",20))
message.obj = bundle
messenger.send(message)
}
}
}

private fun log(info: String) {
Log.i("sample-allens", info)
}
}


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

override fun onBind(intent: Intent?): IBinder? {
//step4:将Messenger对象的Binder返回给客户端
return mMessenger.binder
}

服务端xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.allens.sample_service">


<!-- 自定义权限 -->
<permission
android:name="com.allens.lib_intent.CUSTOM_PERMISSION"
android:description="@string/permission_des"
android:protectionLevel="signature" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Sample">


<!-- messenger start -->
<!-- step5: 注册服务 设置 action-->
<!-- 这里添加了一个自定义的权限 -->
<service
android:name="com.allens.sample_service.messenger.MessengerService"
android:enabled="true"
android:exported="true"
android:permission="com.allens.lib_intent.CUSTOM_PERMISSION" />
<!-- messenger end -->

...
</application>

</manifest>

主要是注册了一个权限。下面来解释一下为什么要自定义权限

关于使用自定义权限的原因

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

官网中也有对其的介绍

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

简单解释。就是需要为服务添加一个特殊的权限。然后在使用此服务的进程中添加此权限即可

如何使用自定义权限

1
2
3
4
5
<!-- 自定义权限 -->
<permission
android:name="com.allens.lib_intent.CUSTOM_PERMISSION"
android:description="@string/permission_des"
android:protectionLevel="signature" />

首先肯定是声明权限,注意这里是服务端,也是Service所在的xml配置,在放上权限。

  • 权限的description描述必填项目.
  • 名称name也是必填项目,虽然说可以随意填写。不过还是按照规范去写。包名加上权限名称
  • protectionLevel权限等级。

添加自定义权限

就和普通权限一样在xml中声明即可,注意这里是在客户端声明,在放上客户端的xml

1
2
<!-- 如果服务定义了权限。那么这里需要申请权限 -->
<uses-permission android:name="com.allens.lib_intent.CUSTOM_PERMISSION"/>

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

关于绑定服务

在网上也发现不少人使用隐式意图的方式去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

说人话就是,为true的时候就是在其他进程了。

关于自定义参数

只需要注意一点。示例中使用的是两个不同的apk.所以需要将User这个自定义的对象完整的目录名称全部拷贝过去。

总结

Messenger作为进程间通信的方案,可以实现数据的传输,却不能做到方法的调用与执行.而Messenger本质上是对AIDL的又一次封装。使用上也更佳简单。在某些特性场景下。书写要比直接使用AIDL方便很多。

测试代码

想着还是放上代码比较好。万一有小伙伴需要测试还能看一下。

github