您的位置:首页 > 移动开发 > Android开发

Android IPC机制——Binder详解

2016-02-25 22:33 423 查看
IPC是指Android中的进程间通信,即在不同进程之间传递消息数据,Android中可实现进程通信的方法有很多,比如Intent、ContentProvider、Messenger、Binder、Socket或是利用文件,这些方式各有千秋,都有最适合使用的场景,这次要介绍的是一种安全高效的面向对象式的IPC实现——Binder。

当使用bindService()绑定一个服务时,service会在其onBind()方法中返回一个Binder对象,然后在client的ServiceConnection中获取这个Binder,即可跨进程使用service的方法,接下来我们就来看一看Binder的实现原理。

在Android中,实现Binder很简单,不需要我们去写,只需要写一个aidl文件,在其中写一个接口,声明需要的方法,其他的工作通过编译之后系统会为我们完成,最后生成java文件。Android为我们提供了这这种简单的Binder使用方式,虽然简化了开发,但也一定程度的限制了我们对其工作原理的深入理解,下面就以系统生成的Binder类来讲解一下Binder构造。

本文会分四个个部分来分析Binder:

1.Binder的组成结构

2.Binder的使用方法

3.Binder对象的传递流程

4.Binder对client请求的处理过程

Aidl生成Binder类

首先,创建一个aidl文件,如下:

package com.ipctest.aidl;

interface IUser {
boolean login(String userName,String userPwd);
void logout(String userName);
}


然后编译工程,在AndroidStudio的目录结构下,生成的.java文件在build–generated–source–aidl–debug目录下,我得到的文件经过格式整理如下:

/*
* This file is auto-generated.  DO NOT MODIFY.
* Original file:
*/
package com.ipctest.aidl;
// Declare any non-default types here with import statements

public interface IUser extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.ipctest.aidl.IUser{

private static final java.lang.String DESCRIPTOR = "com.ipctest.aidl.IUser";

/** Construct the stub at attach it to the interface. */
public Stub(){
this.attachInterface(this, DESCRIPTOR);
}

/**

* Cast an IBinder object into an com.ipctest.aidl.IUser interface,
* generating a proxy if needed.
*/
public static com.ipctest.aidl.IUser asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.ipctest.aidl.IUser))) {
return ((com.ipctest.aidl.IUser)iin);
}
return new com.ipctest.aidl.IUser.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{
switch (code){
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_login:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
java.lang.String _arg1;
_arg1 = data.readString();
boolean _result = this.login(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(((_result)?(1):(0)));
return true;
}
case TRANSACTION_logout:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
this.logout(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}

private static class Proxy implements com.ipctest.aidl.IUser{
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 boolean login(java.lang.String userName, java.lang.String userPwd) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
boolean _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(userName);
_data.writeString(userPwd);
mRemote.transact(Stub.TRANSACTION_login, _data, _reply, 0);
_reply.readException();
_result = (0!=_reply.readInt());
}finally {
_reply.recycle();
_data.recycle();
}
return _result;
}

@Override
public void logout(java.lang.String userName) throws android.os.RemoteException{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(userName);
mRemote.transact(Stub.TRANSACTION_logout, _data, _reply, 0);
_reply.readException();
}finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_login = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_logout = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public boolean login(java.lang.String userName, java.lang.String userPwd) throws android.os.RemoteException;
public void logout(java.lang.String userName) throws android.os.RemoteException;
}


Binder类结构

上面就是是系统生成的IUser.java,可能看起来会有些头疼,但结构其实非常简单,上面作为源码参考,下面我将其方法内容省略,将结构分离出来,这个接口继承于IInterface,在其中中实现了一个内部类Stub,Stub又有一个内部类Proxy,代码如下:

