“我正在参加「掘金·启航计划」”
接着上篇文章重学Binder进程间通信-原理篇,前面我们介绍过,Binder 是基于 C/S 架构的,由 Client、Server、ServiceManager、Binder 驱动组成。其中 Client、Server、ServiceManager运行在用户空间,Binder驱动运行在内核空间。其中ServiceManager 和 Binder 驱动由系统提供,而 Client、Server 由应用程序实现。
Client、Server 通过调用binder驱动为应用层提供了open()
,mmap()
,poll()
,ioctl()
方法来访问设备问价 /dev/binder
.open()
负责打开驱动,mmap()
负责对binder
做内核空间向用户空间的地址映射,ioctl()
负责binder协议的通信。Binder底层再调用 binder_open()、binder_mmap()、binder_ioctl()来实现进程间通信。
Binder进程间通信的过程
Binder跨进程通信有两种方式,如上图
- ServiceManager的getService获取Manager进行Binder通信
- Service组件的binderService方式进行通信
利用ServiceManager的getService获取Manager进行Binder通信
ServiceManager相当于一个DNS服务器,我们从DNS服务器中获取对应的服务,也就是获取到我们需要的Manager,本质上Manager还是IBinder。比如我们想获取电池电量,此时我们就可以用 getSystemService()来获取电池电量,代码如下:
//获取系统注册服务的跨进程通信方式
BatteryManager manager = (BatteryManager) getSystemService(BATTERY_SERVICE);
manager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
manager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE);
manager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CURRENT_NOW);
利用Service组件的binderService方式进行通信
我们知道,在Web端访问的一个流程通常如下:
没错,Binder的机制和此非常相似,它也是一套基于CS的架构。如web的访问过程,Binder也有四个重要的角色:Binder Server、Binder Client、Service Manager、Binder驱动。其和web端的对应关系如下:
至此,我们通过Web访问请求大致能总结出 Binder 通信过程:
- 进程Server 通过 Binder驱动 向 ServiceManager 中注册 Binder(Server 中的 Binder 实体),表明可以对外提供服务。驱动为这个 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager,ServiceManger 将其填入查找表。
- Client 通过唯一标识 在 Binder驱动 的帮助下 从ServiceManager 中获取到对应的 Binder实体的引用,通过这个引用就能实现和 Server 进程的通信。
Binder代码浅析
我们在实际开发中,实现进程间通信最多的就是 AIDL。当我们定义好 AIDL 文件时,在编译阶段编译器会帮我们生成相应的 Java 代码实现 IPC 通信,既然用到了 AIDL,自然而然就衍生出一些问题。
什么是 aidl?为什么要设计 aidl 这门语言?
AIDL是缩写,全称是:Android Interface Definition Language,Android 接口定义语言(AIDL),你可以利用它定义客户端与服务均认可的编程接口,以便二者使用进程间通信(IPC) 进行互相通信。在 Android 中,一个进程通常无法访问另一个进程的内存。因此,为进行通信,进程需将其对象分解成可供操作系统理解的原语,并将其编组为可供您操作的对象。编写执行该编组操作的代码较为繁琐,因此 Android 会使用 AIDL 为您处理此问题。
aidl的优势有两方面:
- aidl简化代码量,如果用java实现需要写很多重复固定的代码,代码量庞大,容易出现错误
- aidl可以通过脚本的配置文件生成固定的java代码
aidl生成java文件是通过 android sdk 中的aidl脚本文件(window是aidl.exe)来生成Java文件的。
aidl的本质作用如下:
在编码实现跨进程调用之前,我们先要了解下用到的一些类(IBinder/IInterface/Binder/BinderProxy/Stub)。了解了这些类的职责,有助于我们更好的理解和实现跨进程通信。
- IBinder : IBinder 是一个接口,代表了一种跨进程通信的能力。只要实现了这个借口,这个对象就能跨进程传输。
- IInterface : IInterface 代表的就是 Server 进程对象具备什么样的能力(能提供哪些方法,其实对应的就是 AIDL 文件中定义的接口)
- Binder : Java 层的 Binder 类,代表的其实就是 Binder 本地对象。BinderProxy 类是 Binder 类的一个内部类,它代表远程进程的 Binder 对象的本地代理;这两个类都继承自 IBinder, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder 驱动会自动完成这两个对象的转换。
- Stub : AIDL 的时候,编译工具会给我们生成一个名为 Stub 的静态内部类;这个类继承了 Binder, 说明它是一个 Binder 本地对象,它实现了 IInterface 接口,表明它具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要开发者自己实现。
源码分析
以一个简单的aidl为例,我们新建一个aidl文件IEegetsInterface,如下:
import com.example.servicedemo.binder.bean.StudentBean;
interface IEegetsInterface {
List<StudentBean> getStudents();
void addStudents(out StudentBean studentBean);
}
新建完成后,我们执行编译,会在 app/build 下生成对应的 IEegetsInterface.java 文件,如下图:
看看系统给我们生成的 IEegetsInterface.java 源码:
public interface IEegetsInterface extends android.os.IInterface
{
/** Default implementation for IEegetsInterface. */
public static class Default implements com.example.servicedemo.IEegetsInterface
{
@Override public java.util.List<com.example.servicedemo.binder.bean.StudentBean> getStudents() throws android.os.RemoteException
{
return null;
}
@Override public void addStudents(com.example.servicedemo.binder.bean.StudentBean studentBean) throws android.os.RemoteException
{
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
/** 「1」静态抽象内部类 Stub 继承自 Binder 同时实现接口的方法,用于构造服务端的IBinder对象 */
public static abstract class Stub extends android.os.Binder implements com.example.servicedemo.IEegetsInterface
{
//「1-1」Service唯一的id
private static final java.lang.String DESCRIPTOR = "com.example.servicedemo.IEegetsInterface";
/** Construct the stub at attach it to the interface. */
public Stub()
{
//「1-2」用于将 `IEegetsInterface` 接口和 Binder 通过 `DESCRIPTOR` 唯一性进行关联
this.attachInterface(this, DESCRIPTOR);
}
/** 「1-3」根据 service的唯一Id通过 `queryLocalInterface` 获取相应的的Interface对象,如果查找不到则会拿`IBinder`对象obj创建一个代理对象Proxy */
public static com.example.servicedemo.IEegetsInterface asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.servicedemo.IEegetsInterface))) {
return ((com.example.servicedemo.IEegetsInterface)iin);
}
return new com.example.servicedemo.IEegetsInterface.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
//接收整个系统的调用,客户端发起请求,通过 Binder内部的驱动处理,系统会调用到 onTransact,从而匹配到对应的接口,最终调用服务端 Service 里的接口方法
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getStudents:
{
data.enforceInterface(descriptor);
java.util.List<com.example.servicedemo.binder.bean.StudentBean> _result = this.getStudents();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addStudents:
{
data.enforceInterface(descriptor);
com.example.servicedemo.binder.bean.StudentBean _arg0;
_arg0 = new com.example.servicedemo.binder.bean.StudentBean();
this.addStudents(_arg0);
reply.writeNoException();
if ((_arg0!=null)) {
reply.writeInt(1);
_arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
}
else {
reply.writeInt(0);
}
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
//客户端的代理类,实现接口的方法
private static class Proxy implements com.example.servicedemo.IEegetsInterface
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override public java.util.List<com.example.servicedemo.binder.bean.StudentBean> getStudents() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.example.servicedemo.binder.bean.StudentBean> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_getStudents, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getStudents();
}
_reply.readException();
_result = _reply.createTypedArrayList(com.example.servicedemo.binder.bean.StudentBean.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addStudents(com.example.servicedemo.binder.bean.StudentBean studentBean) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_addStudents, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().addStudents(studentBean);
return;
}
_reply.readException();
if ((0!=_reply.readInt())) {
studentBean.readFromParcel(_reply);
}
}
finally {
_reply.recycle();
_data.recycle();
}
}
public static com.example.servicedemo.IEegetsInterface sDefaultImpl;
}
static final int TRANSACTION_getStudents = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addStudents = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
public static boolean setDefaultImpl(com.example.servicedemo.IEegetsInterface impl) {
if (Stub.Proxy.sDefaultImpl == null && impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.example.servicedemo.IEegetsInterface getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
public java.util.List<com.example.servicedemo.binder.bean.StudentBean> getStudents() throws android.os.RemoteException;
public void addStudents(com.example.servicedemo.binder.bean.StudentBean studentBean) throws android.os.RemoteException;
}
Stub
当 Client 端在创建和服务端的连接,调用 bindService
时需要创建一个 ServiceConnection
对象作为入参。在 ServiceConnection
的回调方法 onServiceConnected
中 会通过这个 asInterface(IBinder binder)
拿到 AIDL 接口对象,这个 IBinder 类型的入参 binder 是驱动传给我们的。
从 AIDL 生成的 Java 类我们可以看出,Stub 继承自 Binder 同时实现接口的方法,用于构造服务端的IBinder对象,意味着 Stub 自己本身就是一个 Binder 对象,并且实现IEegetsInterface
接口(接口本身是IInterface
),因此 Stub 有客户端需要的方法,也就是(addStudents 和 getStudents),
attachInterface
private static final java.lang.String DESCRIPTOR = "com.example.servicedemo.IEegetsInterface";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
用于将 IEegetsInterface
接口和 Binder 通过 DESCRIPTOR
唯一性进行关联。我们可以简单理解为该方法会将(descriptor,owner
)作为(key,value
)存入Binder
对象中的一个Map
对象中,Binder
对象可通过attachInterface
方法持有一个IInterface
对象(即owner
)的引用,并依靠它获得完成特定任务的能力。queryLocalInterface
方法可以认为是根据key
值(即参数 descriptor
)查找相应的IInterface
对象。
asInterface
public static com.example.servicedemo.IEegetsInterface asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.servicedemo.IEegetsInterface))) {
return ((com.example.servicedemo.IEegetsInterface)iin);
}
return new com.example.servicedemo.IEegetsInterface.Stub.Proxy(obj);
}
根据 service的唯一Id(DESCRIPTOR)通过 queryLocalInterface
获取相应的的Interface对象,queryLocalInterface
方法可以认为是根据key
值(即参数 descriptor
)查找相应的IInterface
对象。如果查找不到则会拿IBinder
对象obj创建一个代理对象Proxy。
Proxy
private static class Proxy implements com.example.servicedemo.IEegetsInterface
{
//Stub 传递的IBinder对象
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public java.util.List<com.example.servicedemo.binder.bean.StudentBean> getStudents() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.example.servicedemo.binder.bean.StudentBean> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_getStudents, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getStudents();
}
_reply.readException();
_result = _reply.createTypedArrayList(com.example.servicedemo.binder.bean.StudentBean.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addStudents(com.example.servicedemo.binder.bean.StudentBean studentBean) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_addStudents, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().addStudents(studentBean);
return;
}
_reply.readException();
if ((0!=_reply.readInt())) {
studentBean.readFromParcel(_reply);
}
}
finally {
_reply.recycle();
_data.recycle();
}
}
public static com.example.servicedemo.IEegetsInterface sDefaultImpl;
}
在 Proxy 中的 getStudents()
和 addStudents()
方法中首先通过 Parcel 将数据序列化,然后调用 remote.transact()
。从代码我们知道 Proxy
是在 Stub 的 asInterface
中创建,即这里的 remote 是个 BinderProxy 对象。最终通过一系列的函数调用,Client 进程通过系统调用陷入内核态,Client 进程中执行 addBook() 的线程挂起等待返回;驱动完成一系列的操作之后唤醒 Server 进程,调用 Server 进程本地对象的 onTransact()
。最终又走到了 Stub 中的 onTransact()
中,onTransact()
根据函数编号调用相关函数(在 Stub 类中为 IEegetsInterface 接口中的每个函数中定义了一个编号,只不过上面的源码中我们简化掉了;在跨进程调用的时候,不会传递函数而是传递编号来指明要调用哪个函数);我们这个例子里面,调用了 Binder 本地对象的 getStudents()
和 addStudents()
并将结果返回给驱动,驱动唤醒 Client 进程里刚刚挂起的线程并将结果返回。
transact
Binder.onTransact()
是为Binder.transact()
的调用而准备的,Binder.transact()
做了两件事:
public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
int flags) throws RemoteException {
if (false) Log.v("Binder", "Transact: " + code + " to " + this);
if (data != null) {
data.setDataPosition(0);
}
boolean r = onTransact(code, data, reply, flags);
if (reply != null) {
reply.setDataPosition(0);
}
return r;
}
此段代码位于android/os/Binder.java
首先,在调用Binder.onTransact()
之前及之后,分别对请求结构的引用及返回结构的引用重置读写position
,以及调用Binder.onTransact()
。在此提醒,Binder.transact()
的调用者是Stub
下的内部类Proxy
中的各个.aidl
中定义的方法。
最终,千辛万苦地,终于来到了.aidl
自行生成实现的Binder.onTransact()
方法了。特别的是,有两个地方值得去注意:
- 其一,在最终的开发中,将会继承抽象类
Stub
,并实现所有在.aidl
中定义的方法。这些具体方法的直接调用者,正是当前我们所在的onTransact()
方法; - 其二,正是基于上面一条,可以得知:无论远程调用者(Client)身处何方,最终,一定会经过此处的
onTransact()
方法,并由onTransact()
直接调用目标方法。
要知道,传入onTransact()
方法的参数中,拥有目标方法的ID
、指向参数的引用,以及指向返回结果的引用。所有远程调用者(Client)想要做的事,都通过层层调用及参数包装汇聚到onTransact()
,再由onTransact()
分发到真正的目标方法执行。
AIDL关键字
oneway修饰符
/*
* IEegetsInterface.aidl
*/
interface IChangeCallback {
int changeData(int changeIndex); //同步调用
oneway void changeData(int changeIndex); //异步调用
}
oneway关键字用于修饰远程调用的行为,被oneway修饰的方法不能有返回值。也不可以带有in
或 out
的参数。
被 oneway
修饰的方法支持异步回调,它只是发送事物数据并立即返回,接口的实现最终调用此实现时,是以正常远程调用形式将其作为来自 Binder 线程池的常规调用来接收。
in、out、inout
/*
* IEegetsInterface.aidl
*/
interface IChangeCallback {
String getConvertName(in StudentInfo info);
void getServiceStudentInfo(out StudentInfo serviceInfo);
void getServiceStudentInfoInOut(inout StudentInfo serviceInfo);
}
通信中的数据流向。
- in: 数据只能由客户端流向服务端(表现为服务端修改参数对象,并不会影响客户端。服务端->客户端)
- out: 数据只能由服务端流向客户端(表现为服务端收到的参数是空对象,服务端修改参数对象,客户端也会同步变化。服务端->客户端)
- inout: 表示数据可在服务端和客户端双向流动(表现为服务端可以接收到客户端传递的对象,并且服务端修改数据客户端也会同步改变。服务端<->客户端)
参考资料: