当一个Android App存在某个不需要UI的后台运行需求时,或者是因为内存占用需要采用多进程方案时,我们免不了与多进程打交道。必不可少的,需要考虑Binder在其中如何实现。
最常见的Binder实现当然是AIDL,然而Binder的实现绝不仅仅只有AIDL一种方式,如果止步于写.aidl,那么对于Binder,对于Android整体的跨进程传输过程的理解都只能流于形式。
但是想理解Binder不是一件容易的事情,Binder的概念涉及太多知识点,遍观各大论坛上关于Binder的博客或是文章,作者多半已经默认阅读者具备了必要的学习基础。这对于零基础的同学肿么办,我罗列出了几个自己学习中觉得十分重要的知识点,如果希望能够彻底的理解Binder JAVA层概念,那么务必要明了这些知识点。希望对那些初学者朋友有一定帮助。
AIDL是Android为了在不同进程中实现Binder接口的一套专用接口语言。
在维基百科上对于接口语言的定义是这样的
接口描述语言(Interface description language,缩写IDL),是用来描述软件组件接口的一种计算机语言。IDL通过一种中立的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信交流;比如,一个组件用C++写成,另一个组件用Java写成。
接口语言通常用来描述两种不同对象之间共同一套通信协议,放在Android概念里,一般指的是两个不同进程之间需要通信时必须遵守的一套标准。
Binder的实质仍然是IPC。
通常我们所说的IPC,指的是以某种标准所做的进程间通信过程(inter-process communication)。我们知道,Android的每一个应用可以视作是跑在Davlk虚拟机上的独立进程。而Linux内核出于对进程安全的考虑,要求不同UID/GID的进程不能访问互相访问彼此的资源。因此一般来讲,每个Android应用,或者说每个独立进程的组件都不能像本地方法一样调用其他进程方法。
基于这个原因,Linux内核支持若干种进程间通信机制,为的是在维护进程安全性的同时还能提供针对不同场景的通信手段。而在Android Binder用到的是叫做匿名共享内存(Anonymous Shared Memory)的方式。Binder的实质就是通过这种IPC方式来完成不同进程之间的通信的。
Binder的实现是一个典型的C/S结构。
C/S可以简单的理解为系统为了负载均衡,将一个大任务拆分为多个子任务,并分发给不同的终端结构来处理,以达到减少负荷提高响应速度的目的。在Android的环境中,在分布式系统中,终端结构可以是计算机与服务器,而在Android系统中,则指的是不同的进程实体。理解了C/S结构的一些特征,对Binder的消息握手,传输模式等等行为会有更深刻的理解。
终于说到了Binder。
对于Binder的解释,最官方的莫过于developerAndroid对于Service使用这一章的说明。如果详细的读过这篇文档,我想大部分人能够对Binder的使用,特别是如何通过AIDL来实现Binder接口的操作,有比较清楚的认识。
但是Binder究竟是什么?它是如何实现的?
这里十分推荐老罗关于Binder学习计划这个系列的文档,对于Binder的整体运行机制有了十分清晰的描述,如果你需要整体全面的理解Binder,那么阅读源代码的同时,这套文档一定可以帮助你很多。
弄清楚以上几个概念后,我们可以开始讨论Binder的实现了。
Binder的实现有很多种,最常见的方式是通过AIDL来实现,相信做过跨进程访问Service的同学也肯定写过.aidl。除此之外,Android还支持通过Message方式和直接继承Binder类的方式来完成Binder实现。
而我要给大家介绍的,就是最后一种方式。
我以一个最简单的功能为例——控制并打印不同进程的pid,来介绍一个典型的Binder实现过程。
为了在同一个项目中模拟多进程情形,我将Service组件设置为其他的进程
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".BinderService"
android:process=":other"
android:enabled="true"
android:exported="true" >
</service>
在我们的模拟代码中,Activity与Service绑定之后可以在onServiceConnection回调中获取一个Binder实例,通过对这个对象向下转型得到指定Interface的接口对象,从而调用Service中实现的具体的LogService方法。
真实的情况虽然没有所描述的这么简单,但也与之相差不远,流程图如下:
Activity和Servie的代码实现如下:
Activity端
void onServiceConnected(ComponentName name, IBinder service) {
mService = StubService.asInterface(service);
Log.d(TAG, "pid=" + android.os.Process.myPid());
mService.LogService();
}
@Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindService(new Intent(this, BinderService.class), serviceConnection, BIND_AUTO_CREATE);
}
...
}
Service端实现
public class BinderService extends Service {
private static final String TAG = "BinderService";
//implement the interface
private StubService mBinder = new StubService() {
@Override
public void LogService() {
Log.d(TAG, "pid=" + android.os.Process.myPid());
}
};
//expose the interface to client
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
简单的描述就是。
- Actiivty尝试去bind一个Service。
- bind成功之后,Activity可以得到一个Binder对象。
- 通过这个Binder对象,Activity能够调用被bind的那个Service里实现的具体方法,也就是实现了远程调用Service的目的。
而这里有几个问题:
1. Activity获取的Binder对象本质是什么?
2. 为什么可以通过这个Binder对象远程调用Service方法?
3. 我们如何区分不同进程中的实例对象?
这几个问题可以从具体的实现代码中得到答案。
在我们的demo中,Activity代表的是C/S模型中的Client端进程,Binder结构实现的过程之一,就是会交给这个Client进程一个它的Proxy实现,也就是我们在onServiceConnection回调中所得到的Binder实例。
具体怎么生成Proxy实例的呢?从代码中可以看到:
public interface IMyTransInterface extends IInterface {
public void LogService();
}
最开始,我们必须创建一个继承子IInterface的接口,并在这个接口中声明我们在Client进程中要调用的,Service进程中会实现的方法。
之后我们在Proxy类中就可以实现这个接口了
public class ProxyClient implements IMyTransInterface {
public static final int TRANSACTION_LogService = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
private IBinder mRemote;
ProxyClient(IBinder mRemote) {
this.mRemote = mRemote;
}
@Override
public IBinder asBinder() {
return mRemote;
}
@Override
public void LogService() {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
try {
_data.writeInterfaceToken(StubService.DESCRIPTOR);
mRemote.transact(TRANSACTION_LogService, _data, _reply, 0);
_reply.readException();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
这个Proxy的实现,就是Activity所得到的向下转型Binder对象。它之所以可以向下转型从代码中看到,是因为它实现了IMyTaransInterface接口,也就是支持我们自定义的一套接口定义。在我们用AIDL实现的场合中,这套细节是隐藏的,但是我们仍然知道我们所以能够自定义的接口,是因为在.aidl文件中声明的缘故。
Proxy部分的代码中,完成了两个非常重要的功能。
- 通过asBinder方法返回一个IBinder接口实现。这个返回的mRemote对象可以看到是在构造函数中传入的,至于什么时候会构造Proxy对象稍后会介绍。但是需要知道通过asBinder能够得到一个远端对象。我们在想的远一点,从字面意义上来理解,这个远端指的应该是什么呢?
- 实现了IMyTransInterface声明的接口方法LogService。这是Binder结构中至关重要的一个环节。
LogService在Proxy中的实现并没有真正的逻辑结构,只是机械的将对应参数序列化后,并调用远端对象的transact方法写入到某个缓存区中,之后读取可能存在的返回值。
而我们在Activity中,也就是Client进程中实际调用的也就是这个方法,所谓的远程调用Service进程的方法不过只是从宏观得到的结论而已,此时真正完成的,只是将数据写入缓存区并得到回传的结果而已。
这一套实现机制,从模型上看,就是典型的C/S结构,Client端发送请求并等待计算结果,并不关心实现的过程。而真正实现的细节完全交给Server来完成。而真正的实现细节,也即是我们写入的缓存后的处理,最终也会由匿名共享内存机制来完成。
如果你对Binder代理的设置已经理解的话,那么对服务器端的实现很快就能明白——因为这两者基本是对称的。
还是先把代码贴出来:
public abstract class StubService extends Binder implements IMyTransInterface {
public static final java.lang.String DESCRIPTOR = "com.guiyutest.bindertest.IMyTrasInterfacke";
public abstract void LogService();
public StubService() {
this.attachInterface(this, DESCRIPTOR);
}
public static IMyTransInterface asInterface(IBinder obj) {
if (obj == null) {
return null;
}
IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if ((iin != null) && (iin instanceof IMyTransInterface)) {
return (IMyTransInterface) iin;
}
return new ProxyClient(obj);
}
@Override
public IBinder asBinder() {
return this;
}
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code) {
case INTERFACE_TRANSACTION:
reply.writeString(DESCRIPTOR);
return true;
case ProxyClient.TRANSACTION_LogService:
data.enforceInterface(DESCRIPTOR);
this.LogService();
reply.writeNoException();
return true;
}
return super.onTransact(code, data, reply, flags);
}
}
从代码结构上看,Service端的实现包括三个主要部分,构造函数,asInterface方法以及onTrasnact回调方法。
值得一提的是后两者:
Service端的代码,故名思议,应该是可以实现C/S模型中Server端具体业务逻辑的代码。通过匿名共享内存机制,收到Proxy进程打包发来的数据并且加以处理,然后返回结果。期间无需关注数据的来源以及调用者的信息。
说了这么多,细心的你肯定已经发现,我所罗列出的这一套代码逻辑,正是AIDL在gen中生成的.java文件机制。唯一的不同是,懒惰的Google只用了一个文件,而我将它们拆分成为了本该属于不同进程的三个文件。
在具体的实现细节中,我们也可以看到Google的用苦良心,Binder的Service端实现其实完全可以写在Stub中,只不过为了为了重用性考虑以及更好的便于开发者使用,Google把这个功能加以抽象包装,把底层的功能再次封装,仅仅只暴露出用以实现具体逻辑的抽象类给了开发者,然我们可以心无旁骛的专注于业务逻辑,而对这整整一套传输过程都无需关注,实在是居功甚伟。
Demo代码下载地址:
BinderTest
—–最后的分割线——
这是我用Markdown写的第一篇文档,不得不赞一句,真是好东西。
原文地址:http://blog.csdn.net/guiyu_1985/article/details/43835735