笔记:Binder通信机制

TODO: 待修正

Binder简介

Binder是android系统中实现的一种高效的IPC机制,平常接触到的各种XxxManager,以及绑定Service时都在使用它进行跨进程操作。 它的实现基于OpenBinder项目,属于核心库。framework层的Binder通信用到的相关java类型都是对应C++类型的一个封装。

这里framework层就是android提供的java api层,类似jre中的java标准类库,也就是我们sdk中用到的各种java类型。

IPC和远程对象(Remotable Object)

在Java编程中,大多数情况下都是进程内的各个对象相互交互,另一些情况,java程序和其它进程进行的通信,就是IPC(inter-process communication)。

广义上看,像最常见的HTTP网络通信就是一种跨进程通信——客户端java程序和远程服务器上的服务进程通信。所以,其它进程可以是非java程序,如C++程序。抽象的看,不论哪两种语言的程序进程之间的沟通,都是一些方法调用——可以调用的方法就是所谓的协议,或接口API。

在Java中,一切皆对象,那么在和一个外部进程通信时,首先需要针对它的一组接口描述——也就是方法描述,此描述的类型定义显然就是通过接口实现了。这样,此接口所定义的通信协议就是本地java端对外部进程可执行操作的一个描述,实际上其它进程是否是java程序,是否有对象之说倒不重要,这里站在java程序的角度,虚拟地认为其它进程内部包含一个拥有这些操作的对象——远程对象——算是本地接口的实现类对象。远程对象的概念是从某个进程看其它进程的对象而言的。

java程序使用java接口对远程进程通信协议进行描述,其它像Swift这样的语言又有它们自己的通信协议的描述方式。

Binder系统

下面用Binder-SYS表示安卓系统中运行的Binder系统,Binder-IPC表示Binder实现IPC的机制。

Server和Client

Binder-SYS将通信的双方分为Server和Client,即C/S架构。 两端进程均使用一个接口IBinder的实例进行通信,它定义了方法IBinder.transact(),方法原型:

/**
 * 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.
 */
public boolean transact(int code, Parcel data, Parcel reply, int flags)
    throws RemoteException;
  • code 表示要执行的动作,类似Handler发送的Message的what。 code指示了当前远程操作的命令,IBinder定义了像INTERFACE_TRANSACTION、PING_TRANSACTION这样的几个通用命令。自己使用的命令的标识值需要在FIRST_CALL_TRANSACTION和LAST_CALL_TRANSACTION之间,仅仅是整数范围的一个约定,很好理解。
  • data和reply data和reply参数相当于普通java方法里的调用参数和返回值。Parcel类型是可以跨进程的数据。
  • flags 参数flags只有0和FLAG_ONEWAY两种,默认的跨进程操作是同步的,所以transact()方法的执行会阻塞,调用以同步的形式传递到远程的transact(),等待远端的transact()返回后继续执行——最好理解的方式就是把两端的transact()看作一个方法,Binder机制的目标也就是这样。指定FLAG_ONEWAY时,表示Client的transact()是单向调用,执行后立即返回,无需等待Server端transact()返回。

Server和Client利用IBinder跨进程通信的原理是:

Client调用其IBinder实例的transact()发起操作,Binder-SYS使得方法调用传递到Server端,以相同的参数执行Server端IBinder实例的transact()方法——这就是Binder-SYS实现的跨进程操作。

Binder和BinderProxy

Binder-SYS提供了BinderBinderProxy作为IBinder的子类。 每一个Binder对象都会唯一关联一个BinderProxy对象。 在跨进程通信时,提供服务的Server进程持有一个Binder对象,记为Server_Binder_obj。而其它Client进程持有关联的BinderProxy对象,记为Client_BinderProxy_obj。不同Client进程里的BinderProxy是不同java对象,而底层是同一个由Binder-SYS维护的C++对象。所以BinderProxy对象实际上是跨进程唯一的。 这里Proxy的含义就是Client进程得到的Server端Binder对象的一个本地引用。

最终Client对Server发起远程调用的过程就是: Client调用其BinderProxy实例的transact()发起操作,Binder-SYS使得方法调用传递到Server端,以相同的参数执行Server端Binder实例的transact()方法

