AIDL(Android接口定义语言)与你可能使用过的其它的IDLs是类似的。它允许你定义客户端与service协调一致的编程接口,以便于彼此之间使用进程间通信(IPC)机制进行通信。在Android上,一个进程通常不能访问另一个进程的内存。可以说,它们需要把它们的对象分解为操作系统能够理解的原始数据类型,并在进程之间按次序排列对象。那种排列对象的代码写起来是很乏味的,因此Android通过AIDL来为你处理这些事情。
注意:只有在你允许来自于不同的应用的客户端访问你的service以实现IPC,并想要在你的service中处理多线程时,才需要使用AIDL。如果你不需要跨不同应用执行并发IPC,你应该通过实现一个Binder来创建你的接口,或者如果你想要执行IPC,但不需要处理多线程,可以使用一个Messenger来实现你的接口。无论哪种,请确保在实现一个AIDL之前,你理解了Bound Services。/
在开始设计你的AIDL接口之前,请意识到对于一个AIDL接口的调用是直接的函数调用(大概指的是阻塞调用,在调用一个IPC方法时,客户端线程会一直等待,直到service端处理完成并返回)。你不应该假设调用所发生的线程。依赖于调用是来自于本进程的一个线程,还是一个远端进程,则会发生不同的事情。特别地:
发起于本进程的调用将在发起调用相同的线程中执行。如果这是你的UI线程,则线程将继续在AIDL接口中执行。如果它是另外一个线程,那它将是执行你的service代码的线程。因此,如果只有本地线程访问service,你可以完全控制在哪个线程中来执行它(但如果是那种情况,则你不应该使用AIDL,而应该通过实现一个Binder来创建接口)。
调用来自于远端进程,并从平台维护的你自己的进程中的一个线程池派发。你必须为调用可能来自于未知线程做好准备,多个调用可能在同一时刻发生(Service是一个对象,而不是一个线程,这里的未知线程大概指的是binder线程,这里的意思大概是说,AIDL service类要按照线程安全类的标准来构造)。换句话说,一个AIDL接口的实现必须完全是线程安全的。
单路行为的远程调用。当使用时,一个远程调用不发生阻塞;它仅仅是发送事务数据并立即返回。接口的实现最终将它作为来自于Binder线程池的一个普通远端调用来接收。如果单路被用于一个本地调用,则没有影响,调用依然是同步的。
定义一个AIDL接口
你必须使用Java编程语言语法在一个.aidl文件中定义你的AIDL接口,然后同时保存在service所在的应用和其他要bind到service的应用的源代码中(在src/目录下)。
当你编译每个包含.aidl文件的应用时,Android SDK工具会基于.aidl文件产生一个IBinder接口,并把它保存在项目的gen/目录下。Service必须适当的实现IBinder接口。客户端应用可以bind到service并调用来自于IBinder的方法来执行IPC。
要使用AIDL来创建一个bounded service,则遵从以下几个步骤:
这个文件通过方法签名定义了编程接口。
Android SDK工具基于.aidl文件以Java编程语言产生一个接口。这个接口具有一个名为Stub的内部抽象类,该抽象类扩展了Binder并实现了来自于你的AIDL接口的方法。你必须扩展Stub类并实现那些方法。
注意:在你首次发布你的AIDL接口之后,对它的任何修改都必须要保持向后兼容,以避免破坏使用了你的service的其他应用。即,由于你的.aidl文件必须被复制到其他的应用以使它们能够访问你的service的接口,你必须维护对于原始接口的支持。
1. 创建.aidl文件
AIDL使用了一种简单的语法供你声明一个接口,接口可以有一个或多个方法,每个方法可以接收一些参数并返回值。参数和返回值可以是任何类型,甚至是其他的AIDL-generated接口。
你必须使用Java编程语言构造.aidl文件。每个.aidl文件必须定义一个单独的接口,并且只需要接口声明和方法签名。
默认情况下,AIDL支持下述数据类型:
Java编程语言中的所有原始数据类型(比如int, long, char, boolean, 等等)
List中的所有元素必须是所列出的被支持的数据类型,或某种其它的AIDL-generated接口,或你已经声明的parcelables。一个List可能被用作一个"generic"类(比如,List<String>)。另外一边实际接收到的具体类总是一个ArrayList,尽管产生的方法使用List接口。
Map中的所有元素必须是所列出的被支持的数据类型,或某种其它的AIDL-generated接口,或你已经声明的parcelables。泛型maps,(比如那些形如Map<String,Integer>的maps)是不被支持的。另外一边实际接收到的具体类总是一个HashMap,尽管产生的方法使用Map接口。
对于没有列出的每种额外的类型你都必须包含一个import声明,即使它们定义在与你的接口相同的package。
当定义你的service接口时,请意识到:
方法可以接收0个或多个参数,并返回一个值或void。
所有的非原始数据类型需要一个指示标记来表明数据的流向。in, out, 或者inout(请参考下面的例子)。
原始数据类型默认是in,其他则不是。
注意:你必须把方向限制在真正需要的方向,因为对于参数的排列是昂贵的。
.aidl文件中的所有代码注释被包含进了产生的IBinder接口中(除了那些在import和package声明前面的注释)。
只支持方法;你不能在AIDL中暴露static域。
这里有一个.aidl文件的例子:
// IRemoteService.aidl
package com.example.android;
// Declare any non-default types here with import statements
/** Example service interface */
interface IRemoteService {
/** Request the process ID of this service, to do evil things with it. */
int getPid();
/** Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
把你的.aidl文件保存在你的工程的src/目录下,当你编译你的应用时,SDK工具会在你的工程的gen/目录下产生IBinder接口文件。所产生的文件名与.aidl文件名匹配,但扩展名为.java(比如,IRemoteService.aidl产生IRemoteService.java)。
如果你使用Eclipse,增量编译几乎可以立即产生binder类。如果你不使用Eclipse,Ant工具在你下一次编译你的应用时产生binder类——你应该在你写完.aidl文件之后立即通过ant debug (或ant release) 编译你的工程,以使你的代码可以再次链接产生的类。
2. 实现接口
当你编译你的应用时,Android SDK工具产生一个以你的.aidl文件命名的.java接口文件。产生的接口包含一个名为Stub的子类,它是一个它的父接口的抽象实现 (比如,YourInterface.Stub),并声明了来自于.aidl文件的所有方法。
注意:Stub也定义了一些辅助方法,最值得注意的就是asInterface()了,它接收一个IBinder (通常是传递给客户端的onServiceConnected()回调方法的那个)并返回一个stub接口的实例。参考调用一个IPC方法部分,来获取更多关于如何做强制类型转换的信息。
要实现产生自.aidl的接口,则扩展产生的Binder接口 (比如YourInterface.Stub)并实现继承自.aidl文件的方法。
这里有一个称为IRemoteService的接口(由上面的例子IRemoteService.aidl定义)的一个示例实现:
private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
public int getPid(){
return Process.myPid();
}
public void basicTypes(int anInt, long aLong, boolean aBoolean,
float aFloat, double aDouble, String aString) {
// Does nothing
}
};
现在mBinder是一个Stub类的实例(一个Binder),它为service定义了IPC接口。在下一步中,这个实例会被暴露给客户端,以使它们能够与service交互。
当实现你的AIDL接口时有一些规则应该注意:
进入的调用不保证在主线程执行,因此你需要从一开始就考虑多线程,并适当的构建你的service以实现线程安全。
默认情况下,IPC调用是同步的。如果你知道service需要好多毫秒的时间来完成一个请求,则你不应该在activity的主线程中调用,因为它可能挂起应用(Android可能显示一个"Application is Not Responding"对话框)——你通常应该在客户端的另外一个线程中来调用它们。
你throw的exceptions不会被发回调用者。
3. 将接口暴露给客户端
为你的service实现了接口之后,你需要把它暴露给客户端,以使它们可以bind到它。要将你的service暴露出来,则扩展Service并实现onBind()来返回一个你的实现了产生的Stub的类的实例(如前面的讨论)。这里是一个示例service,它将IRemoteService例子接口暴露给客户端。
public class RemoteService extends Service {
@Override
public void onCreate() {
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
// Return the interface
return mBinder;
}
private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
public int getPid(){
return Process.myPid();
}
public void basicTypes(int anInt, long aLong, boolean aBoolean,
float aFloat, double aDouble, String aString) {
// Does nothing
}
};
}
现在当一个客户端(比如一个activity)调用bindService()来连接这个service时,客户端的onServiceConnected()回调将接收由service的onBind()方法返回的mBinder实例。
客户端必须也有访问接口类的权限,因此,如果客户端和service在不同的应用中,则客户端的应用必须具有一份.aidl文件的拷贝放在它的src/目录(其产生android.os.Binder接口——提供客户端访问AIDL方法的权限)下。
当客户端在onServiceConnected()回调中接收了IBinder,它必须调用YourServiceInterface.Stub.asInterface(service)来把返回的参数转换为YourServiceInterface类型。比如:
IRemoteService mIRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
// Called when the connection with the service is established
public void onServiceConnected(ComponentName className, IBinder service) {
// Following the example above for an AIDL interface,
// this gets an instance of the IRemoteInterface, which we can use to call on the service
mIRemoteService = IRemoteService.Stub.asInterface(service);
}
// Called when the connection with the service disconnects unexpectedly
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "Service has unexpectedly disconnected");
mIRemoteService = null;
}
};
更多示例代码,请参考ApiDemos中的RemoteService.java类。
透过IPC传递对象
如果你有一个类,你想要通过IPC接口将它从一个进程发送到另一个进程,那么你可以那样做。然而,你必须确保你的类的代码在IPC通道的另一端也是可以访问的,并且你的类必须支持Parcelable接口。支持Parcelable接口是很重要的,因为它允许Android系统来把对象分解为可以被跨进程处理原始数据类型。
要创建一个支持Parcelable协议的类,你必须做这些事情:
使你的类实现Parcelable接口。
实现writeToParcel,它将对象的当前状态写入一个Parcel。
给你的类添加一个static成员CREATOR,它是一个对象,并实现了Parcelable.Creator接口。
最后,创建一个.aidl文件,它声明了你的parcelable类 (如下面的Rect.aidl文件所显示的那样)。如果你在使用一个定制的编译过程,则不要把.aidl文件添加到你的build。类似于C语言中的头文件,这个.aidl文件不被编译。
AIDL使用代码中它产生的这些方法和成员来排序和解序你的对象。
比如,这里是一个Rect.aidl文件,用于创建一个parcelable的Rect类。
package android.graphics;
// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;
这里是一个Rect类如何实现Parcelable协议的例子。
import android.os.Parcel;
import android.os.Parcelable;
public final class Rect implements Parcelable {
public int left;
public int top;
public int right;
public int bottom;
public static final Parcelable.Creator<Rect> CREATOR = new
Parcelable.Creator<Rect>() {
public Rect createFromParcel(Parcel in) {
return new Rect(in);
}
public Rect[] newArray(int size) {
return new Rect[size];
}
};
public Rect() {
}
private Rect(Parcel in) {
readFromParcel(in);
}
public void writeToParcel(Parcel out) {
out.writeInt(left);
out.writeInt(top);
out.writeInt(right);
out.writeInt(bottom);
}
public void readFromParcel(Parcel in) {
left = in.readInt();
top = in.readInt();
right = in.readInt();
bottom = in.readInt();
}
}
Rect类中的成员排列组织相当简单。看一下Parcel上的其它方法,来了解你可以写入一个Parcel的其它种类的值。
警告:不要忘记从其它进程接收数据的安全隐患。在这个例子中,Rect从Parcel读取4个数字,但你要确保这些数字在可接受的值的范围以内,而无论调用者要去做什麽。参考Security and Permissions来获取更多关于如何使你的应用更安全并远离病毒的信息。
调用一个IPC方法
这里是一个调用类调用一个AIDL定义的远程接口所要采取的步骤:
在项目的src/目录下包含.aidl文件。
声明一个IBinder接口的实例(基于AIDL而产生)。
调用Context.bindService(),传入你的ServiceConnection实现。
在你的onServiceConnected()实现中,你将接收一个IBinder实例(称为service)。调用YourInterfaceName.Stub.asInterface((IBinder)service)来将返回的参数转换为YourInterface类型。
调用你的接口中定义的方法。你应该总是捕捉DeadObjectException异常,当连接断开时会抛出这个异常;这是远程方法将会抛出的唯一的异常。
要断开连接,则通过你的接口的实例调用Context.unbindService()。
关于调用IPC service的一些说明:
对象是跨进程引用计数的。
你可以发送匿名的对象作为方法参数。
更多关于binding到一个service的信息,请阅读Bound Services文档。
这里是一些实例代码,演示了调用一个AIDL-created service,来自于ApiDemos工程中的Remote Service示例。
public static class Binding extends Activity {
/** The primary interface we will be calling on the service. */
IRemoteService mService = null;
/** Another interface we use on the service. */
ISecondary mSecondaryService = null;
Button mKillButton;
TextView mCallbackText;
private boolean mIsBound;
/**
* Standard initialization of this activity. Set up the UI, then wait
* for the user to poke it before doing anything.
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.remote_service_binding);
// Watch for button clicks.
Button button = (Button)findViewById(R.id.bind);
button.setOnClickListener(mBindListener);
button = (Button)findViewById(R.id.unbind);
button.setOnClickListener(mUnbindListener);
mKillButton = (Button)findViewById(R.id.kill);
mKillButton.setOnClickListener(mKillListener);
mKillButton.setEnabled(false);
mCallbackText = (TextView)findViewById(R.id.callback);
mCallbackText.setText("Not attached.");
}
/**
* Class for interacting with the main interface of the service.
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. We are communicating with our
// service through an IDL interface, so get a client-side
// representation of that from the raw service object.
mService = IRemoteService.Stub.asInterface(service);
mKillButton.setEnabled(true);
mCallbackText.setText("Attached.");
// We want to monitor the service for as long as we are
// connected to it.
try {
mService.registerCallback(mCallback);
} catch (RemoteException e) {
// In this case the service has crashed before we could even
// do anything with it; we can count on soon being
// disconnected (and then reconnected if it can be restarted)
// so there is no need to do anything here.
}
// As part of the sample, tell the user what happened.
Toast.makeText(Binding.this, R.string.remote_service_connected,
Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
mKillButton.setEnabled(false);
mCallbackText.setText("Disconnected.");
// As part of the sample, tell the user what happened.
Toast.makeText(Binding.this, R.string.remote_service_disconnected,
Toast.LENGTH_SHORT).show();
}
};
/**
* Class for interacting with the secondary interface of the service.
*/
private ServiceConnection mSecondaryConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
// Connecting to a secondary interface is the same as any
// other interface.
mSecondaryService = ISecondary.Stub.asInterface(service);
mKillButton.setEnabled(true);
}
public void onServiceDisconnected(ComponentName className) {
mSecondaryService = null;
mKillButton.setEnabled(false);
}
};
private OnClickListener mBindListener = new OnClickListener() {
public void onClick(View v) {
// Establish a couple connections with the service, binding
// by interface names. This allows other applications to be
// installed that replace the remote service by implementing
// the same interface.
bindService(new Intent(IRemoteService.class.getName()),
mConnection, Context.BIND_AUTO_CREATE);
bindService(new Intent(ISecondary.class.getName()),
mSecondaryConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
mCallbackText.setText("Binding.");
}
};
private OnClickListener mUnbindListener = new OnClickListener() {
public void onClick(View v) {
if (mIsBound) {
// If we have received the service, and hence registered with
// it, then now is the time to unregister.
if (mService != null) {
try {
mService.unregisterCallback(mCallback);
} catch (RemoteException e) {
// There is nothing special we need to do if the service
// has crashed.
}
}
// Detach our existing connection.
unbindService(mConnection);
unbindService(mSecondaryConnection);
mKillButton.setEnabled(false);
mIsBound = false;
mCallbackText.setText("Unbinding.");
}
}
};
private OnClickListener mKillListener = new OnClickListener() {
public void onClick(View v) {
// To kill the process hosting our service, we need to know its
// PID. Conveniently our service has a call that will return
// to us that information.
if (mSecondaryService != null) {
try {
int pid = mSecondaryService.getPid();
// Note that, though this API allows us to request to
// kill any process based on its PID, the kernel will
// still impose standard restrictions on which PIDs you
// are actually able to kill. Typically this means only
// the process running your application and any additional
// processes created by that app as shown here; packages
// sharing a common UID will also be able to kill each
// other's processes.
Process.killProcess(pid);
mCallbackText.setText("Killed service process.");
} catch (RemoteException ex) {
// Recover gracefully from the process hosting the
// server dying.
// Just for purposes of the sample, put up a notification.
Toast.makeText(Binding.this,
R.string.remote_call_failed,
Toast.LENGTH_SHORT).show();
}
}
}
};
// ----------------------------------------------------------------------
// Code showing how to deal with callbacks.
// ----------------------------------------------------------------------
/**
* This implementation is used to receive callbacks from the remote
* service.
*/
private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
/**
* This is called by the remote service regularly to tell us about
* new values. Note that IPC calls are dispatched through a thread
* pool running in each process, so the code executing here will
* NOT be running in our main thread like most other things -- so,
* to update the UI, we need to use a Handler to hop over there.
*/
public void valueChanged(int value) {
mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, 0));
}
};
private static final int BUMP_MSG = 1;
private Handler mHandler = new Handler() {
@Override public void handleMessage(Message msg) {
switch (msg.what) {
case BUMP_MSG:
mCallbackText.setText("Received from service: " + msg.arg1);
break;
default:
super.handleMessage(msg);
}
}
};
}
Done.
来源:oschina
链接:https://my.oschina.net/u/919237/blog/212528