Android(安卓)IPC机制——Binder详解
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 statementspublic 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生成的Binderpublic 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设置一个进程 ".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驱动交给ServiceManager,ServiceManager将binder的引用存储起来,在client中通过binder的名字来访问ServiceManager中存储的对binder对象的引用,然后Binder驱动会为client也创建一个Binder对象,不同的是这个对象并不是一个Binder实体,而是对service中binder的方法调用请求的封装(调用通过从ServiceManager中得到的binder引用)
那么到这里就可以知道,之所以mDescriptor.equals(descriptor)在跨进程的时候会不成立,是因为在Binder驱动为client创建binder对象时,这个对象只是一个对service中的binder实体各种业务请求的封装,而不是一个真正的binder实体
想要深入理解这个部分,可以看看:http://blog.csdn.net/universus/article/details/6211589
现在来整理一下:
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就介绍到这,如果有什么考虑不周,大家可以帮忙提出来
更多相关文章
- Android开发 HTTP 发送 Post 与 Get 请求
- 史上最全的Android面试题集锦
- Android(安卓)getDimensionPixelSize, 代码中设置字体大小,读xml
- Android(安卓)IPC原理分析小结
- Android中ExpandableListView的使用(一)
- Android,谁动了我的内存(1)
- 浅谈Java中Collections.sort对List排序的两种方法
- 类和 Json对象
- Python list sort方法的具体使用