这里注意下transact()在BinderProxy和Binder中的不同之处: BinderProxy.transact()方法是Client用来主动发出远程操作命令的,它接收code、data参数。BinderProxy是个final类,它的transact()方法只能被调用。 Binder.transact()是用来被动响应Client发出的远程调用的。 BinderProxy.transact()调用后,Server端Binder.transact()方法以同样的code、data参数被调用。 Binder类定义了onTransact()方法来供子类去响应命令,而它的transact()方法调用onTransact()的逻辑。onTransact()更好的表达了Binder的行为。

通信过程

有了以上核心概念,Binder-IPC的原理还是很简单明了的,一次跨进程远程通信的过程是:

  1. Client的代码调用BinderProxy.transact(),发起远程调用。参数flags为0时方法阻塞,等待Server端对应方法返回后继续执行。参数flags为FLAG_ONEWAY时立即返回。
  2. Client中的transact()调用传递式触发Server端Binder.transact()的调用,它又调用Binder.onTransact()。
  3. Server端,Binder.onTransact()中,子类的重写方法根据收到的code,执行对应业务逻辑,设置必要的返回数据到参数reply,然后Binder.transact()方法返回。
  4. Client端,BinderProxy.transact()从阻塞状态返回,调用者解析得到必要的返回参数,继续执行。

可以看到,Binder机制维持了Client进程的transact()的调用传递给Server端transact()以及相应的调用返回的传递过程。注意参数flags为FLAG_ONEWAY时指定通信为“单向”的,这样整个远程调用就成为了异步的——Client的transact()会很快返回,不需要等待Server端的方法调用完成(甚至是开始)。

以上就是Binder-IPC的主要原理。

示例项目:StudentManager

下面提供一个示例项目来说明如何使用Binder-IPC完成跨进程通信。 案例提供这样的功能: Server端程序实现了学生管理功能,提供了按学号查询学生年龄的操作。 然后Client通过Binder-IPC对Server端执行远程调用获得结果。

协议部分

从编程角度看,使用Binder-IPC实现Client和Server通信,有一些类型它们都会用到,这些类型仅仅和通信相关,而不涉及实际业务。这里称它们为通信协议相关类型。 一般都是提供服务的Server端定义这些类型,下面就依次实现它们。

通信接口

它定义了Server端可接收的方法调用,也就是Client可以发起的远程调用。 这里根据假设的需求,定义下面的接口:

package com.idlestars.binderdemo.serviceapi;
...

interface IStudentManager extends IInterface {
  String DESCRIPTOR = "com.tiro.binder.StudentManagerStub";
  int TRANSACTION_GET_AGE = (IBinder.FIRST_CALL_TRANSACTION + 0);

  int getAge(int studentId) throws RemoteException;;
}

通信接口是偏业务上的,它定义了方法getAge()用来根据学生id来获取其年龄。下面对它进行一些说明。

  • RemoteException 所有接口方法需要声明RemoteException异常,跨进程操作时目标服务进程总可能意外终止,或者服务类调用Parcel.writeException()来通知异常发生,这样Client端接口方法的调用就抛出RemoteException。
  • DESCRIPTOR 对当前接口的一个字符串标识,Binder类的attachInterface()和queryLocalInterface()方法会用到它。
  • TRANSACTION_GET_AGE 对应接口方法getAge()的命令code。
  • IInterface Binder-IPC要求的标准实现方式是,通信接口需要继承接口IInterface。它定义了asBinder()方法用来返回接口实例关联的IBinder对象。 这是因为一般正是Server端的Binder子类会实现通信接口,然后,Client是无法拿到Server端的IStudentManager对象的,所以,为Client定义一个本地的IStudentManager的代理实现类,该实现类使用BinderProxy调用Server端方法获取结果。 也就是通常两端实现接口IStudentManager的地方都密切关联了一个可以用来远程通信的IBinder对象,而asBinder()就是用来返回这个IBinder的。 经试验,这不是必须的。

Client端代理类

有了通信接口后,紧接着需要实现Server和Client使用IBinder进行数据交互的部分。也就是transact()的逻辑。 IStudentManager的实现是位于Server端的,Client端无法得到其对象。所以,Client端定义一个接口的代理实现类:

package com.idlestars.binderdemo.serviceapi;
...
public class StudentManager implements IStudentManager {
    IBinder mRemote;

    public StudentManager(IBinder remote) {
        mRemote = remote;
    }

    @Override
    public int getAge(int studentId) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();

        try {
            data.writeInterfaceToken(DESCRIPTOR);
            data.writeInt(studentId);
            mRemote.transact(TRANSACTION_GET_AGE, data, reply, 0);
            reply.readException();
            int age = reply.readInt();

            return age;
        } finally {
            data.recycle();
            reply.recycle();
        }
    }

    @Override
    public IBinder asBinder() {
        return mRemote;
    }
}

代理类StudentManager对getAge()的实现是通过mRemote来发送远程调用。 此处的mRemote是连接到Server时,Server端Binder关联的BinderProxy对象。 上面getAge()中写入和读取数据的顺序必须和Server端的Binder.onTransact()对应——主要就是code的取值和data、reply中数据的写入读取顺序。

StudentManager实现的IInterface.asBinder()返回它组合的mRemote。

Server端通信类

应用层提供可供其它进程访问的服务的方式就是通过Service组件,Service组件所在进程就是Server进程。 Client进程使用bindService()来和Server进程进行通信。

所以这里需要准备onBind()返回的Binder对象。同进程内的bindService()调用会返回给调用者Binder对象本身,而其它进程的调用最终得到的是Binder关联的BinderProxy对象。总之这个Binder子类就是服务绑定者后续和服务进行通信的渠道。

Server端使用Binder进行通信,也就是是响应transact()调用。 一般为了让返回的Binder对象可以被同进程内的绑定者当做IStudentManager去使用,这里定义的Binder子类就同时实现IStudentManager。

在Binder.onTransact()中将会实现和Client使用的代理类StudentManager.transact()对应的通信数据交换逻辑。 这部分逻辑是和IStudentManager定义的业务方法无关的。 为了方便将Binder.onTransact()的逻辑暴漏给Client——因为Client程序会最终引用这些类型,不论是源码方式还是库引用,处于安全或方便的目的,这里先定义一个抽象的Binder子类StudentManagerStub,它完成了和StudentManager交换数据,响应远程调用的逻辑。作为一个抽象类,它实现接口IStudentManager,但不去做任何实际处理,onBind()返回的类型会继承它,并实现IStudentManager的方法。

public abstract class StudentManagerStub extends Binder implements IStudentManager {

    public StudentManagerStub() {
        this.attachInterface(this, DESCRIPTOR);
    }

    @Override
    public IBinder asBinder() {
        return this;
    }

    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (code)
        {
            case INTERFACE_TRANSACTION:
            {
                reply.writeString(DESCRIPTOR);
                return true;
            }
            case TRANSACTION_GET_AGE:
            {
                data.enforceInterface(DESCRIPTOR);
                int stuId = data.readInt();
                reply.writeNoException();
                reply.writeInt(getAge(stuId));
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }

    public static IStudentManager asInterface(IBinder obj) {
        if (obj == null) {
            return null;
        }
        IStudentManager in =
                (IStudentManager)obj.queryLocalInterface(DESCRIPTOR);
        if (in != null) {
            return in;
        }

        return new StudentManager(obj);
    }
}

可以看到StudentManagerStub.onTransact()和StudentManager.transact()是对应的,后者的每一个code和前者都有一个case语句去处理。重要的是,有关data、reply参数的数据的写入和读取顺序也严格对应。

在onTransact()中调用了getAge()方法,它由最终的StudentManagerStub的子类去完成。getAge()是业务方法,和通信细节没有关系。

StudentManagerStub作为Binder子类,它实现的IInterface.asBinder()直接返回其对象本身——它既是IInterface的子类,又是Binder子类。

小结

以上的IStudentManager、StudentManager、StudentManagerStub是协议部分涉及到的类型。它们由Server端程序提供,Client端去引用。

StudentManagerStub的定义,使得Server端通信协议实现和通信接口的业务方法的实现得以分离。

有关协议类型要注意下面几点

