笔者刚开始学习的时候看的《安卓开发艺术探索》,看完之后感觉不太清楚难以理解。在此建议大家先去实战,去体验代码写的原因以及逻辑,然后再学习AIDL生成的binder代码感觉会好很多;
Binder的起因
- 程序都是由一些
Activity和Service组成的,那么这些组件可能在一个进程中,又可能不在一个进程中。 - 每一个进程都会有一个独立的虚拟机,每个虚拟机在内存分配上有不同的地址空间,所以当不同进程访问同一个类的对象时,这个类的对象会产生很多的副本,其他进程访问的是副本;
- 所以两个进程无法通信,此时
binder应运而出;
Binder是什么
以下是《安卓开发艺术探索》中对于binder的介绍:
-
Binder是Android中的一个类,继承了IBinder接口;IBinder是Android中IPC的桥梁接口,是客户端和服务端通信的唯一桥梁对象;而Binder实现了这个接口; -
Binder可以理解为一种虚拟的物理设备,它的设备驱动是dev/binder; -
从
Android FrameWork的角度说的话,Binder是连接ServiceManager和MS以及各种Manager的桥梁;- Android FrameWork :是基于Linux以及应用层的系统框架层,包含了很多组件和服务,例如活动管理器,窗口管理器;
ServiceManager:以startActivity()举例,系统启动时,它最早被启动。当AMS启动后,AMS会在ServiceManager注册服务名和b引用(binder),那么当startActivity()时,框架层找不到AMS,这个时候就会去ServiceManager查表,通过binder引用找到AMS,然后AMS会经过效验,去通过b引用去对应的进程中调用方法实现生命周期的管理,此外,AMS还会管理任务栈以及任务、应用进程等;
-
从应用层面来说,
Binder是客户端和服务端通信的媒介,当binderService的时候,那么服务端会返回一个包含了服务端业务调用的binder对象,通过这个binder对象,客户端就可以访问到服务端提供的数据以及服务;(了解binder原理后,看这段话就可以理解了)
Binder的选择
Android继承于Linux,Linux本身提供了很多跨进程的机制,那么Android为什么会开发binder机制呢?
简要介绍Linux中的几种IPC机制:
共享内存
-
介绍:顾名思义,就是两个进程共享一片内存区域,当一个进程修改地址上的内容时,其他进程会察觉到更改;
-
安全和性能分析:
- 因为不需要数据的复制,直接访问的是同一片内存区域,所以是IPC中最快,性能最好的方式;
- 实现较为复杂,且安全性较低(因为任意的访问可能导致恶意修改或者隐私泄露);
管道
-
介绍:管道分为单向管道和双向管道,单向管道有固定的读端、写端,写进程通过写端向管道文件写数据,读进程通过读端从管道文件读数据;
-
安全和性能分析:
- 关于性能:数据复制了两遍;
- 管道分为阻塞和非阻塞,一般简单同步程序阻塞,这时如果读写操作不当,就会出现阻塞的情况;(比如管道里面没数据,读的进程要读,那么进程就会被挂起,也就是发生阻塞)
- 管道只能字节流;
- 管道有缓冲区,传输数据大小超过缓冲区了也会发生阻塞;
消息队列
- 介绍:存放在内核中的消息链表,消息队列由消息标识符表示,允许多个进程同时读和写,发送方和接收方要约定好消息体的大小和类型;
- 安全和性能分析:数据也是会经过两次复制;
Socket
-
介绍:原本是为了网络设计的,但是也可以进行进程间通信;
-
安全和性能分析:
Socket有两个缓冲区,一读一写,那么进程A->写缓冲区复制一次,写缓冲区->读缓冲区是物理传输,读缓冲区->读进程B复制一次,一共复制两次;
Binder的优势
- 从性能角度:
Binder的数据拷贝只需要一次,而管道,消息队列、Socket都需要2次,共享内存方式实现方式复杂; - 从安全角度:传统的
IPC机制对于通信双方没有严格的验证,Binder协议本身就支持双方做身份效验,提升了安全性;
Binder的实战
Binder的工作流程:

