[翻译]Android接口定义语言 (AIDL)

AIDL(Android接口定义语言)与你可能使用过的其它的IDLs是相似的。它容许你定义客户端与service协调一致的编程接口,以便于彼此之间使用进程间通讯(IPC)机制进行通讯。在Android上,一个进程一般不能访问另外一个进程的内存。能够说,它们须要把它们的对象分解为操做系统可以理解的原始数据类型,并在进程之间按次序排列对象。那种排列对象的代码写起来是很乏味的,所以Android经过AIDL来为你处理这些事情。html

注意:只有在你容许来自于不一样的应用的客户端访问你的service以实现IPC,并想要在你的service中处理多线程时,才须要使用AIDL。若是你不须要跨不一样应用执行并发IPC,你应该经过实现一个Binder来建立你的接口,或者若是你想要执行IPC,但不须要处理多线程,能够使用一个Messenger来实现你的接口。不管哪一种,请确保在实现一个AIDL以前,你理解了Bound Services。/java

在开始设计你的AIDL接口以前,请意识到对于一个AIDL接口的调用是直接的函数调用(大概指的是阻塞调用,在调用一个IPC方法时,客户端线程会一直等待,直到service端处理完成并返回)。你不该该假设调用所发生的线程。依赖于调用是来自于本进程的一个线程,仍是一个远端进程,则会发生不一样的事情。特别地:android

  • 发起于本进程的调用将在发起调用相同的线程中执行。若是这是你的UI线程,则线程将继续在AIDL接口中执行。若是它是另一个线程,那它将是执行你的service代码的线程。所以,若是只有本地线程访问service,你能够彻底控制在哪一个线程中来执行它(但若是是那种状况,则你不该该使用AIDL,而应该经过实现一个Binder建立接口)。编程

  • 调用来自于远端进程,并从平台维护的你本身的进程中的一个线程池派发。你必须为调用可能来自于未知线程作好准备,多个调用可能在同一时刻发生(Service是一个对象,而不是一个线程,这里的未知线程大概指的是binder线程,这里的意思大概是说,AIDL service类要按照线程安全类的标准来构造)。换句话说,一个AIDL接口的实现必须彻底是线程安全的。api

  • 单路行为的远程调用。当使用时,一个远程调用不发生阻塞;它仅仅是发送事务数据并当即返回。接口的实现最终将它做为来自于Binder线程池的一个普通远端调用来接收。若是单路被用于一个本地调用,则没有影响,调用依然是同步的。安全

定义一个AIDL接口

你必须使用Java编程语言语法在一个.aidl文件中定义你的AIDL接口,而后同时保存在service所在的应用和其余要bind到service的应用的源代码中(在src/目录下)。多线程

当你编译每一个包含.aidl文件的应用时,Android SDK工具会基于.aidl文件产生一个IBinder接口,并把它保存在项目的gen/目录下。Service必须适当的实现IBinder接口。客户端应用能够bind到service并调用来自于IBinder的方法来执行IPC。并发

要使用AIDL来建立一个bounded service,则听从如下几个步骤:app

  • 建立.aidl文件编程语言

    这个文件经过方法签名定义了编程接口。

  • 实现接口

    Android SDK工具基于.aidl文件以Java编程语言产生一个接口。这个接口具备一个名为Stub的内部抽象类,该抽象类扩展了Binder并实现了来自于你的AIDL接口的方法。你必须扩展Stub类并实现那些方法。

  • 将接口暴露给客户端

    实现一个Service并覆写onBind()来返回你的Stub类的实现。

注意:在你首次发布你的AIDL接口以后,对它的任何修改都必需要保持向后兼容,以免破坏使用了你的service的其余应用。即,因为你的.aidl文件必须被复制到其余的应用以使它们可以访问你的service的接口,你必须维护对于原始接口的支持。

1. 建立.aidl文件

AIDL使用了一种简单的语法供你声明一个接口,接口能够有一个或多个方法,每一个方法能够接收一些参数并返回值。参数和返回值能够是任何类型,甚至是其余的AIDL-generated接口。

你必须使用Java编程语言构造.aidl文件。每一个.aidl文件必须定义一个单独的接口,而且只须要接口声明和方法签名。

默认状况下,AIDL支持下述数据类型:

  • Java编程语言中的全部原始数据类型(好比int, long, char, boolean, 等等)

  • String

  • CharSequence

  • List

    List中的全部元素必须是所列出的被支持的数据类型,或某种其它的AIDL-generated接口,或你已经声明的parcelables。一个List可能被用做一个"generic"类(好比,List<String>)。另一边实际接收到的具体类老是一个ArrayList,尽管产生的方法使用List接口。

  • Map

    Map中的全部元素必须是所列出的被支持的数据类型,或某种其它的AIDL-generated接口,或你已经声明的parcelables。泛型maps,(好比那些形如Map<String,Integer>的maps)是不被支持的。另一边实际接收到的具体类老是一个HashMap,尽管产生的方法使用Map接口。

对于没有列出的每种额外的类型你都必须包含一个import声明,即便它们定义在与你的接口相同的package。

当定义你的service接口时,请意识到:

  • 方法能够接收0个或多个参数,并返回一个值或void。

  • 全部的非原始数据类型须要一个指示标记来代表数据的流向。in, out, 或者inout(请参考下面的例子)。

    原始数据类型默认是in,其余则不是。

    注意:你必须把方向限制在真正须要的方向,由于对于参数的排列是昂贵的。

  • .aidl文件中的全部代码注释被包含进了产生的IBinder接口中(除了那些在import和package声明前面的注释)。

  • 只支持方法;你不能在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 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文件保存在你的工程的src/目录下,当你编译你的应用时,SDK工具会在你的工程的gen/目录下产生IBinder接口文件。所产生的文件名与.aidl文件名匹配,但扩展名为.java(好比,IRemoteService.aidl产生IRemoteService.java)。

