StupidBeauty
Read times:1716Posted at: - no title specified

安卓开发文档翻译:安卓接口定义语言(AIDL),Android Interface Definition Language (AIDL)

妳可能用过其它的接口定义语言(IDL),而安卓接口定义语言(AIDL (Android Interface Definition Language))与它们类似。利用这个,可以定义出客户代码和服务代码在进行进程间通信(IPC)时所遵循的编程接口。一般情况下,在安卓上,一个进程无法访问另一个进程的内存。因此,要想互相通信的话,它们需要将自己的一些对象以操作系统能够理解的形式暴露出来,并由系统将那些对象在进程间传递。相关的代码写起来狠麻烦,因此,安卓通过AIDL 来帮助妳处理这件事。

注意 仅在以下情况下,使用AIDL 才是必要的: 妳允许处于不同应用程序中的客户代码以进程间通信的形式访问到妳的服务,并且 ,想要在妳的服务中处理多线程。如果 妳不需要 在不同应用程序之间进行并发的进程间通信,则, 妳应当通过 实现 一个 Binder 来创建接口 ;或者,如果 妳想要进行进程间通信,但是, 需要处理多线程,则,应当 使用Messenger 来实现妳的接口。 不管怎样,在实现AIDL 之前,应当先理解 被绑定的服务

在开始设计妳的AIDL 接口之前,请注意,对于一个AIDL 接口的调用,是直接的函数调用。妳不应当对于调用所发生于其中的线程作出任何的假设。取决于该调用是来自于本地进程中的某个线程还是来自于某个远端进程,具体发生的事情是不同的。特别地:

  • •. 来自本地进程 的调用,会在发起该调用的同一个线程中执行。 如果这就是妳的主用户界面线程,则,该线程会继续执行该AIDL 接口。如果 它是另一个线程,则, 会在那个线程中执行妳的服务中的代码。因此 ,如果只有本地线程在访问该服务的话,那么, 妳完全可以控制让它在哪些线程中执行 (但是,如果 真是那种情况的话, 妳根本就不需要使用 AIDL 了, 而应该通过 实现Binder 来创建该接口 )

  • •.来自一个远端进程的调用,会由平台在妳自己的进程中维护的一个线程池来分发。妳必须作好准备,传入的调用可能来自未知的线程,并且多个调用可能同时发生。换句话说,对于AIDL接口的实现,必须是线程安全的。

  • •. oneway 关键字 会修改远端调用的行为。如果 妳使用该选项,则,远端调用不会被阻塞; 它只会简单地发送事务数据并且立即返回。 此接口的实现代码,最终会从 Binder 线程池以一个普通的远端调用的形式接收到它。如果 是在一个本地调用中使用了 oneway ,则,不会有影响,该调用仍然是同步的。

定义一个AIDL接口

妳必须使用Java 编程语言 的语法在一个 .aidl 文件中定义妳的AIDL 接口,然后 在提供该服务的应用程序和将要绑定到该服务的其它应用程序的源代码( src/ 目录)中保存该文件。

当妳编译每个包含了该 .aidl 文件的应用程序时,安卓 SDK 中的工具会根据 .aidl 文件生成一个 IBinder 接口,并且 将该接口文件保存在项目的 gen/ 目录中。 该服务必须适当地实现 IBinder 接口。然后 ,客户代码程序 可以绑定到该服务,并调用 IBinder 中的方法,以进行进程间通信。

要使用AIDL 创建一个绑定的服务,则,按照以下步骤来做:

  1. 1. 创建 .aidl文件

    该文件使用方法特征来定义编程接口。

  2. 2. 实现 该接口

    安卓SDK 中的工具,会根据妳的 .aidl 文件来生成 Java 语言的接口。 该接口有一个名为 Stub 的内部抽象类, 该抽象类继承了 Binder ,并且实现 了妳的AIDL 接口中的方法。 妳必须继承该 Stub 类,并且实现其中的方法。

  3. 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 可被用作一个“泛型”类(例如, List<String> )。 在另一端看到的实际类一定是一个 ArrayList ,尽管 生成的该方法使用的是 List 接口。

  • •. Map

    对于 Map 中的所有元素,必须 是: 以上列表中 的被支持的数据类型中的某一种;或者, 妳所声明的其它的由AIDL 生成的接口或可打包对象(parcelables)。通用映射 ,( 例如 Map<String,Integer>  a 这种形式的),是不被支持的。 在另一端看到的实际类一定是一个 HashMap ,即便生成的该方法使用的是 Map 接口也是如此。

