Android Service和它绑定的Binder应该怎么用?

1,997 阅读6分钟

前言

这几天做公司内部的App做的脑壳疼,Service不仅要和当前的App要关联还要在App内部控制Service的内部业务逻辑,ServiceBinder的形式就够用了,奈何是个新手,总得上网上学习学习binder怎么用,度娘一搜,可是为啥看见的都是把binderService代码放在一起的,用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提供的一个实现此类对象的本地实现。

其余的注释很多,总结来说就是:

  1. Binder类继承IBinder类。
  2. 很多开发者会使用aidl来定制他们想要的接口。
  3. Binder是基本的IPC(进程间通信)单元,不会影响应用的生命周期。应该在最高级的应用组件中使用,比如ServiceActivityContentProvider
  4. 必须记住当进程被终止时候的状态。
    附上官方解释链接:Binder Class

正文

网上常看到的BinderService的写法这里就不贴出来了,只写下我认为比较好的调用写法。

1.Service内OnBind返回的Binder怎么写?

ServiceonBinde()时候返回的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相关的IInterfaceIInterfaceBinder使用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.开启绑定的ActivityServiceConnection怎么写?

如果你的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中配置了这个Serviceandroid:process=":reomte"属性,queryLocalInterface这个方法返回的就会是null,这时onServiceConnectedservice就会变成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.javaquecryLocalInterface注释如下:

源码: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日志

下图是ServiceBinder在上述情况下的Log日志

多说一句:上面的截图看的很清楚Binder的日志在Service的进程日志中打了出来,是因为我在Service中的OnBinde时返回的Binder是自己在Service中实例化的。

总结

  1. 上述的写法在使用Service为单独进程时候非常好用,但很麻烦
  2. 相比自定义AIDL这个方法没啥难度
  3. 附上Android Developers中对Service一篇详解:链接

这文章断断续续写了两周了,网上啥都没有,主要还是靠自己一点点查证、调试,如果有错误的话,麻烦一定要告诉啊,我还是个Android萌新。谢谢读完我的文章!