1. Binder简介
1.1 What is Binder?
Android Dev Doc:Base class for a remotable object, the core part of a lightweight remote procedure call mechanism defined by
IBinder
. This class is an implementation of IBinder that provides standard local implementation of such an object.
Binder是一个远程对象的基类,是一个轻量级远程通信机制的核心部分,通过IBinder定义。Binder类实现IBinder接口,并通过提供标准的本地接口对象实现IBinder。
我们以前也接触过一些IPC方式,比如:管道(pipe)、有名管道(FIFO)、信号(signal)、消息队列、共享内存、套接字(socket)等,但是Android系统并没有采取这些方式,而是使用Binder完成IPC。
1.2 why is Binder?
由于移动设备的特殊性,在IPC要尽量满足两个方面的要求:安全性高、传输性能高。
首先从传输性能上看:
socket传输效率低,开销大,因此主要还是用于跨网络的进程间通信。消息队列和管道采用的是存储转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。
共享内存的方式虽然不用数据拷贝,但是控制使用起来很麻烦,大家如果有兴趣的话可以看下Android的匿名共享内存方法,其实是很复杂的,罗升阳的
Android系统匿名共享内存Ashmem(Anonymous Shared Memory)简要介绍和学习计划中有详细介绍。
表1 各种IPC方式数据拷贝次数
IPC | 数据拷贝次数 |
共享内存 | 0 |
Binder | 1 |
Socket/管道/消息队列 |
2
|
其次从安全性上看:
传统的IPC其实是没有任何安全措施,完全依赖上层协议来确保,其接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份。但是Binder会为发送方添加UID/PID身份(由系统添加),安全性高。
1.3 Binder的注意事项
有一点我们需要牢记在心:当我们的进程被“Kill”,那么就需要在该进程重启时重新创建一个Binder对象并re-attach到该进程。例如,当你如果在Activity里面使用Binder,如果你的Activity未启动,那么它就随意可能会系统回收。当Activity重建时,你需要重建一个新的Binder对象并放在正确的地方。当然你也需要注意你的进程因其他原因而启动(比如收到广播),那么这种情况activity不会重建也就不会跑到创建Binder对象的代码了。
1.4 AIDL
如果大家有兴趣阅读Android源码的话,就会发现Android FrameWork层存在着很多Binder机制。因此理解Binder的工作原理是很重要的。
为了能让咱们程序员能很简单地使用Binder,Android为我们提供了一个非常简单地使用Binder的工具,AIDL。那么如何使用AIDL呢?请参考Android官方文档:
http://developer.android.com/guide/components/aidl.html
。
这里主要分析下,AIDL文件自动生成的JAVA文件。aidl文件名为:IAidlCall.aidl,代码如下。
package com.example.aidldemo;
interface IAidlCall{
String getName();
}
自动生成的IAidlCall.java,代码如下。
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: /Users/chenjiawei/Documents/AndroidSpace/ClientDemo/src/com/example/aidldemo/IAidlCall.aidl
*/
package com.example.aidldemo;
public interface IAidlCall extends android.os.IInterface {
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements
com.example.aidldemo.IAidlCall {
// Binder的唯一标示,一般用当前Binder的类名来标示
private static final java.lang.String DESCRIPTOR = "com.example.aidldemo.IAidlCall";
/** Construct the stub at attach it to the interface. */
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.aidldemo.IAidlCall
* interface, generating a proxy if needed.
*/
public static com.example.aidldemo.IAidlCall asInterface(
android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.example.aidldemo.IAidlCall))) {
return ((com.example.aidldemo.IAidlCall) iin);
}
return new com.example.aidldemo.IAidlCall.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_getName: {
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.getName();
reply.writeNoException();
reply.writeString(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.example.aidldemo.IAidlCall {
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.lang.String getName() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public java.lang.String getName() throws android.os.RemoteException;
}
/**
* Base class for Binder interfaces. When defining a new interface,
* you must derive it from IInterface.
*/
public interface IInterface
{
/**
* Retrieve the Binder object associated with this interface.
* You must use this instead of a plain cast, so that proxy objects
* can return the correct result.
*/
public IBinder asBinder();
}
IAidlCall接口中声明了一个静态内部抽象类Stub,该类继承Binder类并实现IAidlCall接口。因此Stub其实就是一个Binder类。
在我们实际的使用AIDL过程中,客户端和服务端都要与Stub打交道。在服务端中,我们需要实现Stub类,代码如下。
public class MyServiceImpl extends IAidlCall.Stub {
@Override
public String getName() throws RemoteException {
return "Jack";
}
}
在客户端中,我们需要调用Stub类中的asInterface方法来得到IAidlCall对象,代码如下。
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// TODO Auto-generated method stub
call = IAidlCall.Stub.asInterface(service);
try {
Toast.makeText(getApplicationContext(), call.getName(),
Toast.LENGTH_LONG).show();
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
接着我们看下asInterface方法中的代码,看完该方法我们就可以了解IAidlCall.java工作流程。
/**
* Cast an IBinder object into an com.example.aidldemo.IAidlCall
* interface, generating a proxy if needed.
*/
public static com.example.aidldemo.IAidlCall asInterface(android.os.IBinder obj) {
//首先判断IBinder是否为空,因为Binder可以“死亡”,前面的有提过
if ((obj == null)) {
return null;
}
//这里queryLocalInterface方法得到IInterface对象,这是通过attachInterface赋值过去的
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
//如果iin不为空并且是IAidlCall实例则将其强转成IAidlCall对象并返回
if (((iin != null) && (iin instanceof com.example.aidldemo.IAidlCall))){
return ((com.example.aidldemo.IAidlCall) iin);
}
//如果lin为空或者不是IAidlCall实例,则返回Proxy对象
return new com.example.aidldemo.IAidlCall.Stub.Proxy(obj);
}
这里我们可以看到一个Proxy类,该类是Stub里的静态类且继承于
IAidlCall
,在Proxy类中有真正完成getName()方法实现的过程。实现getName方法的代码如下。
@Override
public java.lang.String getName() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
这里需要注意的是
_data、_reply两个Parcel对象和
transact方法。
_data是入参序列,如果调用的远程方法需要入参就通过
_data的
writeInt、
writeLong等方法将参数写入
_data中。
_reply是方法的返回值序列,如果调用的远程方法有返回值,就通过
_reply的
readInt、
readLong等方法将参数读到
_reply
中。
这里最主要的方法是
transact
(
int
code
, Parcel
data
, Parcel
reply,
int
flags
)
方法。
1)第一个参数code代表的是方法的代码,例如getName()的方法code为,
static
final
int
TRANSACTION_getName
= (android.os.IBinder.
FIRST_CALL_TRANSACTION + 0);
知道code就能知道调用的是哪个方法。
2)第二个参数是指远程方法的入参。
3)第三个参数是指远程方法的返回值。
4)第四个参数是指额外操作标志,0代表的是普通的RPC,其他值代表的是one-way RPC。
因此我们,可以通过模仿写出transact方法所需要的参数,通过
RemoteObject(Binder)调用其transact方法就可以完成远程方法的调用,最后会给出相应地例子。
Binder中transact方法实现代码如下。
/**
* Default implementation rewinds the parcels and calls onTransact. On
* the remote side, transact calls into the binder to do the IPC.
*/
public final boolean transact(int code, Parcel data, Parcel reply,
int flags) throws RemoteException {
if (false) Log.v("Binder", "Transact: " + code + " to " + this);
if (data != null) {
data.setDataPosition(0);
}
boolean r = onTransact(code, data, reply, flags);
if (reply != null) {
reply.setDataPosition(0);
}
return r;
}
从该代码中我们可以看到调用了
onTransact方法,在该方法中会把远程方法返回值写入到
_reply
中。在
IAidlCall接口中的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_getName: {
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.getName();
reply.writeNoException();
// 这里将getName的返回值写入到reply中
reply.writeString(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
至此,我们分析完了
IAidlCall接口类及其工作过程。其实我们也可以完全自己动手写出
IAidlCall.java,在《Android开发艺术探索》书中就有给出相应地例子。下面可以看看AIDL的工作图。
这里有两点需要注意:
首先,当客户端发起远程请求的时候,由于当前进程会被挂起直到服务端进程返回数据,所以如果一个远程方法是很耗时的,那么不能在UI线程中发起此远程请求;其次,由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了。
通过以上介绍,可以大致了解了Binder的工作方式,后续还将继续深入了解,当然也不会太深,都是结合《Android开发艺术探索》和工作上所碰到的问题进行概述。