上面的图方便大家理解;;;
流程大致如下:
- 服务端:会创建
Binder的实例,此时会开启隐藏的Binder线程(来接收客户端的请求),Binder驱动会创建mRemote对象; - 客户端:调用
bindService()方法后,Binder驱动会返回mRemote对象;那么此时客户端可以调用transact()方法通过Binder驱动向服务端发送请求,同时挂起线程; - 服务端:收到数据后调用
ontransact()方法,通过Binder驱动把结果返回给客户端; - 客户端:收到数据之后,重新拉起线程;
通过Binder实现跨进程通信
功能就是通过名字查询名字对应的信息,重点在于实现的逻辑;
接口
java
public interface Queuemes {
String getMesName(String name);
}
接口里面写了我们要实现的功能;
服务端
java
public class MyService extends Service {
Binder binder;
public MyService() {
binder = new MyServiceNative();
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
}
创建Serviece,在服务端里创建Binder实例;
onBind():return的是客户端接收的Binder对象;
java
class MyServiceNative extends Binder implements Queuemes{
@Override
protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
if (code == 1) {
String name;
name = data.readString();
String res = getMesName(name);
reply.writeString(res);
return true;
}
return super.onTransact(code, data, reply, flags);
}
public String getMesName(String name) {
String mes;
switch (name){
case "徐照茹":
mes = "万事都灵";
break;
case "高远":
mes = "我要进大厂(怒吼)";
break;
default:
mes = "yws";
break;
}
return mes;
}
}
我们自定义了一个Binder类MyServiceNative,它继承了Binder,实现了Queuemes接口;重写了 onTransact方法和实现的功能;
-
为什么要实现
Queuemes接口:原因:如果客户端和服务端是同一个进程,不会走跨进程的过程,
Binder内驱返回的是服务端中Binder的对象;但如果不是一个进程,那么会走跨进程的过程,Binder内驱返回的是Binder的代理(稍后会介绍到);结果:如果是同一个进程,那么客户端需要用到服务端的资源(这里指方法),所以实现我们想要的借口,重写方法,这样客服端拿到
Binder对象之后才可以使用方法; -
onTransact():-
参数:
int code,Parcel data,Parcel reply,int flagscode:用来标识请求的是哪一个方法,data:目标方法的参数,reply:目标方法的返回值,flags:0为同步,IBinder.FLAG_ONEWAY为异步; -
介绍:运行在服务端
Binder线程池中,如果此方法的返回值为false,表示客户端请求失败,以此可以做一些权限验证,提高了安全性; -
过程:判断请求方法,通过
data取得参数,处理后把结果写入reply,返回ture;
-
java
<service
android:name=".MyService"
android:enabled="true"
android:exported="true"
android:process=":ljx"
>
<intent-filter>
<action
android:name="com.example.myselfbinder.action.BIND_MYSERVICE">
</action>
</intent-filter>
</service>
同时我们需要把服务端运行在不同的进程中: android:process=":ljx"
并且指定action
客户端
java
binding.bt1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String action = "com.example.myselfbinder.action.BIND_MYSERVICE";
Intent intent = new Intent(action);
intent.setPackage("com.example.myselfbinder");
bindService(intent,serviceConnection,BIND_AUTO_CREATE);
}
});
点击按钮,我们绑定服务,注意的是我们需要指定启动服务所在的包名,通过bindService进行绑定;
bindService:第二个参数是ServiceConnection的对象,这个方法接收绑定成功或失败的回调;
java
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
binder = asInterface(iBinder);
Toast.makeText(getBaseContext(),"连接成功了",Toast.LENGTH_SHORT).show();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
创建ServiceConnection的对象,在成功链接服务的回调里: binder = asInterface(iBinder):左边是我们定义方法接口的对象;右边调用了asInterface(),这里的ibinder其实是Binder驱动返回的mRemote对象;
asInterface():因为前文提到过,不同的进程,
Binder驱动返回值是不一样的,比如我们这里属于跨进程通信,返回的是Binder的代理,那如果同一个进程,返回的是Binder本体;那么返回的
Binder不一样,其实走的就是两条路了,所以我们应该写个方法判断一下,是否是同一个进程,这个方法就是asInterface();左边为什么是我们定义接口的对象:
代理也需要实现
Queuemes我们统一定义的接口,这样我们不管是不是代理,都只需要调用mRemote对象中我们需要的方法,就可以实现通信,为了统一把;
java
private static Queuemes asInterface(android.os.IBinder os){
if(os == null){
return null;
}
else if(os instanceof Binder){
return (Queuemes) os;
}
else{
return new MesBinderProxy(os);
}
}
实现Binder返回值的判断,我们可以看到,如果是Binder,那么强转为Queuemes,如果不是,则为Binder的代理,那我们就创建实例,接下来看看代理类怎么写;
java
private static class MesBinderProxy implements Queuemes{
private IBinder mRemote;
public MesBinderProxy(IBinder mRemote) {
this.mRemote = mRemote;
}
@Override
public String getMesName(String name) {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
String mes;
try {
_data.writeString(name);
mRemote.transact(1,_data,_reply,0);
mes = _reply.readString();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
finally {
_data.recycle();
_reply.recycle();
}
return mes;
}
}
方法内很简单,构造方法中我们得到了Binder驱动返回的对象;主要方法介绍:
Parcel.obtain():申请一个空的parcel对象;为什么不new一个?
parcel不是普通的java类,它的内容储存在native层共享内存,obtain()是从对象池去一个现成的parcel,减少内存分配的开销;
transact:底层会通过Binder的驱动,将_data数据发送给服务端,服务端会把最后的数据写到_reply中;
_data.recycle():回收parcel对象,释放底层空间;
大致逻辑就是,得到两个parcel对象,然后把数据写入_data,通过transact和服务端通信,将线程挂起,当服务端返回时,线程继续,我们从服务端返回的 _reply对象中读出返回的值,最后回收parcel对象;
java
binding.bt2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String s = binder.getMesName("徐照茹");
Toast.makeText(getBaseContext(),s,Toast.LENGTH_SHORT).show();
}
});
最后通过按钮的点击事件,我们得到服务端返回的数据,通过Toast提示出来;
至于为什么这么写,大家可以根据上面的Binder工作流程结合进行理解;
Binder的高效实现
为了帮助开发者实现Binder,AIDL自动生成了跨进程通信的模版,避免写一大堆统一的代码,有了AIDL,我们在服务端关注方法的实现就行,客户端不需要写额外的代码,因为全部自动生成了;
接下来我们对生成的代码进行解析比对,这下理解起来就比较容易了;
操作很简单,写两个aidl文件以及一个java文件:
java
// Book.aidl
package com.example.binder.aidl;
// Declare any non-default types here with import statements
parcelable Book;
aidl用到了实现序列化的Book类,这里声明一下;
java
// IBookManager.aidl
package com.example.binder.aidl;
// Declare any non-default types here with import statements
import com.example.binder.aidl.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
aidl接口文件,里面声明了我们想实现的两个方法,一个得到书的列表,一个增加书;
需要注意的是import com.example.binder.aidl.Book;一定要导入book类,即使是在同一个包下,也要导入!!!
java
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(@NonNull Parcel parcel, int i) {
parcel.writeInt(bookId);
parcel.writeString(bookName);
}
}
实现序列化的Book类;