  • 数据支持 IBinder.transact()可传递的数据只能是Parcel类型的,关于它可携带的数据类型参见其API。 Parcel对象可以携带的类型决定了通信所能支持的数据类型。
  • 通信规则 Client传递的调用参数data和Server端返回的reply数据,它们在写入和读取的顺序上必须是一致的。 写入和读取的顺序也是通信规则的一部分,所以两端的transact()、onTransact()逻辑严格对应。

业务部分

除了Binder-IPC需要的通信协议相关的类型,Server端需要提供Service来供其它进程绑定。并且它自己实现IStudentManager的业务方法。

Client中会使用代理类StudentManager来访问服务。

定义Service

Server提供一个Service组件来供自身程序、其它程序等访问:

public class RemoteService extends Service {
    StudentManagerService mBinder = new StudentManagerService();

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

为了突出重点,RemoteService的代码非常简单。就是返回一个StudentManagerService对象。 额外的工作就是在AndroidManifest.xml中注册它。

注意onBind()的机制是仅在第一个bindService()的请求时返回关联的IBinder对象,之后不再调用。 所以,一个Service在运行期间也只能通过一个IBinder对象和各个Client通信。

实现IStudentManager

onBind()返回的StudentManagerService继承自StudentManagerStub,它只需要完成实际的getAge()方法。 StudentManagerStub已经实现了通信协议的逻辑。

class StudentManagerService extends StudentManagerStub {
    @Override
    public int getAge(int studentId) throws RemoteException {
        return Math.abs(studentId % 30);
    }
}

Client访问Service

Client端访问目标Service的步骤如下:

  1. 调用bindService()。
  2. 将返回的IBinder使用StudentManagerStub.asInterface()转换为IStudentManager的实例。
  3. 像普通接口那样使用IStudentManager。

可以看到,其实对Client端而言,它只需要知道IStudentManager即可,而 StudentManagerStub和StudentManager直接由提供服务类的Server端定义即可。 在不同程序间进行Service的访问时,由谁来提供这些通信协议的类型是一个说一不二的事情。

使用bindService()来绑定服务是很基础的事情了。 如果是同进程,那么onServiceConnected()返回的是上面onBind()返回的StudentManagerService对象。 如果是不同进程,返回的是onBind()返回的StudentManagerService对象关联的BinderProxy对象。

所以bindService()得到的IBinder是和Service是否在同一个进程密切相关的。 上面StudentManagerStub.asInterface()方法正是用来将bindService()得到的IBinder转换为最终要用到的IStudentManager的实例:

public static IStudentManager asInterface(IBinder obj) {
    if (obj == null) {
        return null;
    }
    IStudentManager in =
            (IStudentManager)obj.queryLocalInterface(DESCRIPTOR);
    if (in != null) {
        return in;
    }

    return new StudentManager(obj);
}
  • 和Service同进程 参数obj就是StudentManagerService,因为使用的是IStudentManager.DESCRIPTOR,它的queryLocalInterface()返回StudentManagerService对象本身,成功强转为IStudentManager。
  • 和Service不同进程 参数obj是BinderProxy对象,上面if语句不会执行,这时,Client进程得到一个组合了此obj的StudentManager代理类对象,StudentManager负责远程调用Server端的方法。

这里使用queryLocalInterface()而不是instanceof这类方式执行类型检查,因为不能依赖到 StudentManagerService,包括对类型名称的假设。 而接口标识这样的方式可以满足上面的IStudentManager类型在Server进程内,或Client端两种转换要求。

实际上方法asInterface()放在协议类型StudentManagerStub、StudentManager中都可以。

Client访问Service的代码:

IStudentManager mStudentManager;

ServiceConnection connection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mStudentManager = StudentManagerStub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mStudentManager = null;
    }
};

private void bindToService() {
    Intent intent = new Intent(this, RemoteService.class);
    bindService(intent, connection, BIND_AUTO_CREATE);
}

bindService()的使用不过多介绍,intent可以指定为Action字符串。

执行远程调用

bindService()获得一个IStudentManager对象后,就可以用它来调用Server端的方法了。 实际上,Client端的代码调用getAge()方法和其它java方法没什么两样,唯一的区别就是需要捕获可能的RemoteException异常。

下面的方法onGetAgeClick()是按钮点击处理,它读取输入的stuId作为参数,执行getAge(),将返回结果显示。

public void onGetAgeClick(View view) {
    if (mStudentManager != null) {
        try {
            int stuId = Integer.valueOf(et_stu_id.getText().toString());
            int age = mStudentManager.getAge(stuId);

            tv_age.setText("Age: " + age);
        } catch (Exception e) {
            Log.d("Binder", "getAge Exception: " + e.getMessage());
        }
    }
}

Binder机制的不同使用方式

需要注意的一点是,以上关于Binder架构讨论的种种都是针对典型的IPC的情形的。 不过IBinder的使用在app层一般都是结合作为四大组件之一的Service进行的,也就是bindService()。Service可以是其它app的Service类,或者是当前程序本身的Service类,可以运行在相同进程或其它进程。

非Service方式的使用

framework层的ActivityManager、PackageManager等都是利用了Binder机制。但它们的服务类不是通过Service组件去提供。

