“我正在参与「启航计划」”
接着上篇文章重学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: 表明数据可在服务端和客户端双向流动(表现为服务端能够接纳到客户端传递的方针,而且服务端修正数据客户端也会同步改动。服务端<->客户端)
参考资料:
Binder学习指南
Binder 整体架构及相关代码浅析
Binder:为什么要经过onTransact()调用方针办法
Android 进阶8:进程通讯之 Binder 机制浅析