Binder是什么
在Android中,Binder从不同的角度理解都是不同的东西。直观来说,Binder就是一个类,实现了IBinder接口;从IPC(Inter-Progress Communication)角度来看,Binder是Android中特有的进程间通信方式;另外,Binder也可以理解为在整个通信机制中的底层驱动,是真正负责进程间数据传输的媒介。然而,总的来说,这些都是为进程间通信而服务的。
IPC原理
既然Binder是为进程间通信服务的,那么有必要讲一下进程间通信的原理。进程间通信,本质上就是数据在进程间的传输,而数据是存放在内存中的,那么进程间的通信可以理解为把进程A可以访问的内存中的数据放到进程B可以访问的内存中。众所周知,Android系统是基于Linux的,而Linux把每个进程的逻辑地址空间划分为用户空间和内核空间(在32位系统中,用户空间为3G,内核空间为1G),进程只能访问自己的用户空间,不能访问其他进程的用户空间,即用户空间是非共享的,而内核空间则是跨进程的(因为每个进程的内核空间映射的物理内存是一样的),每个进程都可以访问,显然,IPC原理(包括Binder机制)就是通过内核空间来实现的,如下图所示。
注意,Binder机制并不等于进程间通信机制,两者属于包含关系,Binder只是Android特有的一种进程间通信的方式。在Linux中,进程间通信的方式还有管道、信号量、共享内存、Socket等,它们无一例外的都使用到了内核空间,而在Binder机制中,Binder驱动就是运行在内核空间中负责传输数据的。
Binder机制原理
Binder机制是基于C/S架构的(Client/Server),其架构图如下。
- Client进程 使用服务的进程。
- Server进程 提供服务的进程。
- ServiceManager 顾名思义,就是用来管理服务的进程,并且是Client进程获取Server进程中Binder实体的中介。
- Binder驱动 提供一系列底层支持,包括进程之间Binder通信的建立、Binder在进程间的传递,数据在进程间的传递等。
Binder运转机制
- 注册服务 首先Server进程会先向ServiceManager注册服务,日常开发中的各种服务,比如通过context.getSystemService获取的服务,在Android系统启动时,就会在ServiceManager中进行注册。
- 获取服务 Client访问ServiceManager,获取相应服务,通常是真实服务的代理对象。
- 使用服务 Client进程获取到具体Service的引用后(代理对象),通过这个引用向服务端发送请求,服务端执行完成后返回结果。
举个例子
//获取WindowManager服务引用
WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE);
//布局参数layoutParams相关设置略...
View view=LayoutInflater.from(getApplication()).inflate(R.layout.float_layout, null);
//添加view
wm.addView(view, layoutParams);
WindowManager在Android系统启动时已经向ServiceManager注册过了,Client进程(即APP运行的所在进程)通过getSystemService向ServiceManager获取WindowManager的引用(即wm),接着使用WindowManager的服务,即addView方法,该方法运行在systemServer进程中。
值得注意的是,Client进程、Server进程以及ServiceManager之间是不能直接通信的,在上面的架构图中,注册服务、获取服务和使用服务在图中是虚线,这3个步骤本身都是Binder通信,都需要借助binder驱动完成数据的传输。
既然这3个步骤都是Binder通信,那么问题来了,在注册服务和获取服务时,Client进程和Server进程都需要和ServiceManager进行通信,那么他们是如何获取ServiceManager的引用的呢。实际上,在Binder机制中,ServiceManager的句柄号永远是0,也就是说其他进程想要和ServiceManager进行通信的话,直接使用句柄号为0的引用即可,可以理解为ServiceManager对其他进程总是可见的。
使用服务的流程
当然,开发者最容易感知的就是使用服务了,因为注册服务和获取服务都是由系统完成的,使用服务的流程如下图所示。
-
Client进程通过查询ServiceManager获取到目标进程的代理接口,该代理接口中定义的方法与server进程定义的方法是一一对应的。
-
当Client进程发起远程调用请求时,即调用代理接口的方法,代理接口会把Client进程传递过来的参数打包成Parcel对象,并发送给运行在内核中的binder驱动。
-
server会读取binder driver中的请求数据,如果是发送给自己的,解包Parcel对象,执行指定的方法,并将结果返回,返回的结果也需要打包成Parcel对象,由binder驱动返回给代理接口,代理接口再返回给Client进程。
这样就完成了Client进程就完成了一次远程调用,整个过程是同步的,Client进程从开始调用到得到结果会被阻塞,因此对于耗时较长的远程调用,不应放在主线程中。
在Android中使用Binder
虽然说在使用系统服务的时候会使用到Binder机制,但对于开发者而言,只是调了一下Android的API,无法接触到Binder的核心。在平时的开发中,如果需要进程间通信,通常会使用AIDL搭配Service进行,因此可以选择AIDL来分析Binder的工作过程。
新建一个aidl文件 IUserManager.aidl
// IUserManager.aidl
package com.example.kotlinproject.aidl;
// Declare any non-default types here with import statements
interface IUserManager {
int getUserAge(in String userName);
}
创建aidl文件后,Android Studio会在gen目录中自动生成一个同名的java文件,但我的AS没有在gen目录生成,可以通过搜索类IUserManager找到对应的文件,AS生成的代码如下。
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package com.example.kotlinproject.aidl;
// Declare any non-default types here with import statements
public interface IUserManager extends android.os.IInterface
{
/** Default implementation for IUserManager. */
public static class Default implements com.example.kotlinproject.aidl.IUserManager
{
@Override public int getUserAge(java.lang.String userName) throws android.os.RemoteException
{
return 0;
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.kotlinproject.aidl.IUserManager
{
private static final java.lang.String DESCRIPTOR = "com.example.kotlinproject.aidl.IUserManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.kotlinproject.aidl.IUserManager interface,
* generating a proxy if needed.
*/
public static com.example.kotlinproject.aidl.IUserManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.kotlinproject.aidl.IUserManager))) {
return ((com.example.kotlinproject.aidl.IUserManager)iin);
}
return new com.example.kotlinproject.aidl.IUserManager.Stub.Proxy(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
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getUserAge:
{
data.enforceInterface(descriptor);
java.lang.String _arg0;
_arg0 = data.readString();
int _result = this.getUserAge(_arg0);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.example.kotlinproject.aidl.IUserManager
{
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 int getUserAge(java.lang.String userName) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(userName);
boolean _status = mRemote.transact(Stub.TRANSACTION_getUserAge, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getUserAge(userName);
}
_reply.readException();
_result = _reply.readInt();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
public static com.example.kotlinproject.aidl.IUserManager sDefaultImpl;
}
static final int TRANSACTION_getUserAge = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
public static boolean setDefaultImpl(com.example.kotlinproject.aidl.IUserManager impl) {
if (Stub.Proxy.sDefaultImpl == null && impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.example.kotlinproject.aidl.IUserManager getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
public int getUserAge(java.lang.String userName) throws android.os.RemoteException;
}
乍一看,这个自动生成的文件里面的代码很复杂,但只要有耐心去分析,这个类的结构还是很清晰的,可以借助AS中左边菜单栏的Structure工具进行分析。
IUserManager
public interface IUserManager extends android.os.IInterface
先看外层的IUserManager,可以看出,这个文件就是一个接口,并且这个接口继承了IInterface接口。从AS的Structure工具可以看出里面有两个内部类(Default、Stub,其中Default可以忽略)以及一个待实现的方法,这个方法是跟aidl文件相对应的,即aidl文件定义了哪些方法,这里就有相同的方法。既然IUserManager是一个接口,那么它就要被实现,而实现这个接口就要实现它本身定义的方法以及它继承的接口里定义的方法,那么先看一下IInterface中定义了哪些方法。
嗯,可以看到IInterface只有一个asBinder方法待实现,也就是说实现IUserManager,需要实现两个方法,asBinder以及getUserAge,目前只要记住这一点就行。
Defalut类
其实Defalut是可以不用看的,它只是提供了默认的结果,可以看到它只是实现了IUserManager并实现了上面讲的两个方法。
Stub抽象类
public static abstract class Stub extends android.os.Binder implements com.example.kotlinproject.aidl.IUserManager
Stub是一个抽象类,继承自Binder并实现IUserManager,下面是Stub类的结构图。
- DESCRIPTOR
Binder的唯一标识符,一般用当前Binder的全类名表示。
- TRANSACTION_getUserAge
int常量,标识IUserManager中定义的方法,IUserManager有几个方法就有多少个int常量。
- asInterface(IBinder obj)
用于将Binder类转换成接口,因为在进程间传输的是Binder类,要使用服务端的方法的话需要先转换成目标接口。值得注意的是,该方法会区分进程的,如果客户端和服务端位于同一进程,则返回服务端的Stub对象本身,否则返回的是Stub.Proxy对象。开发时在客户端需要调用该方法。
- asBinder()
Stub类实现了IUserManager,上面分析可知需要实现asBinder方法,该方法Stub返回自己,因为Stub继承了Binder。
- public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
该方法运行在服务端的Binder线程池中,当客户端发起远程调用请求时,经过binder驱动发送到服务端,最终会调用该方法进行处理。服务端根据code可以知道客户端请求的目标方法是什么,接着从data中取出客户端传递的参数(如果目标方法需要参数的话),然后执行相应的目标方法,执行完成后把结果写入reply中(如果目标方法有返回值的话)
注意,这里执行目标方法时,调用了this.xxx,但Stub并没有实现这些方法。这是正常的,因为Stub本来就是AS自动生成的代码,怎么可能知道目标方法如何实现呢,因此我们需要把自己实现的方法放到Stub类中,具体做法就是新建一个类继承Stub类并实现在aidl中定义的方法。
Proxy
private static class Proxy implements com.example.kotlinproject.aidl.IUserManager
Proxy是Stub类的私有静态内部类,意味着只有在Stub类中才能创建这个Proxy类,它实现了IUserManager接口,从前面的分析可知它必须实现asBinder以及getUserAge方法,它的类结构图如下图所示。
- Proxy(IBinder)
构造函数,传入一个IBinder对象,在Stub类中的asInterface有使用到(上面分析过如果客户端与服务端不在同一个进程中,那么asInterface返回的就是这个Proxy对象)。
- asBinder
返回创建对象时传入的IBinder对象。
- getInterfaceDescriptor
获取Binder唯一描述符
- getUserAge(String)
- 首先创建了两个Parcel对象, _data 与 _reply,分别代表请求执行的目标方法的参数和返回结果
- 向 _data中写入参数userName
- 调用Binder对象的transact方法,该方法用于向服务端发送请求,执行指定的目标方法,方法中第一个参数表明请求服务端执行getUserAge(String)这个方法,并将 _data 和 _reply传入,服务端会在 _data中取出参数,执行完目标方法后写入 _reply里
- 阻塞等待服务端返回
- 在 _reply 里取出结果并返回
到这里,通过aidl生成的代码已经分析完了,数据的流向如下图所示。
Demo
实际开发中是不用管自动生成的代码的,分析代码是为了理解Binder的机制,以这种角度来看的化实际上不用使用aidl也可以实现进程间的通信,只要模仿自动生成的写一份代码就行了,但没有这个必要,aidl就是为了简化这个过程而存在的工具。
服务端
创建好aidl文件后,就可以使用了。在Android中,使用进程间通信通常都是绑定其他进程的服务,从而获取IBinder对象,再转换成接口,,这样才能使用远程调用。那么,在工程中创建一个Service,为客户端提供服务。
public class UserService extends Service {
public UserService() {
}
@Override
public IBinder onBind(Intent intent) {
return stub;
}
IUserManager.Stub stub = new IUserManager.Stub() {
@Override
public int getUserAge(String userName) throws RemoteException {
if ("songyang".equals(userName)) {
return 22;
} else {
return 0;
}
}
};
}
可以看到这里我通过匿名内部类的方法创建了一个IUserManager.Stub的子类对象stub,并实现了aidl接口中定义的getUserAge的方法,为什么要这样做上面已经分析过了,接着在onBind方法里把这个stub返回。这样,服务端的代码就写好了。
客户端
在客户端,需要创建一个与服务端一模一样的aidl文件,这里的一模一样不仅包括内容,还包括包名,如图,客户端工程的包名是com.example.activitytest,但aidl文件所在的包是要跟服务端的一样的。
同样的,创建好aidl文件后就可以使用了,我们可以在Activity中利用隐式的intent调用bindService方法,从而绑定其他进程的服务。
public class MainActivity extends AppCompatActivity{
private IUserManager mUserManager;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mUserManager = IUserManager.Stub.asInterface(service);
try {
Log.d("songyang", "songyang's age is " + mUserManager.getUserAge("songyang"));
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindUserService();
}
private void bindUserService() {
Intent intent = new Intent();
intent.setComponent(new
ComponentName("com.example.kotlinproject","com.example.kotlinproject.aidl.UserService"));
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
}
可以看到onServiceConnected回调中带有一个IBinder参数,把这个参数通过asInterface转换为接口(如果客户端和服务端在同一进程,则得到的是Stub,否则为Proxy),接着调用远程方法,这样就完成了远程调用啦。