以IActivityManager为例:

  • 接口IActivityManager定义了有关管理系统中所有Activity的API。
  • ActivityManagerService继承自Binder,并实现了IActivityManager,相当于一个Server端的服务类。
  • SystemServer进程启动后,它实例化ActivityManagerService对象,然后注册到ServiceManager中。
  • ActivityManager通过ServiceManager获得ActivityManagerService的BinderProxy,它也实现了IActivityManager, 相当于一个代理类。

可见,像系统服务它们位于特殊的进程内,不是以Service组件的方式运行。 其注册和获取的方式也不是bindService()这样的,而是依赖ServiceManager。

这里简单的举例Binder的非Service使用形式,说明一点,Binder机制提供“更底层”的API,bindService()方式访问Service组件时用到Binder架构,在app开发中这是主要形式。但可以脱离Service来使用Binder进行IPC通信。

SystemServer作为系统进程它的生命周期必然会更稳定,而Service对进程的“重要性”的影响显然还不如Activity。 在使用Binder来进行IPC时,总要关心目标进程的存活状态,后面会再涉及到这点。

Service方式

此时分两种情况:目标Service在同一进程和不同进程。绑定其它程序的Service肯定是不同进程的情况了。而当前项目里的Service也可以通过在AndroidManifest.xml中声明不同的android:process来使其运行在另外进程中。

同进程内

这种是最简单的bindService()的使用场景。 此时,Service.onBind()返回的就是Binder的子类。它是同进程的对象,而且类型已知。 Service返回的Binder类是可以完全访问Service的功能的,为了使用此Binder和Service通信,有以下方式:

  • Binder子类提供一些public方法供调用,这些方法操作Service。
  • Binder子类返回对应的Service对象供使用。
  • Binder子类返回寄宿在Service中的一些公开成员对象供访问,一般就是此Service提供的不同接口的实现类对象。

此时不需要标准的Client端,bindService()的调用者得到Binder后就可以访问Service提供的各项功能了。

绑定同程序不同进程中的Service

此时从另一个进程获取到的是BinderProxy对象,属于标准的IPC方式。 bindService()的代码最终获得的是代理类,而不是Binder对象本身。 此时的使用方式和上面StudentManager示例完全一样。 因为是同一个程序,涉及的相关类型无需显示引用。

绑定其它程序的Service

其它程序必然运行在其它进程。这时,通信协议相关的IStudentManager、StudentManagerStub和 StudentManager这些类型都是目标程序提供的。 自己的项目中需要引用这些类型,之后的使用方式和上面StudentManager示例一样。

向Server端注册回调

除了可以调用Server端的业务接口方法外,还可以向Server注册回调接口,这样就相当于实现了跨进程的观察者模式。或者是类似广播监听器那样的效果。

文章底部“案例代码”中的RemoteService示例演示了如何注册回调接口道其它进程。 示例中的通信接口如下:

interface IRemoteServiceCallback {
    void valueChanged(int value);
}

interface IRemoteService {
  void registerCallback(IRemoteServiceCallback cb);
  void unregisterCallback(IRemoteServiceCallback cb);
}

代码演示了Client使用IRemoteService将自己实现的IRemoteServiceCallback对象注册到Server。 之后Server端就可以调用valueChanged()方法通知Client。

实际上IRemoteServiceCallback对象肯定是无法跨进程传递的,valueChanged()的调用又是一次IPC过程, 此时Client作为IRemoteServiceCallback的实现方,而Server作为IRemoteServiceCallback方法的调用方,valueChanged()的远程调用的过程中,原Server是Client,原Client是Server。

可见Binder-IPC进行的通信中,Server和Client是相对概念。对应一个通信接口,谁发起远程调用,谁就是Client。

registerCallback()传递给Server的是当前Client实现IRemoteServiceCallback所定义的Binder子类对象的BinderProxy。考虑许多Client向Server注册回调接口的情形,最终的,Server端维护了这些Client提供的 IRemoteServiceCallback实例关联的IBinder对象,Server通过这些IBinder对象通知Client,达到调用其valueChanged()方法。

android.os.RemoteCallbackList正是用来管理远程回调接口的,它就是以这些接口实例关联的IBinder作为标识。所以可以用来跨进程唯一标识不同Clients注册的IRemoteServiceCallback对象。

监听Server端IBinder的死亡

