Binder进程通信基础使用



Binder 进程通信基础使用

一、服务端进程创建 Service,Service 中创建 Binder 子类对象并于 onBind 中返回。xml 定义。

  1. 创建 Service,创建 Binder 子类对象并于 onBind 返回
class UserService : Service() {

    private companion object {
        const val TAG: String = "test_service_tag"
    }

    private val service = object : Binder() {
        override fun onTransact(p0: Int, p1: Parcel, p2: Parcel?, p3: Int): Boolean {
            p1.enforceInterface("UserService")
            when(p0) {
                0 -> {
                    // 读取客户端传递的 int 值
                    val readInt = p1.readInt()
                    Log.e(TAG, "service get int : $readInt")
                    // 写入无异常,表示这次调用没有出现问题
                    p2?.writeNoException()
                    p2?.writeInt(readInt * 2)
                    return true
                }
                1 -> {
                    // 读取客户端传递的 String 值
                    val readString = p1.readString()
                    Log.e(TAG, "service get String: $readString")
                    // 写入无异常,表示这次调用没有出现问题
                    p2?.writeNoException()
                    p2?.writeString("service version: 1.0.0")
                    return true
                }
                // 2的时候传的 Binder,拿到客户端 Binder 模拟跨进程回调,也就是此时随时可以双向通信
                2 -> {
                    // 读取客户端传递的 Binder 对象,双向通信,直接点说就是callback回调对象
                    val readStrongBinder = p1.readStrongBinder()
                    Log.e(TAG, "service get binder: $readStrongBinder execute method:")
                    val data = Parcel.obtain()
                    val reply = Parcel.obtain()
                    try {
                        //确认 token 令牌
                        data.writeInterfaceToken("UserService")
                        data.writeInt(111)
                        // 回调客户端,保存传来 Binder 后,随时可以回调,这里简易写一下就行
                        readStrongBinder.transact(0 ,data, reply, 0)
                        //这个方法必须要 reply 调了writeException 或 writeNoException,不然直接抛异常,看方法源码就看出来了
                        reply.readException()
                        Log.e(TAG, "service binder call back result: " + reply.readInt())
                    } finally {
                        data.recycle()
                        reply.recycle()
                    }
                    return true
                }
                else -> {
                    Log.d(TAG, "service unspecific value")
                }
            }
            return super.onTransact(p0, p1, p2, p3)
        }
    }

    override fun onBind(p0: Intent?): IBinder {
        Log.e(TAG, "service onBind Service ")
        return service
    }
}

简易说明:onTransact(p0: Int, p1: Parcel, p2: Parcel?, p3: Int)

在客户端调用传过去的 Binder.transact 方法时会回调此方法
p0: requestCode 请求码,可以用于区分不同的执行操作,类似 aidl 中不同的方法的区分
p1: data 数据,客户端传来的数据,客户端 write 写入,服务端 read 读取。
p2: reply 数据,当 p3 参数是 0时,在服务端进行 write,客户端调完方法直接 read 读取写入的值。
p3: flag 标记,0 表示这个方法有返回值,可对 reply 进行写入,然后客户端读取,1 的话表示单向,数据只从客户端到服务端,没有返回值,就算对 reply 写值,客户端也读不到。

  1. 在服务端 AndroidManifest.xml 里定义 service.
        <service android:name=".UserService" android:exported="true"
            android:enabled="true"
            android:permission="com.example.service">
            <intent-filter>
                <action android:name="com.example.service.UserService" />
            intent-filter>
        service>

简易说明:exported 外部调用开关,permission 权限限制,不设置不一定能绑定上

二、客户端绑定服务,通过返回的 Binder 执行逻辑,传递 Binder 完成跨进程回调。xml 权限及包名定义。

  1. 客户端 AndroidManifest.xml 中设定权限、包名.
    <permission android:name="com.example.service" />
    <uses-permission android:name="com.example.service" />
    <queries>
        <package android:name="com.example.service" />
    queries>

