Android接口定义语言(AIDL)
AIDL与其他一些你曾经使用过的IDLs一样。它允许你定义一些编程接口,这些接口是可以让已经达成一致协议的用户和服务通过进程间交互来相互调用的。
(注:只要你想要实现允许用户从其他应使用IPC获取你的服务然后发送多线程到你的服务中,你就应该使用AIDL。如果你觉得没有必要通过不同的应用程序去执行当前的IPC,你应该创立一个实现了Binder的接口;或者你想执行IPC,但是不想发送多线程,那就可以通过使用Messenger实现你的接口。但是无论怎样,你需要理解了Bound Services之后,才能良好的实现AIDL)
在你开始设计你的AIDL接口的时候,你需要清楚的意识到调用AIDL接口是一种直接的方法调用。你也没必要推测发生的线程调用。无论是从线程调用,还是从本地进程调用,甚至是从远程的进程调用都会让AIDL产生不同的结果。特殊地:
•在并发的线程中执行从本地进程执行的call方法。如果这是你的UI主线程,那么线程继续在AIDL接口中执行。如果是其他线程,那么这个线程就可以在service中执行你的代码。因此,只要本地线程与service相通,你就可以完全控制哪些线程在service中运行。
•从远程的线程调用,它们都不在一个线程池中。所以你必须准备好接收那些未知线程的调用,甚至是它们的同时调用。换句话说,你想实现AIDL接口一定要确保线程是安全的。
•oneway关键字修改远程调用的行为。当你使用这个关键字的时候,远程的调用不会被锁住,它仅仅发送交互数据然后立刻返回。实现这个接口最终会接收这个oneway作为一个常规的远程的从Binder线程池的调用。如果oneway关键字作为本地调用,那么会对程序本身没有影响,调用依然是同步的。
你一定要把你定义的AIDL接口放在.aidl文件中,而且还要用java语言编写。然后把这个文件保存在src/文件夹下,这样这个接口就可以使用这个应用所开启的服务和其他调用这个应用程序的服务。
每当你添加一个.aidl文件在里面,android SDK就会生成一个基于这个文件IBinder接口然后在工程的gen/目录下保存起来。Service必须适度的实现这个IBinder接口。然后用户的其他程序就可以绑定到这个service中并调用从IBinder来实现IPC
你需要如下几个步骤来实现AIDL:
创建.aidl文件:定义这个编程接口的签名
实现该接口:android SDK工具生成了JAVA编程语言的接口基于你的.aidl文件,这个接口有一个抽象内部类Stub继承了Binder并且实现从你AIDL接口的方法。所以,你要继承Stub类并且实现这个方法。
暴露接口:实现一个服务并重载onBind()方法作为你实现Stub类的返回值
(注:在你的服务首次释放的时候,你对于AIDL接口的每一次改动都会保持反向的兼容,这是为了避免打断其他使用你的服务的应用。那是因为,你的.aidl文件必须要拷贝到其他应用程序中这样它们才可以使用你的service,你一定要维持对根接口的支持)
1. 创建一个.aidl文件
AIDL使用简单的语法让你声明一个接口,参数和返回值。参数和返回值可以是任意值,甚至是其它AIDL-生成的接口。你一定要构造一个使用了java语言的.aidl文件,每一个.aidl文件一定要定义一个简单的接口并需要仅仅是接口的声明和方法的签名;
默认的情况下,AIDL支持如下文件格式:
·所有原始的变量,int,long,char,boolean等等
·String
·CharSequence
·List:所有在list中的元素,一定是以上变量的类型的某一种或者是其他AIDL接口或者是你声明的parcelables。一个list中可以随意的放一些泛型,list<String>。而实际的使用之中我们会在等号的右侧用一个ArrayList承接。
·Map:解释同上,等号左侧是MAP,等号右侧用HashMap承接;
你一定要使用import语句把包导入进来,即使他们作为你的接口被定义在同一个包中。
当你定义service接口的时候,需要注意:
·方法可以接收0至多个参数,返回值或者不返回;
·所有的自定义参数都要指向性的标记来说明数据的流向。In,out或者inout,指定方向其实是十分必要的
·生成IBinder接口的时候所有包含在.aidl的注释代码也将会被生成
·只有方法是可以暴露的,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 thingswith 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, floataFloat,
double aDouble, String aString);
}
1. 实现接口
当你建立应用程序的时候,android SDK工具生成一个.java接口文件。生成的接口包括Stub的子类(YourInterface.Stub)并且从.aidl文件中声明所有的方法。
为了实现.aidl接口,继承Binder interface(例如YourInterface.Stub)并且实现那些继承了.aidl文件的方法。代码如下:
private final IRemoteService.StubmBinder = 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类的一个实例化,该实例化给service定义RPC接口。接下来,把这个实例暴露给用户,这样他们就能够通过服务相互交互了。
在实现AIDL接口的时候,你需要认识到如下几个规则:
·外部的调用不一定能保证是在主线程中运行的,所以你需要考虑多线程时合理的绑定servcie以达到线程安全
·默认的情况下,PRC的同步的,如果你有一个十分费时操作,就不应该放在主线程中,时间长了有可能发生ANR。
·你的回调方法应该不存在异常
2. 给用户暴露接口
一旦你为你的服务实现了接口,你就需要把它暴露给用户,这样他们就能绑定到这个接口上并调用你的服务,继承service和实现onBind(),并把实现的返回值作为实例化Stub类的对象。具体请看下述代码:
public class RemoteServiceextends 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, booleanaBoolean,
float aFloat, double aDouble, String aString){
// Does nothing
}
};
}
现在,当一个用户(比如activity)调用bindService()方法来连接到这个service,它的onServiceConnected()回调方法接收service的onBind()方法的返回值作为mBinder的实例化对象。用户必须也要有连通interfance类的方法,所以若用户和服务不在同一个应用程序中,用户应用程序必须备份一份.aidl文件在src/文件夹下。
当用户服务接收到在onServiceConnected()方法中的返回值IBinder时,它必须调用YourServiceInterface.Stub.asInterface(serivce)来传递一个返回值,例子如下:
IRemoteServicemIRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
// Called when the connection with the service is established
public void onServiceConnected(ComponentName className, IBinderservice) {
// Following the example above for an AIDLinterface,
// 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 disconnectsunexpectedly
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "Service has unexpectedlydisconnected");
mIRemoteService = null;
}
};
通过IPC传递对象
如果你有一个类,然后你想把它通过IPC接口从一个进程传递到另一个进程,你是可以这么做的。然而,你必须要保证你的代码在IPC通道的另一端也要好用,而且,你的类必须支持Parcelable接口。支持Parcelable接口是非常重要的,因为它允许androidx系统分解对象并可以通过进程间进行分发。
为了支持Parcelable接口,你要做到以下几件事:
·保证你的类实现Parcelable接口
·实现writeToParcel,保证它可以获取当前对象的状态并把它写入到Parcel
·在你的类中添加一个静态的变量CREATOR作为对象,用于实现Parcelable.Creator接口
·最后,创建一个.aidl文件声明你的parcelable类。
在代码中,AIDL使用这些方法和变量,并且由它来管理是否分发你定义的对象。
如下例,有一个Rect.file文件创建一个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();
}
}
调用IPC方法:
下面的几句话用于说明一个类要调用远程的定义了AIDL接口的步骤:
1. src/文件夹下使用.aidl文件
2. 声明IBinder接口的实例化对象
3. 实现ServiceConnection
4. 调用Context.bindService(),传送到你的ServiceConnection的实现
5. 在你的onServiceConnected()的实现的类里,你会接收到IBinder的实例,调用YourInterfaceName.Stub.asInterface((IBinder)service)来传递返回值给你的YourInterface类型
6. 调用你定义的接口的方法
7. 通过你的接口的实例化对象调用Contex.unbindService()用于断开连接
下面这个代码是展示使用AIDL创建的service,然后被其他的service取走这个service的数据的Demo:
public static classBinding 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 upthe 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 theservice.
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentNameclassName,
IBinder service) {
// This is called when the connectionwith the service has been
// established, giving us the serviceobject 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 theraw service object.
mService = IRemoteService.Stub.asInterface(service);
mKillButton.setEnabled(true);
mCallbackText.setText("Attached.");
// We want to monitor the service foras long as we are
// connected to it.
try {
mService.registerCallback(mCallback);
} catch (RemoteException e) {
// In this case theservice has crashed before we could even
// do anything with it;we can count on soon being
// disconnected (andthen reconnected if it can be restarted)
// so there is no needto do anything here.
}
// As part of the sample, tell theuser what happened.
Toast.makeText(Binding.this, R.string.remote_service_connected,
Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentNameclassName) {
// This is called when the connectionwith the service has been
// unexpectedly disconnected -- thatis, its process crashed.
mService = null;
mKillButton.setEnabled(false);
mCallbackText.setText("Disconnected.");
// As part of the sample, tell theuser what happened.
Toast.makeText(Binding.this, R.string.remote_service_disconnected,
Toast.LENGTH_SHORT).show();
}
};
/**
* Class for interacting with the secondary interface of theservice.
*/
private ServiceConnection mSecondaryConnection = new ServiceConnection(){
public void onServiceConnected(ComponentNameclassName,
IBinder service) {
// Connecting to a secondaryinterface is the same as any
// other interface.
mSecondaryService = ISecondary.Stub.asInterface(service);
mKillButton.setEnabled(true);
}
public void onServiceDisconnected(ComponentNameclassName) {
mSecondaryService = null;
mKillButton.setEnabled(false);
}
};
private OnClickListener mBindListener = new OnClickListener() {
public void onClick(View v) {
// Establish a couple connectionswith the service, binding
// by interface names. Thisallows other applications to be
// installed that replace the remoteservice 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 receivedthe service, and hence registered with
// it, then now is thetime to unregister.
if (mService != null) {
try {
mService.unregisterCallback(mCallback);
} catch (RemoteExceptione) {
// There is nothing special we need to do if the service
// has crashed.
}
}
// Detach our existingconnection.
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 ourservice, we need to know its
// PID. Conveniently ourservice has a call that will return
// to us that information.
if (mSecondaryService != null) {
try {
int pid =mSecondaryService.getPid();
// Notethat, though this API allows us to request to
// killany process based on its PID, the kernel will
// stillimpose standard restrictions on which PIDs you
// areactually able to kill. Typically this means only
// theprocess 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 (RemoteExceptionex) {
//Recover gracefully from the process hosting the
// serverdying.
// Justfor 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 theremote
* service.
*/
private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub(){
/**
* This is called by the remote serviceregularly to tell us about
* new values. Note that IPC calls aredispatched through a thread
* pool running in each process, so the codeexecuting here will
* NOT be running in our main thread like mostother things -- so,
* to update the UI, we need to use a Handlerto 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);
}
}
};
}
读者个人的理解:我们编写的大部分程序是用不到AIDL的,但是AIDL却是两个进程间通信方法的最终解决方案。也就是说,如果我们可以使用其他的方法实现进程间的通信,我们就用别的方法。例如下一篇我们即将要讲的Bound Service和其他朋友介绍给我的broadcastReceiver(待验证)。