preface

One of the ways to communicate between Android processes is to use Messenger. This article describes how to use Messenger to communicate between processes

The effect

What is the Messenger

Here’s what it says on the website:

Reference handler that others can use to send messages to. Message-based communication across processes is possible by creating a Messenger in one process that points to the handler and handing that Messenger over to another process. Note: The following implementation is a simple wrapper that Binder uses to perform communication. This means that semantically you should treat this as something that doesn’t affect process lifecycle management (you have to use some higher-level component to tell the system that your process needs to continue running), if the process disappears, the connection will be disconnected for any reason, etc

Using the Binder mechanism, you can use Messenger to send messages to other processes using handlers

How to use

On the use of Hongyang in 2015 wrote an Android Message based interprocess communication Messenger fully resolved, in its article also introduced the corresponding source code. This article describes how it is used. Messenger is understandable. I write to you from afar. And then you wrote back to me. This is the way to do inter-process communication.

Here the two applications are treated as separate processes

Client code

Let’s take a look at the details of the client code, mainly the binding of the service. The sending of data and the receiving of data.

//step1: create a class that inherits 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("Received reply :$info")}10002 -> {
                val info = (msg.obj as Bundle).get("index") as Int
                activity.log("Received reply :$info")}10003 -> {
                val info = (msg.obj as Bundle).get("user") as User
                activity.log("Received reply :$info")}}}}//step2: create a messenger
private val clientMessenger: Messenger = Messenger(ClientHandler(this))

//step3: create a connection interface
private var serverMessenger: Messenger? = null