简易说明::TargetSDK > 30之后绑定非自身应用的 service 要加,里面写绑定服务的包名.
: 在服务端对 service 要求需要该权限,所以要加。

  1. 客户端绑定服务。
    private var service: IBinder? = null//这个代码是定义的 class成员变量,比较懒,就仍这儿了


        // 绑定服务不能只要 action,会抛异常,看了下实现,最少得加个包名
        val intent = Intent("com.example.service.UserService").also {
            it.component = ComponentName("com.example.service", "com.example.service.UserService")
        }
        Log.e(
            TAG,
            " activity bind service result : " + bindService(intent, object : ServiceConnection {
                override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
                    Log.e(TAG, "activity onServiceConnected, service : $service")
                    // 保存服务端传递的 Binder,就是这个
                    this@MainActivity.service = service
                }

                override fun onServiceDisconnected(name: ComponentName?) {
                    Log.e(TAG, "activity onServiceDisconnected, service : $service")
                    this@MainActivity.service = null
                }
                // 最后一个参数要传 Context.BIND_AUTO_CREATE,不然有问题,啥问题可以看 ComtextImpl.bindService 的源码,里面有 flags 参数校验
            }, Context.BIND_AUTO_CREATE)
        )

简易说明:就俩注意点
(1)intent 不止传 action,class 和 component 还得加上这两个其中的一个.
(2)binderService 最后一个参数 flag 需要传 Context.BIND_AUTO_CREATE.

  1. 调用 transact 完成跨进程通信。
    private fun getServiceInfo() {

        Log.d(TAG, "activity getServiceInfo, service : $service")
        val data0 = Parcel.obtain()
        val reply0 = Parcel.obtain()
        val requestCode0 = 0
        try {
            //写入 token 令牌,这里是因为客户端写了验证 token,服务端没写就可以不加
            data0.writeInterfaceToken("UserService")
            data0.writeInt(100)
            service?.transact(requestCode0, data0, reply0, 0)
            //这个方法必须要 reply 调了writeException 或 writeNoException,不然直接抛异常,看方法源码就看出来了
            reply0.readException()
            Log.e(
                TAG,
                "activity requestCode : $requestCode0, getServiceInfo :  " + reply0.readInt()
            )
        } finally {
            data0.recycle()
            reply0.recycle()
        }

        val data1 = Parcel.obtain()
        val reply1 = Parcel.obtain()
        val requestCode1 = 1
        try {
            data1.writeInterfaceToken("UserService")
            data1.writeString("what is service version?")
            service?.transact(requestCode1, data1, reply1, 0)
            reply1.readException()
            Log.e(
                TAG,
                "activity requestCode : $requestCode1, getServiceInfo :  " + reply1.readString()
            )
        } finally {
            data1.recycle()
            reply1.recycle()
        }

    }

问题点:
(1)token 验证。当服务端进行 data.enforceInterface(令牌值) token验证时,调用的客户端必须写入 token data.writeInterfaceToken(令牌值)
(2)默认就有异常。客户端调完 transact() 后,如果调了 reply.readException() 那客户端必须要调用 reply.writeNoException() 或者 reply.writeException()。
(3)data(p1) 和 reply(p2)。data 是客户端写值,服务端读取,相当于 aidl 方法入参,reply 服务端写入,客户端读取,相当于 aidl 方法返回值。
(4)回调双向通信。客户端服务端在已连接情况下双方随时可向对方通信,客户端调用的 transact() 中,data(p1) 写入的时候写入 Binder 对象,服务端拿到 Binder 对象后保存,这样双方都保存了对方的 Binder,也就是随时可以通信。直接点说就是常见的 registerCallback 方式,客户端拿着 service,服务端拿着 callback。

三、记一下。。

  1. Binder 子类对象完成跨进程调用
  2. Binder.transact(p0,p1,p2,p3) 调用时回调 Binder.onTransact(p0,p1,p2,p3),p0 code 区分此次事件该做啥;p1 是传来的参数,可以读取另一边的数据;p2 是传给对方的数据,写入,p3 0时 p2有效,1时p2无效.
  3. Parcel token 验证。transact(p0,p1,p2,p3) 调用方调用前写入,onTransact(p0,p1,p2,p3) 接收方中验证。
  4. Parcel.exception 。transact(p0,p1,p2,p3) 调用方调用后读取,onTransact(p0,p1,p2,p3) 接收方执行时写入。

你可能感兴趣的:(binder,前端,kotlin,android,android,studio)