前言
这几天做公司内部的App
做的脑壳疼,Service
不仅要和当前的App
要关联还要在App
内部控制Service
的内部业务逻辑,Service
和Binder
的形式就够用了,奈何是个新手,总得上网上学习学习binder
怎么用,度娘一搜,可是为啥看见的都是把binder
和Service
代码放在一起的,用binder
的内部几行函数来返回Service
实例,眼看Activity
都能直接访问Service
的内部逻辑了,看的浑身难受,还是算了,还是自己试试binder
到底应该怎么用?
用到的工具
IDE | 语言 | 模拟器版本 |
---|---|---|
AS 3.5 | kotlin | Android 10 Api 29 |
Binder的官方解释
Base class for a remotable object, the core part of a lightweight remote procedure call mechanism defined by {@link IBinder}.
一个远程对象的基类,轻量级的核心部分由{@link IBinder}
定义的远程过程调用机制。
This class is an implementation of IBinder that provides standard local implementation of such an object.
这个类是IBinder
提供的一个实现此类对象的本地实现。
其余的注释很多,总结来说就是:
Binder
类继承IBinder
类。- 很多开发者会使用
aidl
来定制他们想要的接口。 Binder
是基本的IPC(进程间通信)
单元,不会影响应用的生命周期。应该在最高级的应用组件中使用,比如Service
,Activity
,ContentProvider
。- 必须记住当进程被终止时候的状态。
附上官方解释链接:Binder Class
。
正文
网上常看到的Binder
和Service
的写法这里就不贴出来了,只写下我认为比较好的调用写法。
1.Service内OnBind返回的Binder怎么写?
Service
在onBinde()
时候返回的Binder
:
override fun onBind(p0: Intent?): IBinder? {
/*
* attachInterface是可以将接口与Binder联系起来的一个简便方法
* 定义:attachInterface(@Nullable IInterface owner, @Nullable String descriptor)
*/
binder.attachInterface(object : MyBinderInterface {
override fun asBinder(): IBinder {
return binder
}
override fun start(s: String) {
log("catch string $s")
//do your work
}
}, binderInterfaceDescriptor)
return binder
}
2.与Binder attach的接口怎么写?
在Service
内部初始化Binder
时候使用了attachInterface()
这个简便方法,在Service
时候就可以使用queryLocalInterface()
来获得与Binder
相关的IInterface
。
IInterface
是Binder
使用interfaces
时候的基类,源码中是这样写的:
源码:IInterface.java
/**
* Base class for Binder interfaces. When defining a new interface,
* you must derive it from IInterface.
*/
public interface IInterface
{
/**
* Retrieve the Binder object associated with this interface.
* You must use this instead of a plain cast, so that proxy objects
* can return the correct result.
*/
public IBinder asBinder();
}
所以我们与Binder
相关联的Interface
也应当继承IInterface
这个类,测试的代码是这样的:
interface MyBinderInterface: IInterface {
fun start(s:String)
}
3.开启绑定的Activity
的ServiceConnection
怎么写?
如果你的Service
跑在主进程里面queryLocalInterface
方法返回的就与Binder
使用attachInterface
相联系的接口,就可以直接使用myBinderInterface
操作Service
了。
private val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
val myBinderInterface =
service.queryLocalInterface(MyService.binderInterfaceDescriptor) as MyBinderInterface
myBinderInterface?.start("hello world")
}
/**
* 注:同一个进程中不会调用此方法
*/
override fun onServiceDisconnected(name: ComponentName) {
}
}
可是如果在AndroidManifest.xml
中配置了这个Service
的android:process=":reomte"
属性,queryLocalInterface
这个方法返回的就会是null
,这时onServiceConnected
中service
就会变成BinderProxy
,这是个final class
并且继承IBinder
,该类中queryLocalInterface
方法如下:
源码:BinderProxy.java
/**
* Retrieve a local interface - always null in case of a proxy
*/
public IInterface queryLocalInterface(String descriptor) {
return null;
}
这就是为什么Service
位于独立进程的时候queryLocalInterface
会返回null
的原因。那么怎样才可以和Binder
进行交互呢?
在IBinder.java
中quecryLocalInterface
注释如下:
源码:IBinder.java
/**
* Attempt to retrieve a local implementation of an interface
* for this Binder object. If null is returned, you will need
* to instantiate a proxy class to marshall calls through
* the transact() method.
*/
public @Nullable IInterface quecryLocalInterface(@NonNull String descriptor);
所以我们要用transact()
方法与Binder
进行交互了。transact()
的定义如下:
源码:IBinder.java
/**
* Perform a generic operation with the object.
*
* @param code The action to perform. This should
* be a number between {@link #FIRST_CALL_TRANSACTION} and
* {@link #LAST_CALL_TRANSACTION}.
* @param data Marshalled data to send to the target. Must not be null.
* If you are not sending any data, you must create an empty Parcel
* that is given here.
* 这个参数用来发送数据的
* @param reply Marshalled data to be received from the target. May be
* null if you are not interested in the return value.
* 这个参数用来接收数据的
* @param flags Additional operation flags. Either 0 for a normal
* RPC, or {@link #FLAG_ONEWAY} for a one-way RPC.
* 单向发送的话就用FLAG_ONEWAY,即使该方法执行成功了reply这个参数也会为null。
* 双向的话用0就可以了
*
* @return Returns the result from {@link Binder#onTransact}. A successful call
* generally returns true; false generally means the transaction code was not
* understood.
*/
public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags)
throws RemoteException;
这里我新建了一个MyProxy
类并实现Parcelable
接口:
class MyProxy(var action: String?, var result: Boolean): Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readByte() != 0.toByte()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(action)
parcel.writeByte(if (result) 1 else 0)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<MyProxy> {
override fun createFromParcel(parcel: Parcel): MyProxy {
return MyProxy(parcel)
}
override fun newArray(size: Int): Array<MyProxy?> {
return arrayOfNulls(size)
}
}
}
所以在ServiceConnection
中的onServiceConnected()
就可以这么写:
override fun onServiceConnected(name: ComponentName, service: IBinder) {
val parcelData: Parcel = Parcel.obtain()
val parcelResult: Parcel = Parcel.obtain()
MyProxy("action", false).writeToParcel(parcelData, 0)
MyProxy("action", true).writeToParcel(parcelResult, 0)
//change as you logic
val code = 1001
//maybe throw RemoteException use try catch
val proxyResult = service.transact(code, parcelData, parcelResult, 0)
//you can get this parcelResult data with createFromParcel() like
//val result = MyProxy.createFromParcel(parcelResult)
parcelData.recycle()
parcelResult.recycle()
}
如果你想的够仔细的话,transact
方法工作在两个进程之间,不会有延时吗?在IBinder.java
的开头有一段注释:
源码:IBinder.java
/* <p>The key IBinder API is {@link #transact transact()} matched by
* {@link Binder#onTransact Binder.onTransact()}. These
* methods allow you to send a call to an IBinder object and receive a
* call coming in to a Binder object, respectively. This transaction API
* is synchronous, such that a call to {@link #transact transact()} does not
* return until the target has returned from
* {@link Binder#onTransact Binder.onTransact()}; this is the
* expected behavior when calling an object that exists in the local
* process, and the underlying inter-process communication (IPC) mechanism
* ensures that these same semantics apply when going across processes.
*/
很明确的说明transact()
是个同步方法,它的返回会等到Binder.onTransact()
方法返回,这里就可以体现出来Kotlin
的优势了,使用以下代码来替代上述的同步代码:
var result: MyProxy= MyProxy("example",false)
GlobalScope.launch {
//你可以将下列代码抽出为suspend修饰的函数
withContext(Dispatchers.IO) {
val parcelData: Parcel = Parcel.obtain()
val parcelResult: Parcel = Parcel.obtain()
result.writeToParcel(parcelData, 0)
MyProxy("action", true).writeToParcel(parcelResult, 0)
//change as you logic
val code = 1001
//maybe throw RemoteException use try catch
val proxyResult = service.transact(code, parcelData, parcelResult, 0)
//这个就是返回的结果
result = MyProxy.createFromParcel(parcelResult)
}
}
4.Binder怎么写?
当然在Service
运行在主进程的的时候,Binder
类空置即可:
class MyBinder : Binder() {
}
如果Service
运行在单独进程的时候要怎么办?在上面我们知道transact
方法会由Binder.onTransact()
方法处理并且返回,所以我们重写这个方法:
class MyBinder : Binder() {
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
//这里直接简单写了返回的类,你可以根据code做判断,code是transat()方法传入的
MyProxy("reply", true).writeToParcel(reply, 0)
//用 quecryLocalInterface 这个方法可以获取到接口,就能控制Service了
//return super.onTransact(code, data, reply, flags)
//base your logic
return true
}
}
5.上面的示例代码结果
Service
运行在主进程中结果就不贴了,只贴下运行在单独进程中的截图:
下图是绑定并开始服务的时候,并向Service
使用transact
方法传递参数,Activity
中的Log日志
Service
和Binder
在上述情况下的Log日志
多说一句:上面的截图看的很清楚Binder
的日志在Service
的进程日志中打了出来,是因为我在Service
中的OnBinde
时返回的Binder
是自己在Service
中实例化的。
总结
- 上述的写法在使用
Service
为单独进程时候非常好用,但很麻烦 - 相比自定义
AIDL
这个方法没啥难度 - 附上
Android Developers
中对Service
一篇详解:链接
这文章断断续续写了两周了,网上啥都没有,主要还是靠自己一点点查证、调试,如果有错误的话,麻烦一定要告诉啊,我还是个Android
萌新。谢谢读完我的文章!