package com.ipctest.aidl;
//AIDL文件中定义的IUser接口
public interface IUser extends android.os.IInterface
{
//IUser中的内部类,继承于Binder类并实现了IUser接口,在service中传递的就是这个类
public static abstract class Stub extends android.os.Binder implements com.ipctest.aidl.IUser{
//接口的唯一标识,一般由包名+类名组成
private static final java.lang.String DESCRIPTOR = "com.ipctest.aidl.IUser";

public Stub(){
//在构造方法中将自身接口标识存储起来
this.attachInterface(this, DESCRIPTOR);
}

public static com.ipctest.aidl.IUser asInterface(android.os.IBinder obj)
{
//获取binder对象
//在这里会调用obj.queryLocalInterface(DESCRIPTOR)来获取binder,在service返回Binder时会判断client请求进行处理
//如果请求来自当前线程,queryLocalInterface()会返直接返回构造方法中attachInterface()的binder对象,也就是binder本身
//如果来自其他进程,queryLocalInterface方法直接返回null
//这时就需要创建一个Proxy对象(Stub的内部代理类)供client使用
}

@Override
public android.os.IBinder asBinder()
{
//获取当前Binder
return this;
}

@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException{
//在跨进程请求时被调用,请求先被Proxy对象处理,Proxy将方法参数序列化之后
//和方法编号以及方法参数和用于存储返回值的序列化对象(如果有返回值)一同交给此方法
//然后在这里将参数还原,并调用相应的方法进行处理,最后将返回值序列化后返回给Proxy。
}

//Stub的内部类,当请求来自同一进程时,不会使用,当请求来自另一个进程时,会将client得到的binder包装成它的实例
private static class Proxy implements com.ipctest.aidl.IUser{
private android.os.IBinder mRemote;
//持有一个IBinder,通常就是stub本身
Proxy(android.os.IBinder remote){
mRemote = remote;
}

//获取当前的Proxy对象
@Override
public android.os.IBinder asBinder(){
return mRemote;
}

public java.lang.String getInterfaceDescriptor(){
return DESCRIPTOR;
}

@Override
public boolean login(java.lang.String userName, java.lang.String userPwd) throws android.os.RemoteException{
//login方法请求封装
}

@Override
public void logout(java.lang.String userName) throws android.os.RemoteException{
//logout方法请求封装
}
}
//IUser借口中两个方法的code
static final int TRANSACTION_login = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_logout = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
//待实现的方法
public boolean login(java.lang.String userName, java.lang.String userPwd) throws android.os.RemoteException;
public void logout(java.lang.String userName) throws android.os.RemoteException;
}


Binder使用方法

上面就是IUser的结构,下面通过一个例子先演示一下Binder的实现方式:

//首先创建Service,还是使用前面的aidl生成的Binder
public class MyService extends Service{
private final String TAG="BinderTest";
//创建Binder对象,实现两个方法
private Binder mBinder= new IUser.Stub() {
@Override
public boolean login(String userName, String userPwd) throws RemoteException {
Log.d(TAG,userName+"   登录成功!");
return true;
}

@Override
public void logout(String userName) throws RemoteException {
Log.d(TAG,userName+"   退出成功!");
}
};

//在onBind中返回Binder对象
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}


//然后在Activity中启动服务
public class MainActivity extends AppCompatActivity {
private final String TAG = "MyServiceTest";
private EditText mUserNameEdt, mUserPwdEdt;
private Button mLoginBtn, mLogoutBtn;
IUser mUserBinder;

//创建ServicerConnection
ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected");
mUserBinder = IUser.Stub.asInterface(service);
}

@Override
public void onServiceDisconnected(ComponentName name) {

}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "oncreate");
setContentView(R.layout.activity_main);
initLayout();
//绑定服务
Intent intent = new Intent(MainActivity.this, MyService.class);
bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
}

private void initLayout() {
mUserNameEdt = (EditText) this.findViewById(R.id.user_name_edt);
mUserPwdEdt = (EditText) this.findViewById(R.id.user_pwd_edt);
mLoginBtn = (Button) this.findViewById(R.id.login_btn);
mLogoutBtn = (Button) this.findViewById(R.id.logout_btn);
//点击登录按钮访问Service的Binder的login方法
mLoginBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String userName = mUserNameEdt.getText().toString();
String userPwd = mUserPwdEdt.getText().toString();
if (mUserBinder != null) {
try {
mUserBinder.login(userName, userPwd);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
});

//点击退出按钮访问Service的Binder的logout方法
mLogoutBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String userName = mUserNameEdt.getText().toString();
if (mUserBinder != null) {
try {
mUserBinder.logout(userName);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
});
}