结构大概长这样,括弧,如果你放的路径有问题,也是无法正常生成的;
那么重新build,系统就会自动在gen目录下生成一个类IBookManager.java:
现在我们来简单介绍一下这个类
生成类框架理解
java
package com.example.binder.aidl;
public interface IBookManager extends android.os.IInterface
{
public static abstract class Stub extends android.os.Binder implements com.example.binder.aidl.IBookManager
{
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
}
private static class Proxy implements com.example.binder.aidl.IBookManager
{
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
//Binder的唯一标识
public static final java.lang.String DESCRIPTOR =
//声明的方法
public java.util.List<com.example.binder.aidl.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.example.binder.aidl.Book book) throws android.os.RemoteException;
}
这是它的大致框架:
-
IBookManager实现了IInterface接口,同时本身也是一个接口;所有在Binder中传输的接口都必须实现这个接口;
-
接口里面,声明了我们想调用的两个方法,以及DESCRIPTOR;
DESCRIPTOR:Binder的唯一标识,一般用Binder的类名标识; -
Stub:就是一个Binder类,Stub内部还有一个Proxy是客户端的代理类; -
在
Stub里面定义了两个方法的整形Id,为了标识在跨进程通信中,客户端到底调用的是哪个方法;
生成类Stub
java
public static abstract class Stub extends android.os.Binder implements com.example.binder.aidl.IBookManager
{
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
public static com.example.binder.aidl.IBookManager asInterface(android.os.IBinder obj)
{
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
}
private static class Proxy implements com.example.binder.aidl.IBookManager
{
}
}
我们可以看到Binder类Stub中,除了构造方法以外,还有asInterface, onTransact,asBinder()三个方法;
其实前两个方法在我们刚刚完成了实战中已经讲过了,,,这里先说下第三个方法,也很好理解;
asBinder():相当于一个get方法,用于返回当前的Binder对象;
onTransact:根据code来分发具体要执行的方法;
asInterface:没啥好说的,如果是不同进程,就会返回包装好的客户端的代理类对象Proxy;这个方法在客户端调用,用来获取一个IBookManager的对象,就是我们自己定义的接口对象;
生成类Proxy
java
private static class Proxy implements com.example.binder.aidl.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.example.binder.aidl.Book> getBookList() throws android.os.RemoteException
{
//得到Parcel对象
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
//返回值的对象
java.util.List<com.example.binder.aidl.Book> _result;
try {
//把参数写入
_data.writeInterfaceToken(DESCRIPTOR);
//调用Binder或Binder代理的方法,请求服务端
boolean _status = mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
//获取服务端信息
_reply.readException();
//获取信息,构造返回的列表;
_result = _reply.createTypedArrayList(com.example.binder.aidl.Book.CREATOR);
}
finally {
//避免内存泄漏
_reply.recycle();
_data.recycle();
}
return _result;
}
}
}
结构和我们自己写的很相似,这里不做过多介绍,也都写注释;
Binder的总结
Binder的线程管理
- 每个
Binder的服务端会创建很多Binder的线程来处理Binder的请求,可以简单理解为Binder的线程池,但是它不是由Service端管理的,是由Binder的驱动来管理的; - 一个进程中最大
Binder线程数量为16,超出数量的请求会阻塞等待空闲的Binder线程;
Binder讲的到底是什么?
- 通常意义上来说,
Binder指的是Android的IPC机制; - 从服务端的进程来说,
Binder指的是Binder的本地对象;从客户端来说,Binder指的是Binder的代理对象; - 从传输过程中来说,
Binder是可夸进程传输的对象;
Binder的通信原理
-
Binder是基于内存映射mmap来实现的,直接操作映射的这一部分内存的数据,后面通过映射来得到数据,所以它只用复制一次,所以它的性能很好; -
大致的流程:
- Binder驱动会在内核空间开辟出一个接收数据的缓冲区;
- 接着又会在内核空间开辟出一个内核缓冲区;
- 内核缓冲区会和接收数据缓冲区建立映射关系;
- 接收数据缓冲区又会和接收进程的空间地址建立映射关系;
- 发送数据的进程会将数据复制给内核缓冲区;
- 由于内核缓冲区和接收数据缓冲区存在映射关系,接收数据缓冲区又和接收进程的空间地址有映射关系,所以在接受进程中会直接获得这段数据;

这样便完成了一次Binder通信!
好啦,本次分享的内容到此结束,期待下次具体的IPC方式的文章和大家见面~