当Client端持有的IBinder代表着的其它进程中的服务类时,就需要特别关注其是否仍然有效——一般就是其所在进程是否还在运行决定。有下面三个方法用来完成对远程对象的检查:

  • The transact() method will throw a RemoteException exception if you try to call it on an IBinder whose process no longer exists.
  • The pingBinder() method can be called, and will return false if the remote process no longer exists.
  • The linkToDeath() method can be used to register a IBinder.DeathRecipient with the IBinder, which will be called when its containing process goes away.

使用bindService()时都知道,目标Service所在进程终止时,ServiceConnection.onServiceConnected()方法就会执行(总会在main线程中执行)。

因为使用IBinder的方式不限于Service中,所以在IBinder类的级别上也可以去主动监听服务类对象的死亡。 前面在IBinder的说明中提到它提供了几个方法用来检查目标Binder是否存活,而主动获取其死亡通知的方式 就是注册IBinder.DeathRecipient到代理类持有的IBinder。

Client在获取到IBinder后:

binder.linkToDeath(new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        // 做一些清理,或者是尝试重新激活目标服务进程
    }
}, 0);

linkToDeath()会在binder线程池中的线程中执行,第二个参数flags文档中竟然没有说明?

线程问题

当Server和Client处在同一个进程或不同进程中时,transact()的执行所在的线程会是不同的表现。

不同进程

  • Client端 Client端是发起transact()的地方,其方法调用者所在线程就是transact()的执行线程。 前面的知识已经明确知道了transact()可以是同步或异步的,如果是同步的形式,而Server端的transact()很可能会耗时,那么Client端的transact()就不应该在UI线程中执行。
  • Server端 服务类对象所在的Server进程会维护一个binder专用的线程池来处理来自Client的请求。 所以Server端的transact()的执行总是异步的,因此还需要注意线程同步的问题。

不论Client的transact()的参数flags指定为0或0和FLAG_ONEWAY,Server端transact()总是在某个线程池的线程中执行。flags仅仅影响Client端transact()是否立即返回。

同一个进程

一般若自己的app内部bindService()来访问进程内的Service时,就属于同进程的C/S通信。 此时Client获取到的IBinder就是服务类对象本身,所以,最终Client和Server端的transact()的执行是同一个方法的调用!!

Binder的其它形式

手动去实现Binder-IPC用到的各种类型非常繁琐,而且如果对Binder的使用不需要非常多的控制时,可以利用下面的AIDL和Messenger方式完成IPC,它们都是对Binder使用的一个简化。

AIDL

AIDL是方便定义通信协议的一个工具,注意它的工作过程是属于项目的构建的一部分。 使用aidl时,需要用它来定义通信的接口,声明用到的自定义的数据格式。 之后工具会自动生成java文件,作为最终编译的代码的一部分。

其生成的类型包括:

  • 通信接口 类似上面的IStudentManager,一个继承IInterface的接口。
  • Server端通信基类 如上面的StudentManagerStub,它实现onTransact()的逻辑,用来响应不同命令,会调用业务接口IStudentManager的对应方法得到执行结果。 作为协议的一部分,它不去实现接口的业务方法。 onTransact()实现了Server端通信的细节。

最终的服务类继承“Server端通信基类”,并实现通信接口里业务方法的逻辑。

  • 代理类 如上面的StudentManager,它封装一个BinderProxy与远程服务类对象进行通信。

作为一个工具,它的功能就是简化开发过程。和上面实现Server端、Client端相关类型的最终效果是一样的。 更多细节参见api文档aidl的介绍,文章底部资料部分给出了具体地址。

Messenger

在创建一个可绑定的Service时,有三种方式可以提供onBind()返回的IBinder对象:

  • 返回Binder子类 这种方式只有同进程时才可行,否则会返回给其它进程Binder的代理BinderProxy对象。
  • AIDL 特点是跨进程,且Server端transact()的执行是由Binder线程池里的线程调用的,需要进行并发控制。
  • Messenger 这种方式下,通信的命令被封装为Message对象,而且Server端使用一个Handler来依次处理收到的命令,不存在并发。

