AIDL(Android接口定义语言)与你可能使用过的其它的IDLs是相似的。它容许你定义客户端与service协调一致的编程接口,以便于彼此之间使用进程间通讯(IPC)机制进行通讯。在Android上,一个进程一般不能访问另外一个进程的内存。能够说,它们须要把它们的对象分解为操做系统可以理解的原始数据类型,并在进程之间按次序排列对象。那种排列对象的代码写起来是很乏味的,所以Android经过AIDL来为你处理这些事情。html
注意:只有在你容许来自于不一样的应用的客户端访问你的service以实现IPC,并想要在你的service中处理多线程时,才须要使用AIDL。若是你不须要跨不一样应用执行并发IPC,你应该经过实现一个Binder来建立你的接口,或者若是你想要执行IPC,但不须要处理多线程,能够使用一个Messenger来实现你的接口。不管哪一种,请确保在实现一个AIDL以前,你理解了Bound Services。/java
在开始设计你的AIDL接口以前,请意识到对于一个AIDL接口的调用是直接的函数调用(大概指的是阻塞调用,在调用一个IPC方法时,客户端线程会一直等待,直到service端处理完成并返回)。你不该该假设调用所发生的线程。依赖于调用是来自于本进程的一个线程,仍是一个远端进程,则会发生不一样的事情。特别地:android
发起于本进程的调用将在发起调用相同的线程中执行。若是这是你的UI线程,则线程将继续在AIDL接口中执行。若是它是另一个线程,那它将是执行你的service代码的线程。所以,若是只有本地线程访问service,你能够彻底控制在哪一个线程中来执行它(但若是是那种状况,则你不该该使用AIDL,而应该经过实现一个Binder来建立接口)。编程
调用来自于远端进程,并从平台维护的你本身的进程中的一个线程池派发。你必须为调用可能来自于未知线程作好准备,多个调用可能在同一时刻发生(Service是一个对象,而不是一个线程,这里的未知线程大概指的是binder线程,这里的意思大概是说,AIDL service类要按照线程安全类的标准来构造)。换句话说,一个AIDL接口的实现必须彻底是线程安全的。api
单路行为的远程调用。当使用时,一个远程调用不发生阻塞;它仅仅是发送事务数据并当即返回。接口的实现最终将它做为来自于Binder线程池的一个普通远端调用来接收。若是单路被用于一个本地调用,则没有影响,调用依然是同步的。安全
你必须使用Java编程语言语法在一个.aidl文件中定义你的AIDL接口,而后同时保存在service所在的应用和其余要bind到service的应用的源代码中(在src/目录下)。多线程
当你编译每一个包含.aidl文件的应用时,Android SDK工具会基于.aidl文件产生一个IBinder接口,并把它保存在项目的gen/目录下。Service必须适当的实现IBinder接口。客户端应用能够bind到service并调用来自于IBinder的方法来执行IPC。并发
要使用AIDL来建立一个bounded service,则听从如下几个步骤:app
建立.aidl文件编程语言
这个文件经过方法签名定义了编程接口。
Android SDK工具基于.aidl文件以Java编程语言产生一个接口。这个接口具备一个名为Stub的内部抽象类,该抽象类扩展了Binder并实现了来自于你的AIDL接口的方法。你必须扩展Stub类并实现那些方法。
注意:在你首次发布你的AIDL接口以后,对它的任何修改都必需要保持向后兼容,以免破坏使用了你的service的其余应用。即,因为你的.aidl文件必须被复制到其余的应用以使它们可以访问你的service的接口,你必须维护对于原始接口的支持。
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) 编译你的工程,以使你的代码能够再次连接产生的类。
当你编译你的应用时,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不会被发回调用者。
为你的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来获取更多关于如何使你的应用更安全并远离病毒的信息。
这里是一个调用类调用一个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.