Android接口定义语言(AIDL)
AIDL(Android界面定义语言)类似于您可能使用过的其他IDL。它允许您定义客户端和服务商定的编程接口,以便使用进程间通信(IPC)相互通信。在Android上,一个进程无法正常访问另一个进程的内存。所以说说,他们需要将对象分解为操作系统可以理解的基元,并为您跨越该边界编组对象。执行编组的代码编写起来很繁琐,因此Android使用AIDL为您处理它。
注意:仅当您允许来自不同应用程序的客户端访问IPC服务并希望在您的服务中处理多线程时,才需要使用AIDL。如果您不需要跨不同的应用程序执行并发IPC,则应通过实现Binder来创建接口,或者,如果要执行IPC,但不需要处理多线程,请使用Messenger实现接口。无论如何,在实施AIDL之前,请确保您了解绑定服务。
在开始设计AIDL接口之前,请注意对AIDL接口的调用是直接函数调用。您不应该对发生调用的线程做出假设。根据调用是来自本地进程中的线程还是远程进程,会发生什么情况会有所不同。特别:
- 从本地进程进行的调用在进行调用的同一线程中执行。如果这是您的主UI线程,则该线程继续在AIDL接口中执行。如果它是另一个线程,那就是在服务中执行代码的线程。因此,如果只有本地线程正在访问该服务,您可以完全控制在其中执行哪些线程(但如果是这种情况,那么您根本不应该使用AIDL,而应该通过实现Binder来创建接口) )。
- 来自远程进程的调用将从平台在您自己的进程内维护的线程池中调度。您必须为来自未知线程的传入呼叫做好准备,同时发生多个呼叫。换句话说,AIDL接口的实现必须完全是线程安全的。
- oneway关键字修改远程调用的行为。使用时,远程呼叫不会阻止;它只是发送交易数据并立即返回。接口的实现最终将此作为来自Binder线程池的常规调用作为普通远程调用接收。如果单向使用本地呼叫,则没有影响,呼叫仍然是同步的。
定义AIDL接口
您必须使用Java编程语言语法在.aidl文件中定义AIDL接口,然后将其保存在托管服务的应用程序和绑定到服务的任何其他应用程序的源代码(在src /目录中)中。
当您构建包含.aidl文件的每个应用程序时,Android SDK工具会生成基于.aidl文件的IBinder接口,并将其保存在项目的gen /目录中。该服务必须适当地实现IBinder接口。然后,客户端应用程序可以绑定到服务并从IBinder调用方法来执行IPC。
要使用AIDL创建服务,请按照下列步骤操作:
1.创建.aidl文件
此文件使用方法签名定义编程接口。
2.实现接口
Android SDK工具基于.aidl文件生成Java编程语言的接口。此接口有一个名为Stub的内部抽象类,它扩展了Binder并实现了AIDL接口的方法。您必须扩展Stub类并实现这些方法。
3.将接口公开给客户端
实现一个Service并重写onBind()以返回Stub类的实现。
警告:首次发布后对AIDL接口所做的任何更改都必须保持向后兼容,以避免破坏使用您服务的其他应用程序。也就是说,因为必须将.aidl文件复制到其他应用程序才能访问服务的接口,所以必须保持对原始接口的支持。
1.创建.aidl文件
AIDL使用一种简单的语法,允许您使用一个或多个可以获取参数和返回值的方法声明接口。参数和返回值可以是任何类型,甚至是其他AIDL生成的接口。
您必须使用Java编程语言构造.aidl文件。每个.aidl文件必须定义单个接口,并且只需要接口声明和方法签名。
默认情况下,AIDL支持以下数据类型:
- Java编程语言中的所有原始类型(例如int,long,char,boolean等)
String
CharSequence
List
List中的所有元素必须是此列表中支持的数据类型之一,或者是您声明的其他AIDL生成的接口或parcelables之一。列表可以可选地用作“通用”类(例如,List<String>)。另一方接收的实际具体类始终是ArrayList,尽管生成该方法以使用List接口。
Map中的所有元素必须是此列表中支持的数据类型之一,或者是您声明的其他AIDL生成的接口或parcelables之一。不支持通用映射(例如Map<String,Integer>形式的映射)。另一方接收的实际具体类始终是HashMap,尽管生成的方法是使用Map接口。
您必须为上面未列出的每个其他类型包含import语句,即使它们与您的接口在同一个包中定义。
定义服务接口时,请注意:
- 方法可以接受零个或多个参数,并返回值或void。
- 所有非原始参数都需要一个方向标记来指示数据的传输方式。 in,out或inout(参见下面的示例)。
原始类型默认为in。
警告:您应该将方向限制为真正需要的方向,因为编组参数很昂贵。
- .aidl文件中包含的所有代码注释都包含在生成的IBinder接口中(import和package语句之前的注释除外)。
- 仅支持方法;您无法在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);
}
只需将.aidl文件保存在项目的src /目录中,当您构建应用程序时,SDK工具会在项目的gen /目录中生成IBinder接口文件。生成的文件名与.aidl文件名匹配,但扩展名为.java(例如,IRemoteService.aidl会生成IRemoteService.java)。
如果您使用Android Studio,则增量构建几乎会立即生成binder类。如果您不使用Android Studio,那么Gradle工具会在您下次构建应用程序时生成binder类 - 您应该在编写.aidl文件后立即使用gradle assembleDebug(或gradle assembleRelease)构建项目,这样您的代码可以链接到生成的类。
2.实现接口
构建应用程序时,Android SDK工具会生成一个以.aidl文件命名的.java接口文件。生成的接口包括一个名为Stub的子类,它是其父接口的抽象实现(例如,YourInterface.Stub),并声明.aidl文件中的所有方法。
注意:Stub还定义了一些辅助方法,最明显的是asInterface(),它接受IBinder(通常是传递给客户端的onServiceConnected()回调方法的方法)并返回存根接口的实例。有关的更多详细信息,请参阅调用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)的一个实例,它定义了服务的RPC接口。在下一步中,此实例将向客户端公开,以便它们可以与服务进行交互。
在实现AIDL接口时,您应该注意一些规则:
- 传入的调用不能保证在主线程上执行,因此您需要从一开始就考虑多线程并正确构建您的服务以保证线程安全。
- 默认情况下,RPC调用是同步的。如果你知道服务需要几毫秒来完成一个请求,你不应该从activity的主线程中调用它,因为它可能会挂起应用程序(Android可能会显示“应用程序没有响应”对话框) - 你应该通常从客户端的单独线程中调用它们。
- 您抛出的异常不会被发送回调用者。
3.将接口公开给客户端
一旦为服务实现了接口,就需要将其公开给客户端,以便它们可以绑定到它。要公开服务的接口,请扩展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
}
};
}
现在,当客户端(例如活动)调用bindService()连接到此服务时,客户端的onServiceConnected()回调接收服务的onBind()方法返回的mBinder实例。
客户端还必须能够访问接口类,因此如果客户端和服务位于不同的应用程序中,则客户端的应用程序必须在其src /目录中生成.aidl文件的副本(生成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。
- 向您的类添加一个名为CREATOR的静态字段,该类是实现Parcelable.Creator接口的对象。
- 最后,创建一个声明您的parcelable类的.aidl文件(如下面的Rect.aidl文件所示)。
如果您使用的是自定义生成过程,请不要将.aidl文件添加到您的构建中。与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上的其他方法,查看可以写入包的其他类型的值。
警告:不要忘记从其他进程接收数据的安全隐患。在这种情况下,Rect从包中读取四个数字,但是由您来确保这些数字在调用者尝试执行的任何值的可接受范围内。有关如何确保应用程序免受恶意软件攻击的详细信息,请参阅安全性和权限。
调用IPC方法
以下是调用类调用AIDL定义的远程接口必须采取的步骤:
- 在项目src /目录中包含.aidl文件。
- 声明IBinder接口的实例(基于AIDL生成)。
- 实现ServiceConnection。
- 调用Context.bindService(),传入ServiceConnection实现。
- 在onServiceConnected()的实现中,您将收到一个IBinder实例(称为服务)。调用YourInterfaceName.Stub.asInterface((IBinder)服务)将返回的参数强制转换为YourInterface类型。
- 调用您在接口上定义的方法。您应始终捕获DeadObjectException异常,这些异常在连接断开时抛出;这将是远程方法抛出的唯一例外。
- 要断开连接,请使用您的接口实例调用Context.unbindService()。
关于调用IPC服务的一些注释:
- 对象是跨进程的引用计数。
- 您可以将匿名对象作为方法参数发送。
有关绑定到服务的更多信息,请阅读“绑定服务”文档。
下面是一些示例代码,演示调用AIDL创建的服务,取自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.
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);
}
}
};
}