Android Interface Definition Language (AIDL)

来源:互联网 发布:企业用电管理系统源码 编辑:程序博客网 时间:2024/04/29 21:55
一. 本章主要讨论以下问题
    1. 定义 ALDL 接口
        1.1 创建 .aidl 文件
        1.2 实现接口
        1.3 把接口暴露给 clients
    2. 通过 IPC 传递 Objects
    3. 调用 IPC 方法

    ALDL 即接口定义语言,也许和你所用的别的 IDLs 是类似的 。其提出是为了让你能够为 clients 和 service 定义一个能够让双方通信的 IPC 通信(interprocess communication)协议 。在 Android 中, 一个进程是不可以直接访问另一个进程的内存空间的 。也就是说,进程间要通信,就需要把它们的 objects 分解成操作系统所能识别的原始类型,并且要把 objects 中各原始的数据都划好边界了 。写这种分解 objects 的代码是非常麻烦的,容易出错的,所以 Android 引进了 AIDL ,用于帮助我们完成这种琐碎繁杂的事情 。
    注意:只有当你想让别的 app 能够通过 IPC 访问你的 service 或者如果你想在你的 service 中处理多线程的时候,你才应该考虑使用 AIDL 。如果你不需要处理不同 app 间的并发 IPC 通信,你可以通过实现 Binder 来处理进程间的通信 。或者如果你想实现 IPC ,但是不需要处理并发线程,那么你可以通过 Messenger 来实现 。在你实现 AIDL 前,请确保你已经理解好了 Bound Services ,不然容易出问题 。
    AIDL 接口的方法调用是直接的方法调用的,不要假设这种方法调用发生的时机 。本地进程调用 AIDL 接口的方法和远程进程调用 AIDL 接口的方法是不一样的:
    * 如果是本地进程执行的调用,那么哪个线程发起的调用,该调用的执行就在这个线程中执行 。所以,如果只有本地线程访问 service ,那么你就可以完全地控制由哪个线程来执行(但是如果是这种情况,那你就完全没必要使用 AIDL 了,用 Binder 就可以了)。
    * 由远程进程发起的调用,会被 thread pool 中的 thread 进行处理(这个 thread pool 是在你的 process 中的,由系统维护的) 。所以你必须随时准备好来自未知线程的并发调用,也就是说,实现 AIDL 接口必须注意线程安全问题 。
    * 关键字 oneway 会改变远过程调用的行为,如果使用了这个关键字,那么远程调用就是异步的,不会阻塞;它只是简单地把交互的数据发送出去,然后立即返回,这和普通的远程调用是一样的,实现接口最终会通过 Binder 接收到这个调用 。如果在本地调用使用了 oneway ,那它对调用就不会有什么影响 。
    
二. 定义 AIDL 接口
    定义 AIDL 接口的方式就是写一个 .aidl 文件,其语法是 Java 语言的语法是一样的 。然后把 .aidl 文件放到 service 端 src/ 目录下,同时还要放到 binds 到这个 service 端的 app 的目录下 。
    在你构建包含 .aidl 文件的 app 的时候,Android 的 SDK 工具会根据 .aidl 文件生成一个 IBinder 接口,并把它保存在 gen/ 目录下 。service 必须实现好 IBinder 接口,这样 client app 就可以 bind 到这个 service 上,然后通过 IBinder 调用相应方法实现 IPC 了 。
    通过 AIDL 创建一个 bounded service ,要经过以下步骤:
    1. 创建 .aidl 文件
    在这个文件中声明各个方法
    2. 实现接口
    Android 的 SDK 工具会根据 .aidl 生成相应的接口,这些接口都是由 Java 语言实现的 。在这个接口中,会有一个名为 Stub 的抽象类,该类继承了 Binder 并且 implements 了 AIDL 接口 。所以你必须继承 Stub 类并实现它的方法 。
    3. 把接口暴露给 clients
    实现一个 Service 并重写其 onBind() 方法,并在该方法中返回你实现的 Stub 类 。

三. 创建 .aidl 文件
    AIDL 使用简单的语法来声明接口,接口中可以有多个方法,这些方法可以带有多个参数,可以返回值 。参数和返回值的类型可以是任意的,甚至可以是其它的 AIDL 接口 。
    在定义 .aidl 文件时,要使用 Java 的语法来定义 。每个 .aidl 文件只能定义一个接口,而且只能声明方法(不能直接在 .aidl 中实现方法)。
    在默认情况下,AIDL 支持以下数据类型:
    * Java 中所有的基本类型(如 int、long、char、boolean等)、String、CharSequence、List 、Map
下面是一个 .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 文件后,要把它存放在 project 的 /src 目录下,这样当你构造 app 的时候,SDK 工具会在你的 project 中的 gen/ 目录下生成相应的 IBinder 接口 。生成的文件名和 .aidl 的文件名是一致的,只是后缀名为 .java 而已(如 IRemoteService.aidl 对应于 IRemoteService.java)
    如果你是用 Eclipse 来开发的应用,那么它会很快给自动生成相应的 binder 类 。

