[翻譯]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.

相關文章
相關標籤/搜索