Android Interface Definition Language (AIDL)
原文:https://developer.android.com/guide/components/aidl.html
为了加深对AIDL的了解,翻译了这篇文章,翻译水平有限,不当之处欢迎指正。
简介
AIDL (安卓接口定义语言) 和其他接口定义语言非常相似.。在处理进程间通信(IPC)的时候,它允许你定义好客户端和服务端之间的编程接口以便他们使用IPC进行通信。在安卓中,一个进程通常并不能访问另外一个进程的内存。也就是说,他们需要将对像分解为操作系统能识别的原始数据,并将这些数据序列化才能跨越进程边界进行通信。这种序列化的代码是非常乏味的,因此安卓提供AIDL来帮你处理这些。
注意:仅当你允许来自其他应用的客户端通过IPC访问你的服务并且服务需要处理多线程的时候,才有必要使用AIDL。如果你不需要处理IPC,那么你可以通过实现Binder接口来创建自己的接口。或者你需要处理IPC,但是不需要处理多线程,那么你可以使用Messageer来创建接口。不管如何,你在使用AIDL之前应该懂得Bound Service。
在你设计自己的AIDL接口前,你应该要知道调用AIDL接口的方式是直接函数调用。你不应该假设这个函数调用发生的线程是哪一个,因为本地进程调用和远程进程调用是不一样的:
- 本地进程发出的调用,服务是在发出调用的同一个线程中执行的。如果你在UI线程中发出调用,那么UI线程继续在AIDL接口中执行。如果是其他线程,那么其他线程就继续在AIDL接口中执行。因此,如果只有本地线程访问服务,你完全可以控制是哪个线程在接口中执行(这种情况你不应该使用AIDL,你应该使用Binder)。
- 远程进程发出的调用,到达服务时是由本地进程的一个线程池中某个线程分发出来的。你需要做好准备以处理调用来自未知线程的情况,同时也要处理同一时间内可能有多个调用的情况。换句话说,AIDL接口的实现需要是完全线程安全(thread-safe)。
- 关键字oneway会修改远程进程调用的行为。当oneway被使用时,一个远程调用不会再被阻塞,调用在传送数据之后会立即返回。AIDL接口这边,仍然从Binder线程池中得到这个调用,和普通的远程进程调用并没有区别。如果本地调用使用oneway关键字,这并不会产生效果,调用仍然是同步的。
定义一个AIDL接口
你必须使用java编程语言语法在一个.aidl文件中定义你的AIDL接口,然后将它同时保存到持有服务的应用和其他绑定到此服务的应用中。
当你编译每一个包含此.aidl文件的应用时,Android SDK 工具都会产生一个基于此.aidl文件的IBinder接口,并且将接口保存到工程的gen目录下面。服务端必须正确地实现这个IBinder接口。客户端应用可以绑定到这个服务,然后通过使用IBinder提供的接口来执行IPC。
使用AIDL来创建一个可以绑定的Service,使用下面的步骤:
- 创建.aidl文件
这个文件使用方法声明定义了编程接口。 - 实现接口
Android SDK 工具跟据你的.aidl文件产生一个基于java编程语言的接口。这个接口产生了一个内部虚类Stub,Stub继承了Binder类并继承了你在AIDL接口中定义的方法。你必须继承Stub类并实现相关的方法。 - 导出接口给客户端
实现一个Service,重写onBind()接口返回Stub类的实现。
注意:在你第一次发布AIDL接口之后,你所做的任何改变都必须向后兼容,避免影响其他使用你的服务的应用。换句话说,因为你的.aidl文件需要被复制到其他应用中以便它们能访问你的服务接口,所以你需要管理对原有接口的支持。
1.创建.aidl文件
AIDL使用简单的语法,允许你使用一个或多个拥有返回值与参数的方法来定义接口。参数和返回值可以是任何类型,甚至是其他AIDL接口。
你必须使用java编程语言来创建.aidl文件。每个.aidl文件必须定义一个单一的接口,并且只有接口定义和方法声明。
默认情况下,AIDL支持下面的数据类型:
- 所有java编程语言中的原始类型(比如int,long,char,boolean等等)
- String
- CharSequence
- List
所有List中的元素都必须是AIDL支持的数据类型或者是AIDL接口或者是自定义的Parcelable类。可以作为泛型方式使用(比如List< String >)。对方收到的数据实际上会被转换成ArrayList,即使方法声明是List。 - Map
同List,但是不支持泛型(比如Map< String,Integer >),另外对方收到的是HashMap.
除了上面这些类型,你必须为每个其他的类型包含一条import语句,即使它们和接口是定义在同一个包里面。
定义你的服务接口,需要明确:
- 方法可以带0个或多个参数,可以返回一个值或者返回void
- 所有的非原始类型的参数,需要有一个方向描述符来指明数据传送的方向。可以是in,out,inout中的某一个。原始数据类型默认方向描述符是in,并且不能被修改。
注意 :你必须要根据实际需要限制数据传送方向,因为组织数据是昂贵的。 - .aidl文件中所有的代码注释会被包含到生成的IBinder接口中(除了import和package之前的注释)。
- 只支持方法,不支持静态成员变量。
下面是一个.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)。
如果你使用的是AndroidStudio,增量编译使得binder类几乎是立即被创建。如果你不是使用AndroidStudio,那么Gradle工具会在下次你编译应用时生成binder类—-你需要在写完.aidl文件之后使用gradle assembleDebug(或者gradle assembleRelease)编译你的工程,这样你的代码才可以链接到那个生成的类。
2. 实现接口
当你编译应用时,Android SDK工具产生一个和.aidl同名并以.java后缀的接口。这个接口包含了一个子类Stub,它是一个虚类,并且包含了.aidl中定义的接口。
注意:Stub中还定义了一些帮助函数,尤其是asInterface(),它可以通过传入一个IBinder参数(通常是传给客户端onServiceConnected()的参数)返回一个Stub接口的实例。
为了实现.aidl产生的接口,需要继承产生的IBinder接口(例如YourInterface.Stub)并且实现从.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接口。下一步,这个实例会被导出给客户端以便它们和服务进行交互。
当你实现AIDL接口时,下面是一些需要遵守的规则:
- 进来的调用不保证是从主线程进来的,所以你要思考多线程处理,并保证你的服务是线程安全的。
- 默认情况下,RPC调用是同步的。如果你知道服务要花费一些时间去完成一个请求,那么你不应该从UI线程中去调用这个服务,因为它可能挂起应用(安卓可能会弹出ANR对话框)—你应该在客户端的一个独立的线程中调用这些服务。
- 你不能抛出异常给调用者。
3. 导出接口给客户端
一旦你实现了接口,那么剩下的就是导出它们到你的客户端,以便客户端可以绑定到服务。为了导出到客户端,你需要继承Service并实现onBind(),返回一个Stub的实例。下面是一个例子:
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()方法返回的mBinder实例。
客户端也需要访问接口类,所以如果客户端在其他独立应用中时,需要复制.aidl文件到客户端应用的src目录下(这样同样会在客户端应用中产生IBinder接口文件,以供客户端调用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;
}
};
在IPC中传输对像
如果你有一个类需要使用IPC接口从一个进程发送到另一个进程,你可以做到。但是你要确保一点,你的类在IPC通道的另一边是有效的,并且此类要支持Parcelable接口。支持Parcelable接口是非常重要的因为这允许操作系统分解对像为原始数据结构并跨进程传输。
创建一个支持Parcelable接口的类,你需要这样做:
- 让你的类implement Parcelable接口。
- 实现writeToParcel函数,这个函数会将对像的目前状态写入到一个Parcel中。
- 增加一个静态成员变量CREATOR,它实现了Parcelable.Creator接口。
- 最后,创建一个.aidl文件,这个文件定义你的parcelable类(例如下面的Rect.aidl文件)。
如果你使用第三方编译系统,不要增加.aidl文件,你需要增加一个c语言头文件,因为这个.aidl文件不会被编译。
AIDL在代码中使用这些方法和域来进行序列化和反序列化你的对像。
例如,下面是一个Rect.aidl文件用于创建Rect类(Parcelable)
package android.graphics;
// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;
下面是Rect类的实现
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读取了四个数值,你需要确保这些数值是在允许范围内的,无论调用者传什么样的值。
调用一个IPC方法
下面是客户端远程调用AIDL接口需要进行的步骤:
- 包含.aidl文件到工程的src目录。
- 定义一个IBinder接口(基于AIDL生成)的实体。(实际上这步应该是服务端来做的,官方文档可能有点问题)
- 实现ServiceConnection。
- 调用Context.bindService(),将你的ServiceConnection实现传递进去。
- 在你的ServiceConnection实现中,你会收到一个IBinder实体(叫service)。调用YourInterfaceName.Stub.asInterface((IBinder)service)将IBinder实体转换成YourInterfaceName类型。
- 调用你定义在接口中的方法。你需要一直捕捉DeadObjectException异常,当连接破裂时会抛出此异常,这是远程方法唯一会抛出的异常。
- 需要断开连接时,调用Context.unbindService()。
调用IPC服务的一些建议:
- 跨进程对像引用会被计数
- 你可以发送匿名对像作为方法参数
下面是一个调用AIDL创建的服务的示例代码,来自于ApiDemos工程:
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);
}
}
};
}