Android 接口定义语言(AIDL)
AIDL类似你可能接触过的其他的IDL。它可以让你定义编程接口,使得客户端和服务端可以达成一致,从而使用IPC(interprocess communication)相互通信。
在Android里面,一个进程通常不能访问其他进程的内存。所以说,他们需要将对象分解为操作系统可以理解的基本的部分,从而使得这些对象为你突破界限。然而突破界限的代码太繁杂,所以Android使用AIDL替你打理。
提示:只有当你想要不同app的客户端都可以对你的service IPC,并且你的service需要多线程处理的时候,你才有必要使用AIDL。如果你不需要跨进程并发IPC,那么你可以通过拓展一个Binder来创建你的接口。或者你需要IPC但是不需要处理多线程,你可以构建使用Messenger来构建一个接口。无论怎样,确保你在开始AIDL之前立即Bound Service。
当你设计你的AIDL接口之前,确保你对AIDL接口的调用都是直接函数调用。你不要预想这些调用从哪些线程里面出现。重点在于,调用来自于一个本地的进程还是一个一个远程的进程。特别是:
- 来自本地进程的调用,会在发起这个调用的线程中执行。如果这是你的主UI界面线程,这个线程会继续在AIDL接口中运行。如果是另一个线程,那就是在你的service中执行的代码的线程。这样的话,只有本地线程在访问srevice的时候,你才可以完全控制哪一个线程在其中执行。(如果是这种情况,你根本不应该使用AIDL,而应该通过构建一个Binder来创建接口)。
- 来自远程进程的调用会被从你的线程池当中派遣,这个线程池是该平台在你自己的进程中维护的。你必须准备好迎接来自位置线程的通信,而且是多个 通信同时。换句话说,AIDL的构建必须要是彻底的线程安全。
- oneway(单向)关键字指定了远程调用的样子。当使用该关键字的时候,远程的调用不会阻塞。它仅仅是传送业务数据并且即刻返回。这个接口的构建最终把它作为一个来自Binder线程池的常规调用,就如同一个正常的远程调用一样。如果oneway关键字被用作一个本地调用则没有影响,调用依旧是同步的。
定义一个AIDL接口
你必须在一个.aidl文件中使用java编程语言的语法来进行定义AIDL接口,然后把它存储在运行service的源代码和其他要bind service的源代码中(在 src/目录下)。
但你构建包含.aidl文件的每一个app时,android SDK工具会基于.aidl产生一个IBinder,并把它存储在项目的gen/目录下。service必须恰当地构建IBinder接口。然后客户端的应用可以bind service并且调用IBinder中的方法来实现IPC。
使用AIDL来创建绑定的service,遵循以下几个步骤:
1.创建.aidl 文件
这个文件使用方法签名(就是函数的声明)定义一个编程接口。
2.构建接口
Android SDK工具基于.aidl文件产生一个Java编程语言的接口。这个接口有一个叫做Stub的内部抽象类,他拓展了Binder并且实现了AIDL接口中的方法。你必须拓展Stub类并且实现这些方法。
3.向客户端暴露这些方法
构建一个Service并且重写onBinde方法来返回你构建的Stub类。
警告:任何在你发布了第一个版本的AIDL之后所做的修改必须要向后兼容,以防止破坏其他使用你的服务的应用。因为你的.aidl文件一定会被其他的app复用来访问你的service的接口,你必须维护并支持原始的接口。
大标题
1.创建.aidl文件
AIDL使用一个简单的语法,它可以让你用一个或者多个方法声明一个接口,接口可以接收参数和返回数值。参数和返回值可以是任何的类型,AIDL生成的其他类型都可以。
你必须使用Java编程语言来构建.aidl文件。每一个.aidl文件都必须定义只一个接口,并且请求指定接口的定义和方法签名。
默认的话,AIDL支持一下的数据的类型:
- 所有Java编程语言中的基本类型。(比如说 int,long,char,boolean等等)
- String
- CharSequence
- List
所有List中的缘分都必须是列表中支持的类型,或者是其他的AIDL生成的接口,以及你声明的可打包类型。一个List可以随意地被用作一个泛型(比如说,List<String>)。另一边实际接收的具体类型通常是一个ArrayList,尽管接口中定义的是List类型。
- Map
Map中的所有元素也必须是列表中所支持的类型,或者是其他的AIDL中生成的接口,或者是你声明的可打包类型。 泛型的 maps(比如说Map<String,Integer>)形式的就不被支持。另一边接收的实际的具体的类通常是一个HashMap,尽管接口中定义的是Map类型。
你必须必须为每一个上面没有提及的附加类型包含一个import声明,尽管他们和你的接口定义在同一个包中。
当你定义你的服务接口时,你要知道:
- 方法可以接受0个或者多个的参数,返回一个值或者void。
- 所有的非基本参数都需要一个方向标签来指明这些数据将怎样流动。in,out,或者inout。
基本类型默认是in类型,不能是其他的。
警告:你必须对真正需要的类型的方向有所限制,因为封装传送(marshalling)参数是很耗费资源的。
- 所有的在.aidl中的代码注释都被包含在了生成的IBinder接口中(除了在import和pachage声明之前的注释)。
- 只能提供方法,你不能暴露AIDL中的静态部分。
下面是一个.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); }只要把你的.ail文件存在你的项目的src/目录下,当你构建你的app时,sdk工具会在你的项目的gen/目录下生成IBinder接口文件。生成的文件的名字和.aidl的文件名对应,但是有一个java拓展名。(比如IRemoteService.aild和IRemoteService.java)。
如果你使用Android Studio,增量构建(incremental build)立即产生一个binder class。如果你不使用这个IDE,那么Gradle工具在你下一次构建你的应用时生成bind class——在你完成.aidl的编辑以后,你要尽早地使用 gradle assembleDebug(或者gradle assmbleRlease)来构建你的项目。这样的话你的代码就可以链接到生成的class了。
2.构建接口
当你(build)构建你的app时,Android SDK工具为你生成一个以.aidl文件的命名的.java接口文件。这个生成的接口包含一个名字为Stub的子类,它是它的父接口的抽象构造接口(比如说,YourInterface.stub),并且声明了.aidl文件中的所有的方法。
<这个cast 是借用的冶金中铸造注模的概念,把液态的金属倒入成型的模范这个过程叫cast。编程中把一段数据装成某个数据类型,让数据具有某个类型的过程叫做cast。
比如让4个字节变成一个int类型,把int变成4个char这种过程。
基本上和“类型转换”同义,不过cast在c++语言中是从对象封装的视角看这个动作。
所以有动态cast,静态cast等多种cast。>
提示:Stub 也定义了一下帮助者的方法,比如说asInterface,它接收一个IBinder参数(通常是传递给onServiceConnected回调方法的那个参数),并且返回一个stub的接口实例。想了解更多的其中的类型转换的细节,请查看Calling an IPC Method。
想要构建从.aidl中生成的接口,就要拓展生成的Binder接口(比如说YourInterface.Stub),并且构建从.aidl文件中继承的方法。
下面是一个使用匿名对象构建一个叫做IRemoterService(在上面的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)实例,它为这个服务定义了一个RPC接口。下一步将会把这个接口呈现给客户端,这样他们可以和service通信了。
在构建AIDL接口时有几个规则你要知道:
- 接到的调用不能保证都在主线程中运行,所以你需要在一开始就考虑多线程,并且保证线程安全。
- 默认而言,RPC调用都是同步的。如果你发现服务处理一个请求花费了可观的时间,那么你不应该在activity的主线程中调用它,因为这样就可能会让app挂起(Android 系统可能会现实ANR对话框)——你需要从客户端的子线程中调用。
- 你抛出的所有异常都不会被发送到调用者那里。
3.向客户端呈现接口
一旦你为service构建好了接口,你需要向客户端暴露呈现它们才能让他们bind。要呈现service的接口,你应该extend Service并且构建一个onBind来返回构建Stub的类的实例(上面说的那个)。下面是一个向客户端呈现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来连接服务,客户端的onServiceConnected回调方法从service的onBind方法中接收一个实例。
客户端必须可以访问接口类,所以如果客户端和server在不同的app中,那么他们各自都应该在src/下有.aidl的副本(它生成了android.os.Binder接口,让用户可以访问AIDL方法)。
当客户端在onServiceConneted回调方法中接收到IBinder的实例以后,他必须调用YourServiceInterface.stub.asInterface(Service)来将返回的参数转换为YourServiceInnterface的类型,例如:
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; } };更多的示例代码,请参考RemoteSErvice.java-ApiDemos.
通过IPC来传递对象
你已经可以通过IPC接口来跨进程传递一个class了。但是,你需要确保你的代码可以从另一端的IPC通道获得,并且你的class需要实现Pacelable接口。实现Pacelable接口很重要,只有这样Android系统才能分解对象来跨进程。
要创建一个支持Pacelable协议的类,你必须这么做:
1.实现Parcelable接口。
2.要实现一个记录当前对象状态并将其写入Parcel的方法,writeToParcel。
3.将一个叫做CREATOR的静态对象放入你的类,这个对象要实现Parcelable.Creator接口。
4.最后,创建一个声明你的parcelable类的.aidl文件(如下面的Rect.aidl文件)。
如果你使用了一个客户构建的进程,那么不要把.aidl文件放到你的构建里面,如同C语言一样,这个.aidl不会被编译。
AIDL 在其代码中使用这些方法和域来装箱和散出你的对象。
例如,下面是一个Rect.aidl文件,创建了一个parcelabel的Rect类。
package android.graphics; // Declare Rect so AIDL can find it and knows that it implements // the parcelable protocol. parcelable Rect;下面是一个Rect class实现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类中的编集很简单。看看其他的Parcled的方法来了解一下其他类型可以写入Parcel的数值。
警告:不要忽视跨进程接收数据对于安全的影响。在本例中,Rect从Parcel中读取了4个数字,但是你应该自己判断这些数字是不是在可用的范围内。 可以参考Sercurity andPermission来获得更多的关于防止恶意软件侵害的内容。
调用一个IPC方法
下面是一个主动的类要通过AIDL调用远程必须做的:
1.在src/目录下有一个.aidl文件。
2.声明一个IBinder接口的实例(在AIDL中生成的接口)。
3.实现SErviceConnection。
4.调用Context.bindService(),传入你构架的ServiceConnection。
5.在你构建的onServiceConnction当中,你将会接到一个叫做service的IBinder对象。调用UourInterfaceName.Stub.asInstance((IBinder)service)来将返回的参数转化为你的接口的类型。
6.可以调用接口中定义的方法了,但是你呀一直都捕获DeadObjectException异常,他们会在连接断开的时候抛出。这是远程方法可能抛出的唯一之中异常。
7.要断开连接,用你的接口实例调用Context.unbindService方法。
下面是关于调用IPC服务的一些注解。
- 对象被当做是跨越进程的引用。
- 你可以在方法参数中放入匿名对象。
更多的关于bind service的资料请看BoundService文档。
下面是一个示例代码,展示了一个AIDL生成的service。来自于ApiDemos项目的Rmote 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. Intent intent = new Intent(Binding.this, RemoteService.class); intent.setAction(IRemoteService.class.getName()); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); intent.setAction(ISecondary.class.getName()); bindService(intent, 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); } } }; }
Android Interface Definition Language (AIDL)——翻译自developer.android.com
原文:http://blog.csdn.net/wallezhe/article/details/51154524