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.aidlpackage 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 = newParcelable.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);      }    }  };}


更多相关文章

  1. Android下如何卸载和格式化sdcard
  2. java中org.xml.sax不能读取xml回车换行的问题解决(android)
  3. 解析Android中的Context
  4. Android(安卓)中的Java跟C/C++的Binder通信
  5. Android(安卓)异步更新UI----handler+thread
  6. Android(安卓)数据库的简单使用
  7. Android(安卓)中如何调节 TextView 的字间距
  8. Android(安卓)Thread线程
  9. Android(安卓)彻底关闭WebView,防止WebView造成OOM

随机推荐

  1. Onvif协议及其在Android下的实现
  2. Android将Service服务打包jar供三方调用
  3. android服务和进程详解
  4. Android拖动相片特效
  5. 挑战全网!最全Android面试知识点梳理。收
  6. icovn移植到android上
  7. Android快速开发框架dyh详解(三)---网络
  8. android系统中运行jar文件
  9. Android intent intent-filter 分类
  10. android room自动生成sql语句