Contents
Android AIDL. 1
定义AIDL接口... 1
1.创建.aidl文件... 2
2.实现接口... 4
3.暴露接口给客户端... 5
IPC之间传递对象... 7
调用IPC方法... 9
Android AIDL
Android AIDL跟其他IDL语言相似。AIDL定义一套client端和service端都支持的编程接口,供IPC使用。Android的进程无法访问另一个进程的数据。所以,需要把数据分解成操作系统能识别的基本数据类型穿过进程界限,接收一方在重新组装。这一过程比较枯燥,所以Android系统用AIDL为你实现了。
注意:只有别的应用程序需要通过进程间通信访问你的service的时候才需要AIDL。如果你不需要应用程序之间并发远程调用,可以用AIDL,实现相应接口的Binder。或者你需要IPC,但不想支持多线程,那么就用Messenger来实现service。
在你开始设计AIDL接口之前,首先要注意AIDL调用就是直接函数调用。你不能假设这个调用发生在哪个线程中。到底在哪个线程来执行取决于调用来自于本地进程还是远程进程。具体的说:
- 本地进程发起的调用仍在调用线程执行。如果在UI进程调用的AIDL接口,那么仍然在UI进程执行AIDL接口的code,如果在其他子线程执行调用,那么AIDL的代码就在子线程中执行。(如果是这种情况,那么你应该不需要AIDL,只需实现一个Binder就可以了。)
- 从远端应用程序调用过来,会从系统维护的本地进程交互线程池中取出一个线程来执行。你必须为不知道的线程调用准备好,也有可能很多线程同时调进来。也就是说AIDL接口的实现者必须是线程安全的。
- Oneway关键字修饰远程调用,更改调用行为。用oneway修饰的方法远程调用不阻塞,传输完数据直接返回。接口实现端就像一个正常普通远程调用一样接收调用。如果oneway修饰的方法被本进程调用,那么依然是象同步调用一样。
定义AIDL接口
首先用java编程语言定义一个.aidl扩展名的文件,保存在实现方和调用方源代码中(/src文件夹)下。
当你编译带.aidl文件的时候,Android SDK工具会基于.aidl生成一个IBinder接口,保存在项目的/gen路径下。服务端要实现这个IBinder接口。应用客户端绑定这一服务并通过IPC经由IBinder调用这些方法。
创建一个AIDL绑定的服务,需要这么几步:
- 创建.aidl文件
这个文件定义了编程接口和方法
- 实现接口
Android SDK工具基于.aidl文件生成接口文件。这个接口有一个名叫Stub的内部抽象类,Stub扩展了Binder并实现了AIDL接口中声明的方法。
- 暴露接口给客户端
实现一个Service重写onBind(),onBind()返回实现了Stub的类。
注意:AIDL接口在第一次发布之后,以后的任何改动都必须向后兼容。否则,可能会影响调用者的功能。因为.aidl必须复制到调用者的应用中,调用者才能使用你提供的服务,所以必须保证原始的接口的可用性。
1.创建.aidl文件
AIDL语法很简单,你可以声明接口,一个或多个方法,定义参数和返回值。参数和返回值可以是任意类型,甚至可以是其他AIDL生成的接口。
你只需用Java编程语言构建.aidl文件。每个.aidl文件定义一个单独的接口,并声明接口和方法。
默认情况下,AIDL支持下面几种文件类型:
- Java编程语言的所有基本数据类型(比如:int,long,char,boolean等等)
- String
- CharSequence
- List
- List中所有元素必须是以上列出的支持的类型或其他AIDL产生的接口或你已经声明的Parcelable。你可以选择性的使用更通用的类表示List(比如List)。尽管生成接口的时候声明的是List,在实际使用中对侧接收到的具体的类通常是ArrayList。
- Map
- Map中所有元素必须是以上列出的支持的类型或其他AIDL产生的接口或你已经声明的Parcelable。这里不支持通用的Map(比如:形如Map)。尽管生成接口的时候声明的是Map,在实际使用中对侧接收到的具体的类通常是HashMap。
不在以上列表的类型,即使跟借口在同一个包中,也需要使用import声明。
定义服务接口需要注意:
- 方法可以有一个或多个参数,也可以没有参数,可以有一个返回值,也可以返回空。
- 所有非基本数据类型需要一个方向声明,以表示数据流向。方向可以是:in, out或者inout(请看下面的例子)。
- 基本数据类型默认是in,不能是其他方向。
!注意:方向限制必须设置成真正需要的方向,因为数据编排开销很大。
- .aidl文件中所有代码的注释都被包含在生成的IBinder接口中(除了import和包语句前面的注释)。
- 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工具会自动生成IBinder接口并保存在你的项目的/gen路径下。生成的文件的文件名与.aidl相同,但扩展名是.java(比如:IRemoteService.aidl生成IRemoteService.java)。
如果你用Android Studio,增量编译几乎瞬间就能生成binder类。如果你不用Android Studio,Gradle工具会在你下次编译应用的时候生成binder类。你应该在完成.idle后马上用gradle assembleDebug(或gradle assembleRelease)编译以下你的工程,这样你的代码就可以链接到生成的类上了。
2.实现接口
当你编译你的应用时,Android SDK工具根据.aidl为你生成了同名的.java的接口文件。生成的文件包括一个名为Stub的子类,Stub是父类接口的抽象实现并声明了.aidl文件的所有方法。
注意:Stub还定义了一些辅助方法,最重要的是asInterface(),输入时一个IBinder(通常是客户端的回调onServiceConnected()里得到的),输出是一个Stub接口的实例。如何具体投射请看调用一个IPC方法。
为了实现从.aidl生成的接口,继承通用的Binder接口(例如:YourInterface.Stub)并实现继承自.aidl文件的方法。
这里有个匿名实例实现了名为IRemoteService(通过IRemoteService定义的)的接口的例子
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)类的一个实例了,mBinder定义了RPC接口的服务。下一步,暴露这个实例给客户端,以便让客户端和服务端交互。
实现AIDL接口的时候有几个规则需要注意:
- 函数调用并不一定在主线程执行,所以需要考虑多线程,把服务做成线程安全的。
- 默认情况下,RPC调用是同步的。如果你知道服务器的反应时间在毫秒级别,那么不要在主线程调用服务,否则可能会导致应用程序不反应(Android可能会显示ANR对话框),在调用这段你应该用一个单独的线程调用服务器。
- 服务端抛出的异常不会发送到调用一侧。
3.暴露接口给客户端
服务接口实现完后,需要把它暴露给客户端,这样客户端才能绑定它。暴露服务接口,只需要扩展Service组件并实现onBind(),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()会接收到从服务的onBind()方法返回的mBinder的实例。
客户端必须由权限访问接口类,这样如果客户端和服务端在单独的应用中,客户端的应用中/src目录下就必须有一个.aidl文件的复制(以便生成android.os.Binder接口,供客户端访问)。
客户端在回调函数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接口,Android系统才能将类对象组装成基本数据类型,以便在不同进程之间传输。
支持Parcelable接口的类需满足以下几点:
- 类实现Parcelable接口
- 实现writeToParcel,将当前的对象写入Parcel
- 类添加一个静态域CREATOR,这是一个实现了Parcelable.Creator的接口
- 最后,创建一个.aidl文件声明你的Parcelable类(就像下面的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, int flags) {
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();
}
public int describeContents() {
return 0;
}
} |
Rect类的组装很简单。关于其他数据类型的组装,详情请参考Parcel。
!注意:不要忘了从其他进程接收数据的安全问题。在这个例子中,Rect从Parcel中读出四个数字,不管调用方想做什么,你要保证这些数据的值在你可以处理范围内。如何保证应用安全,详情请看安全和权限。
调用IPC方法
这里是调用端通过AIDL接口调用服务的步骤:
- 引入.aidl文件到你的项目/src路径下
- 声明IBinder接口的一个实例
- 实现ServiceConnection
- 调用Context.bindService(),传入ServiceConnection的实现
- 在onServiceConnected()函数中,你会收到一个IBinder的实例。调用YourInterface.Stub.asInterface((IBinder)service)把参数转换成YourInterface类型。
- 调用在你的接口上定义的方法。你要负责捕获DeadObjectException,当连接断开的时候会抛出这个异常。你还要负责捕获SecurityException异常,参与IPC的两个进程在AIDL定义上有冲突的时候会抛出这个异常。
- 要断开服务,通过接口实例调用Context.unbindService()。
调用IPC服务的时候一些说明:
- 进程之间的对象是引用计数的。
- 可以通过方法参数传输匿名类。
这里有个AIDL调用的例子。
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 mHandler InternalHandler; 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."); mHandler = new InternalHandler(mCallbackText); }
/** * 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 static class InternalHandler extends Handler { private final WeakReference<TextView> weakTextView;
InternalHandler(TextView textView) { weakTextView = new WeakReference<>(textView); }
@Override public void handleMessage(Message msg) { switch (msg.what) { case BUMP_MSG: TextView textView = weakTextView.get(); if (textView != null) { textView.setText("Received from service: " + msg.arg1); } break; default: super.handleMessage(msg); } } } } |