使用Messenger进程间通信

2,561 阅读5分钟

前言

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方便很多。

测试代码

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

github