您的位置:首页 > 移动开发 > Android开发

Android Interface Definition Language (AIDL)

2014-11-09 20:16 363 查看
一. 本章主要讨论以下问题
    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);
            }
        }

    };
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息