接着上篇binder简要介绍,我们来分析binder机制的设计。binder主要框架分为三个部分:服务端,binder驱动,客户端。
binder在android最常见的使用场景就是一个程序的activity与系统service进行交互。比如我通过wifi的service来获取wifi控制代理对象,来对wifi进行相关的操作。注意:这里的系统service是指System server,而不是sdk 中的Service类。
从Linux空间上来看,Activity,系统service它们都分属不同的进程,不同进程之间的数据交换就是涉及到了IPC通信。而如果我们从开发者调用角度来看,我们看不到进程的概念。从公共对象请求代理的高度来看Binder,我们会惊异于这种设计思路。Binder相对于Activtiy,service来说是一个很低层的概念。当设计android程序涉及到IPC,我们无需考虑底层的实现细节,而去只关心怎么去获取相关服务并通信,这也使我们更专注于软件的开发,而非传统基于C/S架构去思考它们之间的数据是如何去实现交换的。
在用户空间,我们需要做的就是去请求相关有能力的服务对象,不必去了解这个通讯是如何完成。这种设计架构给不仅解决了通讯,引入了一种新的设计理念,也与java面向对象的开发思想契合在一起。这里,我们看不到binder,我们感觉就像是客户端直接身服务端请求,然后通过服务端的一个代理对象处理相关工作。Activity与service之间仿佛是一种很直接的,自然的通信。
对于android外部性空间来说,我们不知道服务对象在哪里,我们只需通过公共代理对象去请求服务。Android系统中,系统级的service都是由serviceManager来管理。借着serviceManger就可以获取service的对象引用。
先了解下serviceManger,它本身也是一个service,但它管理着系统其它的service。Framework提供了一个系统函数BinderInternal.getContextObject(),可以获取该Service对应的Binder引用。通过这个静态函数返回的ServiceManager提供的方法又可以获取其它系统Service的Binder引用。所以serviceManager是整个系统service的总管,也是系统的一个核心对象,它是开机就自启动的。其它的service都要向它进行注册并保管引用,这样保证所有的服务都可以通过servericeManger获取到引用。这种设计模式的一个好处就是仅暴露一个Binder引用,而其它的系统服务可以隐藏起来,从而有助于系统服务的扩展,以及调用系统服务的安全检查 。现在我们来看下serviceManger是怎样处理service的注册和查询的。我先看下serviceManger源码:
在源码里有一个HashMap,HashMap里保存着系统service的名字,和引用。
private static HashMap sCache = new HashMap();
继续看源码了解客户端向服务的请求是怎样完成的:在客户端通过ServiceManager类getService(String name)方法传递一个需要的服务,然后ServcieManager在sCache的HashMap中去查询与name对应的service,然后再将这个service的IBinder引用返回给客户端。
public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
if (service != null) {
return service;
} else {
return getIServiceManager().getService(name);
}
} catch (RemoteException e) {
Log.e(TAG, "error in getService", e);
}
return null;
}
与此同时,ServiceManager还提供了addService,checkService两个重要方法,用来维护sCache列表登记的Service的名称以及引用。我们用一个图来描述这整个过程
通过上面的分析了解:客户端首先需要通过Binder的进程都需要先获得ServiceManager代理对象才能进行Binder通讯。所以,ServiceManager在C/C++层面提供服务代理,又在Java层面提供服务代理。
接着我们通过一个简单跨进程通讯Demo来加深对Binder的了解和使用。我们分三步:
1.设计服务端,新建 一个基于Binder的类。
2.设计客户端,获取远程Binder对象。
3.操作获取的Binder对象执行操作。
创建Service端
我们只要基于Binder类新建一个服务类即可。在设计这个客户端之前,我们要考虑两个问题:
a.客户端如何获得服务端的Binder的引用。
b.客户端和服务端必须事先约定好 服务端函数的参数在包裹中的顺序。
对于第一个问题,我们向一个本地的service类进行连接,在这个客户端与servcie建立连接后返回一个Binder,即我们设计的服务端。
对于第二个问题,android中SDK中为我们提供了一个aidl工具,借着这个工具我们可以将aidl文件上转化为一个java类文件,在该java文件中,同时重载了transact和onTransact()方法,统一了存入包裹和读取包裹参数。
设计服务端
先看下工程目录,了解整个代码的构成
1)、创建一个IAidlBinder服务,这个服务里有两个服务函数,getInfo(),getFruit(),这里我们就编写一个IAidlBinder文件,代码如下:
package com.binderserver;
import com.binderserver.Fruit;
interface IAidlBinder{
String getInfo();
Fruit getFruit();
}
1.java原子类型,如int,long,String等变量。
2.Binder引用。
3.实现了Parcelable的对象。
这里文件名称第一个"I"的含义是IIterface类,即这是一个可以提供远程服务的类。我们创建好文件后,aidi工具会以文件的名称在gen目录下生成一个java类。
接着看看aidl生成的IAidlBinder.java代码。
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: D:\\workspace\\BinderServer\\src\\com\\binderserver\\IAidlBinder.aidl
*/
package com.binderserver;
public interface IAidlBinder extends android.os.IInterface {
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements
com.binderserver.IAidlBinder {
private static final java.lang.String DESCRIPTOR = "com.binderserver.IAidlBinder";
/** Construct the stub at attach it to the interface. */
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.binderserver.IAidlBinder
* interface, generating a proxy if needed.
*/
public static com.binderserver.IAidlBinder asInterface(
android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = (android.os.IInterface) obj
.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.binderserver.IAidlBinder))) {
return ((com.binderserver.IAidlBinder) iin);
}
return new com.binderserver.IAidlBinder.Stub.Proxy(obj);
}
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_getInfo: {
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.getInfo();
reply.writeNoException();
reply.writeString(_result);
return true;
}
case TRANSACTION_getFruit: {
data.enforceInterface(DESCRIPTOR);
Fruit _result = this.getFruit();
reply.writeNoException();
if ((_result != null)) {
reply.writeInt(1);
_result.writeToParcel(reply,
android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.binderserver.IAidlBinder {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
public java.lang.String getInfo() 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_getInfo, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
public Fruit getFruit() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
Fruit _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getFruit, _data, _reply,
0);
_reply.readException();
if ((0 != _reply.readInt())) {
_result = Fruit.CREATOR.createFromParcel(_reply);
} else {
_result = null;
}
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_getInfo = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_getFruit = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.lang.String getInfo() throws android.os.RemoteException;
public Fruit getFruit() throws android.os.RemoteException;
}
这些代码主要完成三个任务:
package com.binderserver;
import android.os.Parcel;
import android.os.Parcelable;
public class Fruit implements Parcelable {
private String name;
private String color;
private int number;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public static final Parcelable.Creator CREATOR = new Creator() {
@Override
public Fruit createFromParcel(Parcel source) {
Fruit fruit = new Fruit();
fruit.name = source.readString();
fruit.color = source.readString();
fruit.number = source.readInt();
return fruit;
}
@Override
public Fruit[] newArray(int size) {
// TODO Auto-generated method stub
return new Fruit[size];
}
};
@Override
public int describeContents() {
// TODO Auto-generated method stub
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeString(color);
dest.writeInt(number);
}
}
3)、创建一个名为Book的aidl文件,代码如下:
parcelable Fruit;
package com.binderserver;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import com.binderserver.IAidlBinder.Stub;
public class ServerService extends Service {
private Fruit mFruit;
@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
mFruit = new Fruit();
mFruit.setName("apple");
mFruit.setColor("red");
mFruit.setNumber(10);
}
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return serviceBinder;
}
private IAidlBinder.Stub serviceBinder = new Stub() {
@Override
public String getInfo() throws RemoteException {
// TODO Auto-generated method stub
return "I'm a server";
}
@Override
public Fruit getFruit() throws RemoteException {
// TODO Auto-generated method stub
return mFruit;
}
};
}
客户端的设计
客户端的工程目录如下 :
客户端的实现:
1)、将服务端的的aidl文件及要被调用的类,直接拷贝到工程目录下。注意:为了客户端调用远程服务,不要改变原aidl文件的地址,不然会报错:
Binder invocation to an incorrect interface
2)、创建主Activity,用来调用Aidl服务。代码如下:
package com.binderclient;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import com.binderserver.IAidlBinder;;
public class BinderClientActivity extends Activity implements OnClickListener {
/** Called when the activity is first created. */
private IAidlBinder binder;
private Button mGetInfo;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mGetInfo = (Button) findViewById(R.id.getinfo);
mGetInfo.setOnClickListener(this);
//注意这里intent要在ServerService进行静态注册。
Intent intent = new Intent("com.binderserver.ServerService");
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection serviceConnection = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
binder = IAidlBinder.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
binder = null;
}
};
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.getinfo:
if(binder == null){
Log.d("Lawrence", "@#$%^&*()_+!#$%^&*_!@#$%^&*()");
}else {
try {
String print = "The name of this fruit is: " + binder.getFruit().getName() + "\n"
+ "The color of this fruit is: " + binder.getFruit().getColor() + "\n"
+ "The number of this fruit is: " + binder.getFruit().getNumber() + "\n"
+ "The server says: " + binder.getInfo();
mGetInfo.setText(print);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
break;
default:
break;
}
}
}
4)、点击getInfo按钮,调用远程服务。界面如下 :
上面就是一个以aidl为例的远程Binder调用的简单设计。
附:Demo源码地址:点击打开链接