妳必须针对未在上面列表中列出的每个额外的类型包含一条 import 语句,即便它们 是在与妳的接口相同的包中定义的也是如此。

在定义服务接口时,注意:

  • •.方法可以拥有0到多个参数,且返回一个值或空(void)。

  • •. 所有 的非基本类型的参数,都需要带上一个方向标记,指明该数据的流向。可以是 in out inout (参考下面 的示例 )。

    基本类型,默认情况 下的方向是 in ,并且不允许具有其它方向。

    警告: 妳应当限制各个参数的方向,只使用那些确实用得上的方向,因为, 跨进程传递参数是非常昂贵的。

  • •. 在该 .aidl 文件中包含的所有代码注释,都会包含在所生成的 IBinder 接口中( 除了那些在 import package 语句前面的注释以外 )。

  • •.只支持方法;妳无法将静态字段暴露到AIDL 中。

以下是一个 .aidl 示例文件:

// IRemoteService.aidl

package com . example . android ;

// 使用import 语句来声明那些非默认的类型

/** 示例服务接口 */

interface IRemoteService {

/** 请求获取 此服务的进程编号,以便做些坏事。 */

int getPid ();

/** 展示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 )。

如果 妳使用 Eclipse ,那么,增量构建功能 几乎会立即生成该绑定类。如果 妳不是使用的 Eclipse ,则, Ant工具 会在妳下次构建自己的应用程序时生成该绑定类—— 妳应当在写完了 .aidl 文件之后立即使用 ant debug ant release )来构建妳的项目,这样,妳的其它代码就可以链接至所生成的类了。

2. 实现该接口

当妳构建妳的应用程序的时候,安卓 SDK工具 会生成一个与该 .aidl 文件同名的 .java 接口文件。 所生成的接口中,包含了一个名为 Stub 的子类, 它是对于其亲代接口的一个抽象实现(例如, YourInterface.Stub ),声明 了该 .aidl 文件中的所有方法。

注意 Stub 还定义了一些辅助方法,其中 最值得注意的是 asInterface() 它需要 一个 IBinder 参数(通常 就是被传递给客户代码的 onServiceConnected() 回调方法的那个参数 ),并且返回 该根(stub)接口的一个实例。参考 调用 一个进程间通信方法 小节 ,以了解如何进行转换。

要想实现由该所 .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 ) {

// 什么 都不干

}

};

现在, mBinder 就是 Stub 类的一个实例( 一个 Binder )了, 它为该服务定义了远端进程调用接口。 下一步,将这个实例暴露给客户代码,这样,它们就可以与该服务交互了。

在实现妳的AIDL 接口时,要注意以下事项:

  • •.传入的调用,并不一定会是在主线程中执行的,因此,妳需要从一开始就考虑好多线程的事项,将妳的服务实现成线程安全的。

  • •.默认情况下,远端进程调用是同步的。如果妳知道该服务无法在几毫秒内就处理完某个请求的话,则,不应该在妳的活动(activity)的主线程中调用它,因为这可能导致该应用程序挂起(安卓可能会显示出一个"应用程序未响应"对话框)——妳应当在客户代码中的单独线程中调用它。

  • •.妳所抛出的异常不会被回发给调用者。

3. 将该接口暴露给客户代码

当妳为妳的服务实现好了接口之后,就需要将它暴露出来,以便让客户代码能够绑定到它。 要想为妳的服务暴露该接口的话,则,继承 Service ,并且实现 onBind() ,让它返回妳的那个实现了所生成的 Stub 接口( 上一小节已经讨论过 )的类的实例。 以下是一个示例服务, 它将 IRemoteService 示例接口暴露给客户 代码。

public class RemoteService extends Service {

@Override

public void onCreate () {

super . onCreate ();

}

@Override

public IBinder onBind ( Intent intent ) {

// 返回 该接口

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 ) {

// 什么 都不做

}

};

}

现在,每当 有哪个客户代码(例如某个活动 (activity) )调用 bindService() 以连接到这个服务的时候, 该客户代码的 onServiceConnected() 回调函数中会接收到该服务的 onBind() 方法所返回的那个 mBinder 实例。

客户代码 也必须能够访问到该接口类,因此,如果客户代码 和服务代码处于不同的应用程序中的话,则,客户代码所在 的应用程序必须 将该 .aidl 文件 它会生成 android.os.Binder 接口——使得客户代码能够访问 到该 AIDL 中的方法 复制一份到自己的 src/ 目录中。

当客户代码在 onServiceConnected() 回调函数中接收到该 IBinder 时,必须调用 YourServiceInterface .Stub.asInterface(service) 来将所返回的参数转换成 YourServiceInterface 类型。例如:

IRemoteService mIRemoteService ;

private ServiceConnection mConnection = new ServiceConnection () {

// 当与服务建立时,会调用此方法

public void onServiceConnected ( ComponentName className , IBinder service ) {

// 按照前面 示例代码 来获取一个AIDL 接口,

// 将获取到一个IRemoteInterface 实例, 我们可以用它来对该服务进行调用

mIRemoteService = IRemoteService . Stub . asInterface ( service );

}

// 当与服务的连接意外断开时,会调用此回调方法

public void onServiceDisconnected ( ComponentName className ) {

Log . e ( TAG , "服务意外断开" );

mIRemoteService = null ;

}

};

欲观摩更多示例代码,则参考 ApiDemos 中的 RemoteService.java 类。

在进程间通信过程中传递对象

如果 妳想要通过进程间通信接口将某个类从一个进程传递到另一个进程的话,首先告诉妳, 是可以实现的。 但是,必须确保,妳的类所对应的代码,在进程间通信的另一端也能访问到,并且妳的类必须支持 Parcelable 接口。必须 要支持 Parcelable 接口,这样,安卓系统才能将那些对象分解成一些基本类型的数据,再进行跨进程的传递。

要创建一个支持 Parcelable 协议的类,必须按以下步骤进行:

  1. 1. 让妳的类实现 Parcelable 接口。

  2. 2. 实现 writeToParcel ,它会获取该对象当前的状态,并写入到 一个 Parcel 中。

  3. 3. 向妳的类中加入一个名为 CREATOR 的静态字段, 它应当是一个实现了 Parcelable.Creator 接口的对象。

  4. 4. 最后 ,创建一个声明了妳的可打包类的 .aidl 文件 (参考下面 Rect.aidl 文件

    如果 妳在使用一个自定义的构建过程,则, 不要 将该 .aidl 文件添加到妳的构建 程中。 与C 语言中的头文件类似的是, .aidl 文件不会被编译。

AIDL会使用它所生成的代码中的这些方法和字段来对妳的对象进行打包和解包。

例如, 以下是一个 Rect.aidl 文件, 它会创建出一个可被打包的 Rect 类:

package android . graphics ;

// 声明Rect,这样,AIDL就能找到它,并且知道它实现了parcelable 协议。

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 中的其它方法,以了解, 可向Parcel 写入的其它类型的值。

警告: 别忘记了, 从其它进程接收数据,是有安全隐患的。 在这个示例中, Rect Parcel 读取了4个数字, 而妳应当检查 以确保这些数字处于可接受的取值范围之内。参考 安全 性及权限 ,以了解,如何确保 妳的应用程序不受恶意软件的骚扰。

调用一个进程间通信方法

以下,是一个调用者类在调用AIDL 中定义的远端接口时必须采取的步骤:

  1. 1. .aidl 文件包含到项目的 src/ 目录中。

  2. 2. 声明 一个用来储存 IBinder 接口(基于AIDL 生成 )的实例。

  3. 3. 实现 ServiceConnection

  4. 4. 调用 Context.bindService() ,将妳的 ServiceConnection 实现传入。

  5. 5. 在妳的 onServiceConnected() 实现中,会接收到一个 IBinder 实例( 名为 service )。调用 YourInterfaceName .Stub.asInterface((IBinder) service ) ,以将返回的参数转换成 YourInterface 类型。

  6. 6. 调用 妳在自己接口中定义的那些方法。 妳应当捕获 DeadObjectException 异常, 当连接断开时,就会抛出这些异常; 这是远端方法会抛出的唯一的异常。

  7. 7. 要想断开连接,则,调用 Context.unbindService() ,传入妳的接口的实例。

一些注意事项:

  • •.对象是在进程间以引用计数的。

  • •. 妳可将匿名对象作为方法参数传递。

欲知更多关于绑定 到一个服务的信息,则阅读 绑定 的服务 文档。

以下是一些示例代码,演示的是,调用一个通过AIDL 创建的服务。这些代码取自于ApiDemos 项目中的Remote Service 示例。

public static class Binding extends Activity {

/** 我们要对该服务进行调用的主要接口。 */

IRemoteService mService = null ;

/** 我们要对该服务进行调用的另一个接口。 */

ISecondary mSecondaryService = null ;

Button mKillButton ;

TextView mCallbackText ;

private boolean mIsBound ;

/**

* 本活动的标准初始化过程。设置 好用户界面,然后 ,等待用户戳它。

*/

@Override

protected void onCreate ( Bundle savedInstanceState ) {

super . onCreate ( savedInstanceState );

setContentView ( R . layout . remote_service_binding );

// 监听按钮 的点击事件。

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." );

}

/**

* 这个类用来与服务的主接口进行交互。

*/

private ServiceConnection mConnection = new ServiceConnection () {

public void onServiceConnected ( ComponentName className ,

IBinder service ) {

// 当成功连接到服务时,会调用此回调方法,

// 给我们返回服务对象,可使用该服务对象来与服务进行交互。

// 我们是在通过接口来与服务进行通信,因此,

// 从这里传入的原始服务对象取出一个客户代码侧的表示对象。

mService = IRemoteService . Stub . asInterface ( service );

mKillButton . setEnabled ( true );

mCallbackText . setText ( "Attached." );

// 我们需要在与该服务保持连接的过程中持续监听它。

try {

mService . registerCallback ( mCallback );

} catch ( RemoteException e ) {

// 如果代码 走到了这里,则说明,服务已经崩溃

// 可以预期,狠快就会断开连接

// (并且 在服务可被重启的情况下重新建立连接 )

// ,所以,此处不用做处理。

}

// 作为示例, 用户告知当前发生了什么。

Toast . makeText ( Binding . this , R . string . remote_service_connected ,

Toast . LENGTH_SHORT ). show ();

}

public void onServiceDisconnected ( ComponentName className ) {

// 如果 与服务之间的连接意外断开了,则会调用此回调方法——

// 这表明,服务所在的进程崩溃了。

mService = null ;

mKillButton . setEnabled ( false );

mCallbackText . setText ( "Disconnected." );

// 作为示例, 用户告知当前发生了什么。

Toast . makeText ( Binding . this , R . string . remote_service_disconnected ,

Toast . LENGTH_SHORT ). show ();

}

};

/**

* 用来 与服务的第二个接口进行交互的类。

*/

private ServiceConnection mSecondaryConnection = new ServiceConnection () {

public void onServiceConnected ( ComponentName className ,

IBinder service ) {

// 与第二个接口建立连接,就像使用其它接口一样。

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 ) {

// 与服务建立两个连接, 并按照接口名字来绑定。

// 这样,日后安装的程序的应用程序 可以通过实现相同的接口来替换掉远端服务

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 ( mService != null ) {

try {

mService . unregisterCallback ( mCallback );

} catch ( RemoteException e ) {

// 当服务崩溃时,我们不需要做什么。

}

}

// 断开连接

unbindService ( mConnection );

unbindService ( mSecondaryConnection );

mKillButton . setEnabled ( false );

mIsBound = false ;

mCallbackText . setText ( "Unbinding." );

}

}

};