2. 实现接口
    在你构建 app 的时候,Android 的 SDK 工具会为你的 .aidl 生成相应的 .java 接口文件 。在这个 .java 文件中,包含一个名为 Stub 的抽象类,它 implements 了它的父接口,所以它包含了 .aidl 文件中声明的所有方法 。Stub 中也定义了一些辅助方法,如 asInterface() ,该方法返回一个 stub 的实例 。
    要实现 .aidl 生成的接口,就要继承生成的 Binder 接口(如 YourInterface.Stub)并实现它从 .aidl 继承的所有方法 。下面是一个实现 IRemoteService 的例子(其 .aidl 为刚刚上面提到的 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 类)的一个实例了,mBinder 是实现对 service 的 RPC 调用的基础 。只要把这个 mBinder 暴露给 clients ,clients 就可以通过它和 service 交互了 。
    在实现 AIDL 接口的时候,要注意以下几点:
    * service 端在收到调用请求时,并不能保证这些调用都会在主线程中执行,所以你要考虑如何把你的 serivce 变成线程安全的 。
    * 默认情况下,RPC 调用是同步的,所以如果你的调用请求需要较长时间,service 才能处理完,那么你就不应该在 activity 的主线程中发出这个请求,要不然可能会引起 ANR ,对于这种请求最好在另外的一个线程中发出 。
* 你在 serivce 端抛出的异常是不会被发回给 client 端的调用者的

3. 把接口暴露给 clients
    在你为 service 实现了相关接口后,你还需要把它暴露给 clients ,这样 clients 才能绑定到你的 service 。要把接口暴露给 clients ,就需要继承 Service 类实现实现其 onBind() 方法,并在该方法中返回一个实现了 Stub 的实例,下面的例子演示了把 IRemoteService 暴露给 clients 的办法:
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
       
}
   
};
}
    现在,当 client 端(如 activity)调用 bindService() 去连接到 service 的时候,client 端的 onServiceConnected() 回调方法就可以接收到由 service 端的 onBind() 方法返回的 mBinder 实例了 。
    client 端也是要访问接口类的,所以如果 client 和 service 是在不同的 app 中的,那么 client app 的 src/ 目录下就必须有相应 service 端的 .aidl 文件的拷贝,这样 client 端的 app 才能访问到 AIDL 中声明的方法 。当 client 在 onSerivceConnected() 回调方法中接收到 IBinder 的时候,它必须调用 YourServiceInterface.Stub.asInterface(service) 来把返回的参数转换成 YourServiceInterface 类型(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 传递 Objects
    你可以通过 IPC 把一个 class 从一个进程传递到另一个进程中,但是你必须保证你的 class 经过 IPC 传递到别的进程后仍然是可以使用的,并且你的 class 必须支持 Parcelable 协议 。如果一个类支持 Parcelable  协议,那么 Android 系统就可以把相应的 objects 分解成原始的数据,以使得这些数据可以被别的进程识别和使用。创建一个支持 Parcelable 协议的类的步骤如下:
1. 让类实现 Parcelable 接口
2. 实现 writeToPacel 方法,该方法用于把 object 写入一个 Parcel 中
3. 在你的类中添加一个名为 CREATOR 的静态变量,它是一个实现了 Parcelable.Creator 接口的对象
4. 最后,创建一个声明了你的 parcelable 类的 .aidl 文件(如下面给出的 Rect.aidl 文件)
    AIDL 就是使用上面这些方法和变量对 objects 进行编码和解码的 。下面是一个例子,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 协议

五. 调用 IPC 方法
    要能够实现对 AIDL 中声明接口的 IPC 调用,就要遵循以下几点:
1. 在 project 的 src/ 目录下包含 .aidl 文件
2. 声明 IBinder 接口的一个实例(这个 IBinder 接口是根据 AIDL 生成的)
3. 实现 ServiceConnection
4. 调用 Context.bindService() ,把你实现的 ServiceConnection 传递进去
5. 在 onServiceConnected() 方法中,会收到一个 IBinder 实例(称为 service)。你需要调用 YourInterfaceName.Stub.asInterface(IBinder)service) 来把它转型成 YourInterfacetype 。
6. 现在你可以调用在 service 端声明的方法了,在调用的时候你还需要捕获 DeadObjectException 异常,如果连接断开了,那么调用远程方法时会抛出这个异常的,这也是你需要捕获的唯一一个异常 。
7. 如果要断开连接,那么调用 Context.unbindService() 就可以了 。

    下面是个简单的例子,当然只是部分示例代码
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.
            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 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);
           
}
       
}

   
};
}

0 0
原创粉丝点击