点击按钮结果:



可以发现Binder中的方法被执行了,这是同一进程的运行结果,如果将service运行在独立线程中又会如何呢?

//在Manifest中为MyService设置一个进程
<service
android:name=".MyService"
android:process=":remote" />


运行结果:





可以看到,在将MyService的进程中login()和logout()被执行了,也就是说点击按钮后成功的跨进程调用了MyService的方法

Binder对象的传递过程

接下来详细解析Binder从Service到client的传递过程

首先看上边的Demo,在MyService中实现了对象

private Binder mBinder= new IUser.Stub() {
@Override
public boolean login(String userName, String userPwd) throws RemoteException {
Log.d(TAG,userName+"   登录成功!");
return true;
}

@Override
public void logout(String userName) throws RemoteException {
Log.d(TAG,userName+"   退出成功!");
}
};
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}


IUser.Stub,在文章开头的源码中可以找到,它是一个抽象类,继承与Binder类,这个对象在service与MyService通信使用的Binder,同时它实现了我们定义的Aidl的IUser接口,也就是说这个Binder拥有了我们的自定义方法(在Stub中只是将IUser接口的方法继承了下来,但并没有实现,直到在我们创建这个实例时手动实现了方法),然后通过Binder的onTransact()的code参数将client的请求类型与本地的方法绑定,在将此Binder对象返回给client,单从应用层来看,如此便将方法暴露给了client。(后面注意Stub类是我们自定义的Binder类,后面说的binder对象便是Stub对象)

再看client代码:

ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected");
mUserBinder = IUser.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {

}
};

bindService(intent,mServiceConnection,BIND_AUTO_CREATE);


在client中,创建了一个ServiceConection对象,并在bindService()启动服务时进行绑定,当服务启动ServiceConnection连接成功时:

1、service的onBind()方法被执行,返回我们我们创建的Binder对象(mBinder)

2、clientServiceConection对象的onServiceConnected(ComponentName name, IBinder service)方法被执行,参数service用来接收service返回的Binder,然后在下面这一句代码将得到的Binder转为可识别的对象(asInterface如果是同进程直接返回收到的binder,如果是跨进程会返回一个Binder的内部代理类Proxy的实例),这样client就得到了在Service中创建的Binder,通过aidl的IUser引用即可使用Binder的方法。

mUserBinder = IUser.Stub.asInterface(service);

那么IUser.Stub.asInterface(service),这个方法是到底是如何处理收到的binder对象的呢?来看源码:

public static com.ipctest.aidl.IUser asInterface(android.os.IBinder obj){
/*obj为service的binder对象,先做非空判断*/
if ((obj==null)) {
return null;
}
/*这里验证binder对象中DESCRIPTOR是否合法,是直接返回binder,否返null(详解见下面的源码)*/
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
/*如果不为null,将binder对象转换为我们定义的IUser类型,返回给client的ServiceConnection*/
if (((iin!=null)&&(iin instanceof com.ipctest.aidl.IUser))) {
return ((com.ipctest.aidl.IUser)iin);
}
/*如果为null代表为跨进程请求,创建一个Proxy代理对象(Stub的内部类,后面详解)*/
return new com.ipctest.aidl.IUser.Stub.Proxy(obj);
}


上面可以看到asInterface将binder对象进行了处理,如果请求为当前进程,那么同进程可共享内存,即可直接使用service返回的binder对象,但如果请求是跨进程,则将binder对象包装为一个代理对象,返回给client,到这里Binder的传递流程就通了,但如何区分是当前进程还是跨进程呢?关键就在Stub的queryLocalInterface的方法,我们继续深入,这个方法是Stub的父类Binder中的方法,我们看源码:

//在Stub的asInterface中调用了这个方法,DESCRIPTOR为Stub的接口唯一标识,默认为包名路径
obj.queryLocalInterface(DESCRIPTOR);