private val connect = object : ServiceConnection {
    override fun onServiceConnected(name: ComponentName? , service: IBinder?) {
        // Get the Messenger from the server
        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)

    // Run sample-service before using this use case
    binding.linear.addView(MaterialButton(this).apply {
        text = "Bind service"
        setOnClickListener {
            // Step4: Use bindService to connect
            val intent = Intent()
            / / parameter 1: appId
            // Parameter 2: the strength of the corresponding Service
            intent.component = ComponentName(PKG, CLS)
            // Parameter 1: the current intent intent.
            // Parameter 2: connection listener
            // Parameter 3: type BIND_AUTO_CREATE: As long as the binding exists, the service is automatically created
            val bindService = context.bindService(intent, connect, BIND_AUTO_CREATE)
            if(! bindService) { log("Service binding failed")
            }
        }
    })



    binding.linear.addView(MaterialButton(this).apply {
        text = "Unbind"
        setOnClickListener {
            context.unbindService(connect)
        }
    })

    binding.linear.addView(MaterialButton(this).apply {
        text = "Send String"
        setOnClickListener {
            / / create the Message
            val message: Message = Message.obtain(null.10001)
            val bundle = Bundle()
            bundle.putString("data"."Hello, hello")
            message.obj = bundle
            // Send the client Messenger to the server
            message.replyTo = clientMessenger
            // Use the send method to send dataserverMessenger? .send(message) } }) binding.linear.addView(MaterialButton(this).apply {
        text = "Send Int"
        setOnClickListener {
            / / create the Message
            val message: Message = Message.obtain(null.10002)
            val bundle = Bundle()
            bundle.putInt("index".22)
            message.obj = bundle
            // Send the client Messenger to the server
            message.replyTo = clientMessenger
            // Use the send method to send dataserverMessenger? .send(message) } }) binding.linear.addView(MaterialButton(this).apply {
        text = "Send custom types"
        setOnClickListener {
            / / create the Message
            val message: Message = Message.obtain(null.10003)
            val bundle = Bundle()
            bundle.putSerializable("user",User("River ocean".18))
            message.obj = bundle
            // Send the client Messenger to the server
            message.replyTo = clientMessenger
            // Use the send method to send dataserverMessenger? .send(message) } })// Add TextView to display log information
    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")}}Copy the code

The client XML

If you look at the XML, there are two more things. One is queries and the other is permission


      
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.allens.sample_messenger">


    <! -- Android 11 needs to specify the process to be accessed -->
    <queries>
        <package android:name="com.allens.sample_service" />
    </queries>

    <! If the service defines permissions. So you need to apply for permission here -->
    <uses-permission android:name="com.allens.lib_intent.CUSTOM_PERMISSION"/>

    <application
     .
    </application>

</manifest>
Copy the code

Why usequeriesField specifies the process package name to access

ActivityManager: Unable to start service not found Unable to access other processes. The reason is that the management package visibility is added in Android11, so you need to add the queries field to the process to declare the package name of the process to be accessed

Why custom permissions

This is detailed in the server configuration

Server code

Inherit Service.


/ / step1: inherit the Service
class MessengerService : Service(a){

//step2: Create a class that inherits 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("Server: received String data :$info")

                // Reply to messenger
                val messenger = msg.replyTo as Messenger
                val message: Message = Message.obtain(null, msg.what)
                val bundle = Bundle()
                bundle.putString("data"."I am the server, I received your message {$info}")
                message.obj = bundle
                messenger.send(message)
            }
            10002 -> {
                val index = acceptBundle.get("index") as Int
                log("Server: received data of type Int :$index")

                // Reply to messenger
                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("Server: received data of custom type :$user")

                // Reply to messenger
                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: create a messenger
private val mMessenger = Messenger(MessengerHandler())

override fun onBind(intent: Intent?): IBinder? {
    //step4: return the Binder of the Messenger object to the client
    return mMessenger.binder
}
Copy the code

The server-side XML


      
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.allens.sample_service">


  <! -- Custom permissions -->
  <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: Register service set action-->
      <! -- Add a custom permission -->
      <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>
Copy the code

Basically, a permission is registered. Let’s explain why you want to customize permissions

Reasons for using custom permissions

If you set android:exported=”true”, 👇 exported service does not require permission

It is also introduced on the official website

The permission name that an entity must have to start or bind to a service. If the calling party startService(), bindService(), or stopService() is not granted this permission, the method will not work properly and the intent object will not be passed to the service. If this property is not set, the permissions set by the element’s Permission property apply to the service. If no properties are set, the service is not protected by permissions.

Simple explanation. You need to add a special permission for the service. Then add this permission to the process that uses the service

How do I use custom permissions

<! -- Custom permissions -->
<permission
    android:name="com.allens.lib_intent.CUSTOM_PERMISSION"
    android:description="@string/permission_des"
    android:protectionLevel="signature" />
Copy the code

The first step is to declare the permissions. Note that this is the XML configuration on the server, where the Service is located.

  • The powers of thedescriptionDescription Mandatory.
  • The name of thenameIt is also a mandatory item, although it is optional to fill in. But let’s do it according to the spec. Package name plus permission name
  • ProtectionLevel Permission level.

Add a custom permission

Just declare it in XML, just like normal permissions, but in this case declare it on the client side, and put the XML on the client side

<! If the service defines permissions. So you need to apply for permission here -->
<uses-permission android:name="com.allens.lib_intent.CUSTOM_PERMISSION"/>
Copy the code

It can also be added dynamically using the addPermission method

About Binding services

It is also found on the Internet that many people use the way of implicit intention to bindService, which is clearly stated on the official website

Note: If you bind to a Service using intents, use explicit intents to secure your application. Using implicit intents to start services has security implications because you can’t be sure which service will respond to the intent, and users can’t see which service will start. Starting with Android 5.0 (API level 21), if you call bindService() with implicit intents, the system will throw an exception.

Instead of adding actions and categories, all examples specify which service to bind to

aboutprocessattribute

Since the example uses two different APKs, you no longer need to set different processes. If you are in the same application, you need to set android: Process =”: process name “to servcie.

Specified as exported, the parameter is described in detail on the official website

Android: Exported =”true”. The exported property specifies whether components of other applications can call or interact with the service. The exported property specifies whether components of other applications can call or interact with the service

The default value depends on whether the service contains an intent filter. The absence of any filters means that it can only be called by specifying its exact class name. This means that the service is only available for internal use by applications (because others will not know the class name). Therefore, in this case, the default value is “false”. On the other hand, the presence of at least one filter means that the service is intended for external use, so the default value is “true”

In human language, when true is in another process.

About custom parameters

Just be careful. The example uses two different APKsUserThe entire directory name of this custom object is copied.

conclusion

Messenger, as an interprocess communication scheme, can realize data transmission, but can not do method invocation and execution. Messenger is essentially another iteration of AIDL. It’s also easier to use. In some feature scenarios. Writing is much more convenient than using AIDL.

The test code

I thought I’d put the code in. In case someone needs a test.

github