Android service介绍——跨进程之Messenger方式介绍和源码初步梳理(2)

问题背景 前一篇文章介绍了service的基本使用(参考 https://blog.51cto.com/baorant24/6091264 ),包括使用startService和bindService两种启动方式,其中介绍通过bindService()方法去绑定启动一个进程内部的service。如果要跨进程(IPC)的方式去绑定服务,可以有几种方式呢?一般来说,跨进程绑定服务有Messenager和AIDL两种,本文将进一步介绍通过Messenager跨进程绑定service的使用。 问题分析 (1)new一个service,作为我们要跨进程启动的远程服务,代码如下:

import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.*
import android.widget.Toast
import java.lang.ref.WeakReference

class MessengerService : Service() {
    // 构造Messenger对象
    val mMessenger: Messenger = Messenger(IncomingHandler(this, Looper.myLooper()!!))

    // 绑定服务后返回mMessenger对应的binder对象
    override fun onBind(intent: Intent): IBinder? {
        Toast.makeText(applicationContext, "binding", Toast.LENGTH_SHORT)
            .show()
        return mMessenger.binder
    }

    companion object {
        const val MSG_SAY_HELLO = 1

        class IncomingHandler constructor(context: Context, looper: Looper): Handler(looper) {
            var weakReference = WeakReference(context)

            override fun handleMessage(msg: Message) {
                when (msg.what) {
                    MSG_SAY_HELLO -> Toast.makeText(
                        weakReference.get(), "hello!",
                        Toast.LENGTH_SHORT
                    ).show()
                    else -> super.handleMessage(msg)
                }
            }
        }
    }
}

(2)manifest配置文件中,对service进行配置,配置远程服务,代码如下:

<service
        android:name="composer.service.MessengerService"
        android:process=":remote"
        android:exported="true"
        android:enabled="true" />

(3)新建一个activity,去绑定刚才的远程服务,代码如下:

import android.app.Activity
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.*
import android.view.LayoutInflater
import android.view.View
import composer.databinding.ActivityTestMessengerBinding
import composer.service.MessengerService
import kotlinx.android.synthetic.main.activity_test_messenger.*

class TestMessengerActivity : Activity() {
    var mService: Messenger? = null

    var mBound = false

    lateinit var binding: ActivityTestMessengerBinding

    private val mConnection: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            mService = Messenger(service)
            mBound = true
        }

        override fun onServiceDisconnected(className: ComponentName) {
            mService = null
            mBound = false
        }
    }

    fun sayHello(v: View?) {
        if (!mBound) return
        val msg: Message = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0)
        try {
            mService!!.send(msg)
        } catch (e: RemoteException) {
            e.printStackTrace()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityTestMessengerBinding.inflate(LayoutInflater.from(this))
        setContentView(binding.root)

        bindMessengerService.setOnClickListener {
            bindService(Intent(this, MessengerService::class.java), mConnection, Context.BIND_AUTO_CREATE)
        }
    }

    override fun onStop() {
        super.onStop()
        if (mBound) {
            unbindService(mConnection)
            mBound = false
        }
    }
}

(4)activity对应的layout布局文件,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".view.TestStartServiceActivity">

  <Button
      android:id="@+id/bindMessengerService"
      android:text="bind MessengerService"
      app:layout_constraintTop_toTopOf="parent"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"/>

  <Button
      android:text="say hello"
      app:layout_constraintTop_toBottomOf="@id/bindMessengerService"
      android:layout_width="match_parent"
      android:onClick="sayHello"
      android:layout_height="wrap_content"/>
</androidx.constraintlayout.widget.ConstraintLayout>

运行项目代码,先点击bindService按钮不,再点击下面的sayHello按钮,效果如下: image.png 问题解决 上面可以看到,通过使用Messenger,我们可以跨进程绑定远程service,我们可以进一步看看相关代码流程。 (1)远程服务中构造Messenger对象

// 构造Messenger对象
    val mMessenger: Messenger = Messenger(IncomingHandler(this, Looper.myLooper()!!))

(2)查看Messenger对应的构造方法 android.os.Messenger#Messenger(android.os.Handler)

public final class Messenger implements Parcelable {
    private final IMessenger mTarget;

    public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }

(3)android.os.Handler#getIMessenger

@UnsupportedAppUsage
    final IMessenger getIMessenger() {
        synchronized (mQueue) {
            if (mMessenger != null) {
                return mMessenger;
            }
            mMessenger = new MessengerImpl();
            return mMessenger;
        }
    }

(4)android.os.Handler.MessengerImpl

private final class MessengerImpl extends IMessenger.Stub {
        public void send(Message msg) {
            msg.sendingUid = Binder.getCallingUid();
            Handler.this.sendMessage(msg);
        }
    }

(5)service被绑定成功后

// 绑定服务后返回mMessenger对应的binder对象
    override fun onBind(intent: Intent): IBinder? {
        Toast.makeText(applicationContext, "binding", Toast.LENGTH_SHORT)
            .show()
        return mMessenger.binder
    }

(6)android.os.Messenger#getBinder

public IBinder getBinder() {
        return mTarget.asBinder();
    }

可以看到,Messenger的底层还是走的AIDL这一套逻辑。 问题总结 前一篇文章介绍了service的基本使用(参考 https://blog.51cto.com/baorant24/6091264 )其中介绍通过bindService()方法去绑定启动一个进程内部的service。如果要跨进程(IPC)的方式去绑定服务,可以有几种方式呢?一般来说,跨进程绑定服务有Messenager和AIDL两种,本文进一步介绍了通过Messenager跨进程绑定service的使用。后面会继续介绍AIDL方式的使用。有兴趣的同学可以进一步深入研究。