在Android中要学习Binder那肯定就绕不开IPC(进程间通信机制)。进程间通信,顾名思义就是将数据从某一进程传递到另一进程的过程。这就涉及到Linux进程中内存空间的划分了。如图所示:
对Binder通信机制的理解
传统IPC,相当于生活中,我们将快递从北京寄到上海。此时,北京的用户需要将快递交到快递员手中,对于快递员而言就是copy_from_user(),然后快递员再通过快递公司的运输将快递交到上海的用户(接收方)手中,对于上海的快递员而言就是copy_to_user()。这个过程在程序中相当于将数据copy了两次。而Binder通信机制相当于我们发送的快递接收方就是上海的快递员,这样就省去了在上海快递员将快递交到用户(接收方)手中的过程,省去了copy_to_user。这相比传统的IPC更快一些。Binder通过mmap映射机制将数据映射到共享的内核空间从而实现减少一次数据copy的目的。下面盗用一张表格来比较说明一下Binder与传统IPC的优缺点。
| Binder | 共享内存 | Socket |
性能 | 需要拷贝一次 | 无需拷贝 | 需要拷贝两次 |
特点 | 基于C/S 架构 易用性高 | 控制复杂,易用性差 | 基于C/S 架构 作为一款通用接口,其传输效率低,开销大 |
安全性 | 为每个APP分配UID 同时支持实名和匿名 | 依赖上层协议 访问接入点是开放的 不安全 | 依赖上层协议 访问接入点是开放的 不安全 |
安全性:共享内存和Socket依赖上层协议,它们是不安全的,Binder是比较安全的。
理解:共享内存和Socket相当于将要传输的数据包括PID和UID打成一个数据包传输到系统中,而此时系统拿到的PID是APP传递过来的不是系统本身通过自己的机制得到的,系统是没有辨别真伪的能力的。这就相当于拿着伪造的身份证到社会中做事情,我们知道社会中的广大民众一般是无法识别身份证的真伪的,这样会造成很大的安全隐患。而Binder机制中,系统会为每个APP分配PID和UID(这也就相当于公安局会为每个公民设定并发放一张身份证一样),这就好比拿着假的身份证去公安局作案,这不得被当场抓获吗。所以Binder机制的安全性就得到了保证。
Android-Binder运行机制总结
Binder是Android中的一个类,它实现了IBinder接口。从IPC角度来说,Binder是Android中的一种进程间通信的机制。从Android FrameWork角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager等等)和相应ManagerService的桥梁;从Android应用程序来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。在Android开发中,Binder主要用于Service中。
首先我们先看一下Binder运行机制图,图是我自己画的有些丑,大家不要见怪
上面提到,Binder是一种进程间的通信机制,Android中我们经常会使用AIDL来完成跨进程的通信。现在我们就来了解一下AIDL的调用过程。首先我们思考三个问题:
1. 客户端如何获取到AIDL的遥控器的?
2. 通过这个遥控器是如何调用到服务端的?
3. 服务端是如何处理的?
带着这三个问题我们去分析AIDL的工作流程。
1.在Android-studio中新建一个Book类,并实现android自己的序列化接口Parcelable,并完成其步骤
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
protected Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
}
2. 新建一个Book.aidl文件
只需要
parcelable Book;
这表示Book.aidl是Book类在AIDL中的声明。
3. 新建一个IBookManager.aidl文件
// 引用一定要写,即使是在同一包下
import com.binderstudydemo.Book;
// Declare any non-default types here with import statements
interface IBookManager {
//用于从远程服务端获取图书列表
List<Book> getBookList();
//用于往图书列表中添加一本书
void addBook(in Book book);
}
4. 系统会为我们生成一个IBookManager.java 类,其代码如下
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: C:\\AndroidSources\\androidStudyDemo\\BinderStudyDemo\\app\\src\\main\\aidl\\com\\binderstudydemo\\IBookManager.aidl
*/
package com.binderstudydemo;
// Declare any non-default types here with import statements
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.binderstudydemo.IBookManager {
//Binder的唯一标识
private static final java.lang.String DESCRIPTOR = "com.binderstudydemo.IBookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
// 注释1
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.binderstudydemo.IBookManager interface,
* generating a proxy if needed.
*/
/**
* 用于将服务端的Binder对象转换成客户端所需要的AIDL接口类型的对象,这种转换过程是区分进程的,
* 如果客户端和服务端在同一进程,那么此方法返回的就是服务端的Stub对象本身,否则返回的是系统封装后的Stub.proxy
*/
public static com.binderstudydemo.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
// 注释2
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.binderstudydemo.IBookManager))) {
return ((com.binderstudydemo.IBookManager) iin);
}
return new com.binderstudydemo.IBookManager.Stub.Proxy(obj);
}
/**
* 此方法用于返回当前Binder对象
* @return
*/
@Override
public android.os.IBinder asBinder() {
return this;
}
/**
* 运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理
*/
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.binderstudydemo.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.binderstudydemo.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.binderstudydemo.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.binderstudydemo.IBookManager {
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.binderstudydemo.Book> getBookList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.binderstudydemo.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.binderstudydemo.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.binderstudydemo.Book book) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain(); // 3
android.os.Parcel _reply = android.os.Parcel.obtain(); // 4
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book != null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
// 注释5
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.util.List<com.binderstudydemo.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.binderstudydemo.Book book) throws android.os.RemoteException;
}
代码有些长,但这是系统自动生成的,我们想着分析其中的处理流程不得不把代码都贴出来。
首先我们对这个类做个分析,IBookManager.Java这个类,它继承了 IInterface 这个接口,同时它自己也还是个接口,所有可以在Binder中传输的接口都需要继承 IInterface 接口。
现在我们来回到上面提出的三个问题。对于问题1:
在客户端连接到服务端后(bindService)后,回调方法onServiceConnected中有一句这样的代码:
iAidl = IBookManager.Stub.asInterface(service);
在Stub的asInterface方法中注释2处,调用了Binder的queryLocalInterface(DESCRIPTOR)方法,我们可以跟进代码,Binder的queryLocalInterface方法代码如下:
/**
* Use information supplied to attachInterface() to return the
* associated IInterface if it matches the requested
* descriptor.
*/
public IInterface queryLocalInterface(String descriptor) {
if (mDescriptor.equals(descriptor)) {
return mOwner;
}
return null;
}
// 而成员变量mDescriptor的赋值是在Binder的attachInterface方法中,代码如下
/**
* Convenience method for associating a specific interface with the Binder.
* After calling, queryLocalInterface() will be implemented for you
* to return the given owner IInterface when the corresponding
* descriptor is requested.
*/
public void attachInterface(IInterface owner, String descriptor) {
mOwner = owner;
mDescriptor = descriptor;
}
注意:这个attachInterface方法在Stub的构造方法注释1中调用了,这也就是说明了DESCRIPTOR是在Stub的构造方法中赋值的。客户端的调用并没有哦对Stub做初始化操作,所以为空;而在服务端中继承自Service重写的onBind方法返回的IBinder对象,它的初始化时new了一个AIDL.Stub();所以服务端中的DESCRIPTOR不为空,所以客户端调用Stub.asInterface方法时里面的queryLocalInterface(DESCRIPTOR)返回值为空,因此该方法返回的是Proxy。调用queryLocalInterface方法的目的是当Activity和Service在同一进程时我们就不需要再去通过Binder来传递信息了。对于非同一进程我们拿到的是Proxy对象的引用,此时我们就可以调用Proxy的方法了,Proxy实现了我们在自定义AIDL中的方法getBookList和addBook方法。此时就可以通过Proxy进行写入操作了,_data包用来存储发送到服务端的数据,_reply包用来存储服务端返回来的数据。问题1的解释就到此结束了。
现在我们继续分析问题2:
在客户端我们通过AIDL类型IBookManager的实例对象调用到了addBook方法(此处我们只拿一个方法举例)。代码就走到了IBookManager的最后一行,我们继续看重写该方法的地方,此时代码就走到了Proxy的addBook方法了。此处做了_data 包的写入工作,注释5处,调用了Binder的transact方法,代码如下:
public final boolean transact(int code, Parcel data, Parcel reply,
int flags) throws RemoteException {
if (false) Log.v("Binder", "Transact: " + code + " to " + this);
if (data != null) {
data.setDataPosition(0);
}
// 调用了onTransact方法,
boolean r = onTransact(code, data, reply, flags);
if (reply != null) {
reply.setDataPosition(0);
}
return r;
}
该方法中,调用了onTransact方法,IBookManager的Stub类中重写了onTransact方法。onTransact方法中也有对我们自定义的AIDL中的两个方法的调用。至此,问题2我们也说明白了。
注意:在Proxy中mRemote.transact(Stub.TRANSACTION_addBook,_data,_reply,0);调用该方法进入Binder后,会将客户端的线程挂起来,直到服务端有数据返回后,客户端的线程才会继续运行。
此函数参数一的含义:为了让客户端告诉服务端调用的是哪个方法,它是一个int型的值(在Proxy的最后进行的赋值),在经过AIDL后服务端可以知道该int型的值对应的方法。
参数三:flags,0代表可发送可返回;1代表不可返回。
现在看最后一个问题3:
由上可知Binder的onTransact调用到了Stub的onTransact方法,服务端在该方法接收数据并作出处理。通过参数code判断调用的是哪个方法。还接着上面的流程,分析调用了this.addBook(....)方法,但是IBookManager的内部类Stub并没有实现addBook,所以此处的addBook调用的正是真正的服务Service中的addBook方法。问题3的分析到此结束了。至此,整个从客户端到服务端的调用以及服务端作出处理的过程我们都分析完了。
至于说Activity中调用IBookManager.addBook,绑定Service的过程,以及Service创建并返回IBinder的过程我没有给出代码,这个就是一个很平常的使用。本文的目的就是分析AIDL的过程。
最后附上AIDL的时序图:
关于Binder的基础学习,之前我已经写过一篇了,本文是在原有基础上增加了AIDL调用过程的详细分析。在文章一开头出现的那张Linux内存空间划分的图我是盗用的其他人的。希望勿怪。
在此我要感谢享学课堂的Leo老师,将AIDL的调用流程讲解的很详细。再次表示感谢。