若是你使用Eclipse,增量编译几乎能够当即产生binder类。若是你不使用Eclipse,Ant工具在你下一次编译你的应用时产生binder类——你应该在你写完.aidl文件以后当即经过ant debug (或ant release) 编译你的工程,以使你的代码能够再次连接产生的类。

2. 实现接口

当你编译你的应用时,Android SDK工具产生一个以你的.aidl文件命名的.java接口文件。产生的接口包含一个名为Stub的子类,它是一个它的父接口的抽象实现 (好比,YourInterface.Stub),并声明了来自于.aidl文件的全部方法。

注意:Stub也定义了一些辅助方法,最值得注意的就是asInterface()了,它接收一个IBinder (一般是传递给客户端的onServiceConnected()回调方法的那个)并返回一个stub接口的实例。参考调用一个IPC方法部分,来获取更多关于如何作强制类型转换的信息。

要实现产生自.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) {
        // Does nothing
    }
};

如今mBinder是一个Stub类的实例(一个Binder),它为service定义了IPC接口。在下一步中,这个实例会被暴露给客户端,以使它们可以与service交互。

当实现你的AIDL接口时有一些规则应该注意:

  • 进入的调用不保证在主线程执行,所以你须要从一开始就考虑多线程,并适当的构建你的service以实现线程安全。

  • 默认状况下,IPC调用是同步的。若是你知道service须要好多毫秒的时间来完成一个请求,则你不该该在activity的主线程中调用,由于它可能挂起应用(Android可能显示一个"Application is Not Responding"对话框)——你一般应该在客户端的另一个线程中来调用它们。

  • 你throw的exceptions不会被发回调用者。

3. 将接口暴露给客户端

为你的service实现了接口以后,你须要把它暴露给客户端,以使它们能够bind到它。要将你的service暴露出来,则扩展Service并实现onBind()来返回一个你的实现了产生的Stub的类的实例(如前面的讨论)。这里是一个示例service,它将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()来链接这个service时,客户端的onServiceConnected()回调将接收由service的onBind()方法返回的mBinder实例。

客户端必须也有访问接口类的权限,所以,若是客户端和service在不一样的应用中,则客户端的应用必须具备一份.aidl文件的拷贝放在它的src/目录(其产生android.os.Binder接口——提供客户端访问AIDL方法的权限)下。

当客户端在onServiceConnected()回调中接收了IBinder,它必须调用YourServiceInterface.Stub.asInterface(service)来把返回的参数转换为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;
    }
};

更多示例代码,请参考ApiDemos中的RemoteService.java类。

透过IPC传递对象

若是你有一个类,你想要经过IPC接口将它从一个进程发送到另外一个进程,那么你能够那样作。然而,你必须确保你的类的代码在IPC通道的另外一端也是能够访问的,而且你的类必须支持Parcelable接口。支持Parcelable接口是很重要的,由于它容许Android系统来把对象分解为能够被跨进程处理原始数据类型。

要建立一个支持Parcelable协议的类,你必须作这些事情:

  1. 使你的类实现Parcelable接口。

  2. 实现writeToParcel,它将对象的当前状态写入一个Parcel

  3. 给你的类添加一个static成员CREATOR,它是一个对象,并实现了Parcelable.Creator接口。

  4. 最后,建立一个.aidl文件,它声明了你的parcelable类 (以下面的Rect.aidl文件所显示的那样)。若是你在使用一个定制的编译过程,则不要把.aidl文件添加到你的build。相似于C语言中的头文件,这个.aidl文件不被编译。

AIDL使用代码中它产生的这些方法和成员来排序和解序你的对象。

好比,这里是一个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协议的例子。

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的其它种类的值。

警告:不要忘记从其它进程接收数据的安全隐患。在这个例子中,RectParcel读取4个数字,但你要确保这些数字在可接受的值的范围之内,而不管调用者要去作什麽。参考Security and Permissions来获取更多关于如何使你的应用更安全并远离病毒的信息。

调用一个IPC方法

这里是一个调用类调用一个AIDL定义的远程接口所要采起的步骤:

  1. 在项目的src/目录下包含.aidl文件。

  2. 声明一个IBinder接口的实例(基于AIDL而产生)。

  3. 实现ServiceConnection

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

  5. 在你的onServiceConnected()实现中,你将接收一个IBinder实例(称为service)。调用YourInterfaceName.Stub.asInterface((IBinder)service)来将返回的参数转换为YourInterface类型。

  6. 调用你的接口中定义的方法。你应该老是捕捉DeadObjectException异常,当链接断开时会抛出这个异常;这是远程方法将会抛出的惟一的异常。

  7. 要断开链接,则经过你的接口的实例调用Context.unbindService()

关于调用IPC service的一些说明:

  • 对象是跨进程引用计数的。

  • 你能够发送匿名的对象做为方法参数。

更多关于binding到一个service的信息,请阅读Bound Services文档。

这里是一些实例代码,演示了调用一个AIDL-created service,来自于ApiDemos工程中的Remote 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.
            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);
            }
        }

    };
}

Done.

相关文章
相关标签/搜索