//queryLocalInterface源码
public IInterface queryLocalInterface(String descriptor) {
//在这里先将传入的接口标识和Binder的字段mDescriptor对比是否一样,是返回mOwner字段,否返回null
//那mDescriptor和mOwner两个字段又代表了什么呢,我们找到其获取值得地方,见下一个方法
if (mDescriptor.equals(descriptor)) {
return mOwner;
}
return null;
}

//在看这两个字段的赋值之前,先看看他们的类型,这是这两个字段的声明
//可以看到mOwner是一个IInterface 接口引用,也就是说他可以接受任何类型的对象实例
private IInterface mOwner;
//mDescriptor为一个字符串,然后看赋值
private String mDescriptor;

//在这里我们发现,attachInterface方法中对mOwner和mDescriptor字段进行了赋值
//既如此,那么我们找到attachInterface方法的调用者即可知道这两个字段的内容,看下一个方法
public void attachInterface(IInterface owner, String descriptor) {
mOwner = owner;
mDescriptor = descriptor;
}

//仔细看了Stub结构的读者应该可以发现,attachInterface方法在Stub的构造方法中就被调用了
public Stub(){
//这里传入的参数为this为我们传递的stub对象本身
//DESCRIPTOR为Stub的接口标识,在Stub源码可以看到
this.attachInterface(this, DESCRIPTOR);
}


也就是说在binder对象被创建时,使用attachInterface(this, DESCRIPTOR)将其自身和接口标识存入mOwner和mDescriptor字段

在client接收到这个对象后,调用queryLocalInterface(DESCRIPTOR)方法,将Stub类的DESCRIPTOR字段与mDescriptor比较,如果相同表示client请求来自同一进程,返回mOwner字段,否则表示是跨进程请求,返回null,那么就有一个问题:

Service返回的是同一个Binder对象,且这个对象在构造时就已经为mDescriptor字段赋值,那么为什么在同一进程的client在进行mDescriptor.equals(descriptor)比较的时候是为true成立的,而client在另一个进程时这个条件就为false了呢?

这个就涉及到更底层的知识了,从系统的角度来看,client得到的binder对象引用并不是由service直接交付的,而是通过Binder驱动: 当我们的client需要serivice中binder对象的引用而又不在同一进程时,service首先会将本地内存中binder对象的名字通过处于内核的Binder驱动交给ServiceManagerServiceManager将binder的引用存储起来,在client中通过binder的名字来访问ServiceManager中存储的对binder对象的引用,然后Binder驱动会为client也创建一个Binder对象,不同的是这个对象并不是一个Binder实体,而是对service中binder的方法调用请求的封装(调用通过从ServiceManager中得到的binder引用)

那么到这里就可以知道,之所以mDescriptor.equals(descriptor)在跨进程的时候会不成立,是因为在Binder驱动为client创建binder对象时,这个对象只是一个对service中的binder实体各种业务请求的封装,而不是一个真正的binder实体

想要深入理解这个部分,可以看看:/article/2095914.html

现在来整理一下:

1、首先在binder对象被创建时,在构造方法中调用attachInterface(this, DESCRIPTOR)将其自身和接口标识存入mOwner和mDescriptor字段

2、Service 的onBinder()返回binder对象,Bidner驱动创建mRemote交给client,client得到binder对象

3、client进行请求方式判断(同进程或跨进程),是同一个进程直接返回binder对象,否则返回代理对象

4、client使用service业务

client拿到binder对象的过程就到这

Binder 请求处理过程

先说service和client在同一进程的情况:同进程内存是可以共享的,所以前面解释过当请求来自同一进程时,client得到的binder就是我们创建的mBinder对象,所以我们调用其方法就是常规的方法调用,

而service和client不在同一进程时,就产生了跨进程的问题,我们知道,不同进程的内存是不可共享的,一个新的进程甚至会导致Application和各个静态变量的重复创建,所以我们就无法直接对binder的方法进行调用,这时就需要通过Binder驱动去访问seriver中的binder。

前面说了,client中使用的binder对象是Binder驱动为client创建的一个“对service中binder的方法调用请求的封装”,那这个调用请求是如何实现的呢?前面讲解了当client请求来自跨进程时,会创建一个Stub中的Proxy类的实例,我们在来看看这个Proxy类的源码,在前面的源码中可以看到Proxy类同样实现了IUser接口,先看看构造方法

