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>
简易说明:<queries>:TargetSDK > 30之后绑定非自身应用的 service 要加,里面写绑定服务的包名.
<permission>,<uses-permission>: 在服务端对 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 = 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。
三、记一下。。
- 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) 接收方执行时写入。