Binder 进程通信基础使用
一、服务端进程创建 Service,Service 中创建 Binder 子类对象并于 onBind 中返回。xml 定义。
- 创建 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 写值,客户端也读不到。
- 在服务端 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 权限及包名定义。
- 客户端 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 要求需要该权限,所以要加。
- 客户端绑定服务。
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.
- 调用 transact 完成跨进程通信。
private fun getServiceInfo() {Log.d(TAG, "activity getServiceInfo, service : $service")val data0 = Parcel.obtain()val reply0 = Parcel.obtain()val requestCode0 = 0try {//写入 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 = 1try {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。
三、记一下。。
- Binder 子类对象完成跨进程调用
- Binder.transact(p0,p1,p2,p3) 调用时回调 Binder.onTransact(p0,p1,p2,p3),p0 code 区分此次事件该做啥;p1 是传来的参数,可以读取另一边的数据;p2 是传给对方的数据,写入,p3 0时 p2有效,1时p2无效.
- Parcel token 验证。transact(p0,p1,p2,p3) 调用方调用前写入,onTransact(p0,p1,p2,p3) 接收方中验证。
- Parcel.exception 。transact(p0,p1,p2,p3) 调用方调用后读取,onTransact(p0,p1,p2,p3) 接收方执行时写入。