Proxy(android.os.IBinder remote){
mRemote = remote;
}


在Stub的asInterface()方法中有这句代码,就是前面说的当请求为跨进程时创建Proxy的对象

return new sikang_demo.ipctest.IUser.Stub.Proxy(obj);


可以看到这里将obj作为构造参数,记录在了Proxy对象中,也就是说它持有了service的service引用,然后再看源码

@Override
public boolean login(java.lang.String userName, java.lang.String userPwd) throws android.os.RemoteException
{
/*用于存储方法参数和返回值*/
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
boolean _result;
try {
/*写入接口标识、和binder中login()方法需要的参数userName,和userPwd*/
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(userName);
_data.writeString(userPwd);
/*调用mRemote的transact方法申请serive业务*/
mRemote.transact(Stub.TRANSACTION_login, _data, _reply, 0);
/*得到返回值*/
_reply.readException();
_result = (0!=_reply.readInt());
}finally {
_reply.recycle();
_data.recycle();
}
/*将结果反馈给客户端*/
return _result;
}

@Override
public void logout(java.lang.String userName) throws android.os.RemoteException{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(userName);
mRemote.transact(Stub.TRANSACTION_logout, _data, _reply, 0);
_reply.readException();
}finally {
_reply.recycle();
_data.recycle();
}
}


这里实现了IUser接口的两个方法,在跨进程的客户端请求binder方法业务时,直接与客户端接触的就是这里的方法,我们看看方法的内容,方法的处理是一样的,这里根据login方法来讲解

首先在方法开始创建了两个Parcel对象,我们知道Parcel是Java中序列化的一种实现,在跨进程通信时,传输的数据必须可序列化,这里这两个Parcel对象 _data_reply分别用于保存方法参数和接收返回值,可以看到在_data中写入了一个binder的接口标识,和login方法需要的两个参数,然后调用了

mRemote.transact(Stub.TRANSACTION_logout, _data, _reply, 0);


mRemote为Proxy被创建时传入的binder引用,先来看看这个方法几个参数的含义:

public final boolean transact(int code, Parcel data, Parcel reply, int flags)

code:请求方法的编号,当这个请求被service的binder收到时,就是通过这个参数来确定客户端请求的是哪个方法。

data:请求方法需要的参数

reply:用于接收方法的返回值

flags:一般用不到这个参数

data和reply很好理解,但这个code是什么呢?先看看Stub类的最下面有这么两句代码

static final int TRANSACTION_login = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_logout = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);


这就是我们在IUser接口中定义的两个方法的code,由此可知,binder被创建时,会为它从接口继承来的每个方法都创建一个唯一的code,用来为方法编号,当client需要请求方法时,只需要向上面一样,传入一个方法code,及这个方法的参数和返回值保存者,就可以实现对指定方法的调用。

现在知道了client是这么请求的,那service又是如何响应的呢?看Stub中的onTransact类,

@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_login:
{
/**取出方法参数*/
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
java.lang.String _arg1;
_arg1 = data.readString();
/**调用本地方法,传入参数*/
boolean _result = this.login(_arg0, _arg1);
reply.writeNoException();
/**将返回值写入reply对象*/
reply.writeInt(((_result)?(1):(0)));
return true;
}
case TRANSACTION_logout:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
this.logout(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}


onTransact()方法就是service的binder实体对client请求的响应方法,可以看到onTransact()的参数和client中调用的transact()方法参数相同,在client发出请求之后,Binder驱动 将这个请求通过ServiceManager提供的binder引用将请求转到binder的onTransact()方法中,如此service便受到了client的请求,然后在看看service是这么处理这个请求的

还是看login方法,看case TRANSACTION_login 中的处理:

首先取出了client写入到data中的参数,然后调用了login方法,最后将返回值写入了client提供的reply对象中,这时client就可以从reply中读取返回结果了

这就是Binder的请求处理过程,Binder就介绍到这,如果有什么考虑不周,大家可以帮忙提出来
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: