前言
Android 进程间通信的方式之一就是使用Messenger进行,本文详细介绍如何使用Messenger的方式进行进程间通信
效果
Messenger是什么
在官网中是这么说的:
引用处理程序,其他人可以使用该处理程序向其发送消息。通过在一个进程中创建一个指向处理程序的Messenger并将该Messenger移交给另一个进程,可以实现跨进程的基于消息的通信。 注意:底下的实现只是Binder用于执行通信的简单包装器。这意味着从语义上讲您应该这样对待:此类不会影响流程生命周期管理(您必须使用一些更高级别的组件来告诉系统您的流程需要继续运行),如果流程消失,连接将断开由于任何原因等
说人话就是:使用Messenger通过Handler将Message发送到另一个进程,实现了进程间通信,底层依然是使用了Binder机制
如何使用
关于使用鸿洋在2015年的时候写了一个Android 基于Message的进程间通信 Messenger完全解析,在其文章中也介绍了相应的源码。本文说一下其使用方式。Messenger
可以理解。我给远方的你写信。然后你在回信给我。这样的方式去做到进程间通信。
这里使用两个应用作为不同的进程进行处理
客户端代码
首先看一下客户端详细的代码,主要还是服务的绑定。数据的发送还有数据的接受。
//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
<?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即可。
//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
<?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 属性设置的权限 适用于服务。如果未设置任何属性,则该服务不受权限保护。
简单解释。就是需要为服务添加一个特殊的权限。然后在使用此服务的进程中添加此权限即可
如何使用自定义权限
<!-- 自定义权限 -->
<permission
android:name="com.allens.lib_intent.CUSTOM_PERMISSION"
android:description="@string/permission_des"
android:protectionLevel="signature" />
复制代码
首先肯定是声明权限,注意这里是服务端,也是Service所在的xml配置,在放上权限。
- 权限的
description
描述必填项目. - 名称
name
也是必填项目,虽然说可以随意填写。不过还是按照规范去写。包名加上权限名称 - protectionLevel权限等级。
添加自定义权限
就和普通权限一样在xml中声明即可,注意这里是在客户端声明,在放上客户端的xml
<!-- 如果服务定义了权限。那么这里需要申请权限 -->
<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方便很多。
测试代码
想着还是放上代码比较好。万一有小伙伴需要测试还能看一下。