使用Messenger的步骤如下:

  • The service implements a Handler that receives a callback for each call from a client.
  • The Handler is used to create a Messenger object (which is a reference to the Handler).
  • The Messenger creates an IBinder that the service returns to clients from onBind().
  • Clients use the IBinder to instantiate the Messenger (that references the service's Handler), which the client uses to send Message objects to the service.
  • The service receives each Message in its Handler—specifically, in the handleMessage() method.

这种方式下,Client和Server之间没有接口方法,而是由Client发送Message表示的命令被Server端处理。 可见Messenger简化了transact()的过程为handler发送消息的方式,Messenger的底层依然是标准的binder IPC方式实现的,Messenger的作用就类似上面的代理类,不过它实现的是通用的IMessenger接口。

如果需要Server端向Client发送Message,在Client中定义好Handler和Messenger,然后把Messenger设置给发送到Server的Message对象的replyTo字段。

IPC权限

如果Server端需要对访问它的Client做权限检查,那么可以在AndroidManifest.xml中首先定义好权限。 方法android.content.ContextWrapper#checkCallingOrSelfPermission()可以用来检查调用者是否拥有指定的权限。

如果使用的是Service,那么onBind()处可以控制是否返回IBinder给某个Client,但是onBind()只会被执行一次,若返回null的话,后续的onServiceConnected()也不会执行。这里没有试验这种默认行为是否影响其它进程对服务的绑定,但终究不灵活。

在onTransact()方法中,Binder提供了getCallingPid()和getCallingUid()来获得当前transact()执行时调用者的进程和用户等信息。在这里,进行权限检查,然后选择性返回false的话会更好些,也就是说,针对一次具体的transact()操作可以拒绝响应,但不影响其它的transact()调用。

补充

跨进程递归调用

The Binder system also supports recursion across processes. For example if process A performs a transaction to process B, and process B while handling that transaction calls transact() on an IBinder that is implemented in A, then the thread in A that is currently waiting for the original transaction to finish will take care of calling Binder.onTransact() on the object being called by B. This ensures that the recursion semantics when calling remote binder object are the same as when calling local objects.

Binder对象的生命周期

Binder class is just a basic IPC primitive; it has no impact on an application's lifecycle, and is valid only as long as the process that created it continues to run. To use this correctly, you must be doing so within the context of a top-level application component (a Service, Activity, or ContentProvider) that lets the system know your process should remain running.

You must keep in mind the situations in which your process could go away, and thus require that you later re-create a new Binder and re-attach it when the process starts again. For example, if you are using this within an Activity, your activity's process may be killed any time the activity is not started; if the activity is later re-created you will need to create a new Binder and hand it back to the correct place again; you need to be aware that your process may be started for another reason (for example to receive a broadcast) that will not involve re-creating the activity and thus run its code to create a new Binder.

案例代码

文件路径: src/main/java/com/example/android/apis/app/RemoteService.java

  • ActivityManager相关源码

资料

  • bound-services /docs/guide/components/bound-services.html
  • aidl /docs/guide/components/aidl.html

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java 技术分享

SpringMVC(二)

1653
来自专栏互联网大杂烩

Spring MVC框架

前端控制器是DispatcherServlet;应用控制器其实拆为处理器映射器(Handler Mapping)进行处理器管理和视图解析器(View Resol...

742
来自专栏小筱月

springboot 整合 MongoDB 实现登录注册,html 页面获取后台参数的方法

Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而...

3180
来自专栏流媒体

cmake用法

示例源码 在 linux 平台下使用 CMake 生成 Makefile 并编译的流程如下:

1353
来自专栏学海无涯

Java Web之Spring Boot

我一直在尝试一个人写demo(Android和iOS)时,如何模拟服务器端返回的 JSON 数据,总的来说,我试过以下几种: 纯Servlet开发,这种方式配合...

2874
来自专栏技术墨客

Spring核心——Profile管理环境 原

在介绍Spring核心模块为运行环境管理提供的功能之前,咱们先得解释清楚“运行环境”是什么。

863
来自专栏玩转JavaEE

使用Spring Boot开发Web项目

按:最近公众号文章主要是整理一些老文章,以个人CSDN上的博客为主,也会穿插一些新的技术点。 ---- 前面两篇博客中我们简单介绍了Spring Boot项目的...

3105
来自专栏技术墨客

Spring-boot特性(2) 原

在使用Spring-boot时,永远要记住它仅仅是Spring Framework的延伸(或者说整合),其底层还是基于Spring Framework(core...

2502
来自专栏Java 源码分析

SpringBoot 笔记(九):分布式

1693
来自专栏Kevin-ZhangCG

[ SSH框架 ] Spring框架学习之一

2506

扫码关注云+社区

领取腾讯云代金券