private OnClickListener mKillListener = new OnClickListener () {

public void onClick ( View v ) {

// 要想杀死容纳着我们服务的进程,则,需要知道它的进程编号(PID)。

// 为了提供便利,我们的服务提供了一个方法,可以向我们告知这个信息。

if ( mSecondaryService != null ) {

try {

int pid = mSecondaryService . getPid ();

// 注意 ,尽管这个接口允许我们请求根据进程编号 来杀死任意进程,

// 但是,内核仍然 会遵循一些限制,

// 使得 妳只能杀死特定的进程编号。

// 一般情况下,这意味着,

// 只能杀死那个 将妳的应用程序运行于其中的进程, 以及, 由该应用程序创建的其它进程;

// 那些共享 同一个用户编号(UID)的软件包,也可以互相杀死对方的进程。

Process . killProcess ( pid );

mCallbackText . setText ( "Killed service process." );

} catch ( RemoteException ex ) {

// 从远端进程的死亡事件中平滑地恢复。

// 在此示例中,弹出一个通知。

Toast . makeText ( Binding . this ,

R . string . remote_call_failed ,

Toast . LENGTH_SHORT ). show ();

}

}

}

};

// ----------------------------------------------------------------------

// 如何处理回调方法

// ----------------------------------------------------------------------

/**

* 以下实现用来从远端服务接受回调事件。

*/

private IRemoteServiceCallback mCallback = new IRemoteServiceCallback . Stub () {

/**

* 这个方法会被远端服务定期调用,以向我们告知新的值。

* 注意 ,进程间通信所发起的调用, 是通过各个进程中运行的一个线程池来调度的,

* 因此 ,此处执行 的代码, 不会 像其它代码那样运行于我们的主线程——

* 因此 要想更新用户界面的话,

* 我们需要动用Handler。

*/

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 );

}

}

};

}

夕阳武士

Your opinions

Your name:Email:Website url:Opinion content:
- no title specified

HxLauncher: Launch Android applications by voice commands