腾讯高级工程师面试问到,什么是Binder?

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的。

    最后

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长。而不成体系的学习效果低效漫长且无助。时间久了,付出巨大的时间成本和努力,没有看到应有的效果,会气馁是再正常不过的。

所以学习一定要找到最适合自己的方式,有一个思路方法,不然不止浪费时间,更可能把未来发展都一起耽误了。

如果你是卡在缺少学习资源的瓶颈上,那么刚刚好我能帮到你。以上知识笔记全部免费分享,如有需要获取知识笔记的朋友,可以点击下方二维码费领取。

猜你喜欢

转载自blog.csdn.net/m0_56255097/article/details/129560067