天天看點

AIDL--Android 6.0開發者文檔一、前言二、定義AIDL接口三、通過程序間通信傳遞對象四、調用IPC方法

原文位址:AIDL

  • 一、前言
  • 二、定義AIDL接口
    • 2.1 建立.aidl檔案
    • 2.2 實作接口
    • 2.3 向調用方暴露接口
  • 三、通過程序間通信傳遞對象
  • 四、調用IPC方法

一、前言

ALDI(Android interface definition language)跟其他接口定義語言類似。你可以用它定義調用方與service之間的程式接口以完成程序間通信。在Android環境中,通常一個程序不能通路另一個程序的記憶體,這時就需要某種方式将對象分解為作業系統可以了解的原始資料,并由系統将這個對象傳遞給另一個程序,這種方式,就是AIDL。

在定義AIDL以前,你需要知道:對AIDL接口的調用不是直接的方法調用。而且被調用的方法處于的線程不一定就是調用方的線程:決定它們兩份是否處于同一線程的是它們是否在同一個程序。下面分三種情況進行叙述:

  • 如果是本程序内的調用,那麼被調用方會在同一個線程執行。隻要不是跨程序調用,那麼不管你是在UI線程還是其他線程調用AIDL方法,AIDL接口都會在調用方所在的線程執行。從這個角度看,如果你隻在本地線程通路bound service,那麼你可以完全控制此AIDL接口被哪個線程執行。但是,如果不是跨程序調用,最好不要用AIDL方式,直接實作Binder類即可,更簡單友善
  • 從remote程序的調用會被分發到由平台在你的程序維護的一個線程池中。你的service必須準備好從未知線程中接收請求,而且可能是同時進入的多個請求。換句話說,你的AIDL接口實作應該是線程安全的
  • 關鍵字

    oneway

    可以修改遠端調用的行為。如果你指定了此關鍵字,那麼遠端調用将不會阻塞:遠端調用會發送資料然後立刻傳回。而你的AIDL接口實作會從Binder線程池中收到格式化的調用資料。如果你在本地調用中指定了此關鍵字,那麼此關鍵字将不會起作用,你的調用仍然是同步的

二、定義AIDL接口

AIDL接口使用java文法編寫,儲存為.aidl檔案,放置在bound service所在應用和要綁定這個service的應用的源碼路徑下(src/路徑下)。

如果你的應用包含.aidl檔案,那麼當build時,Android SDK工具會基于你的.aidl檔案生成IBinder接口并儲存在項目的gen/目錄。你的service必須實作這個IBinder接口,然後調用方就可以通過這個IBinder接口與你的service做跨程序通信了。

要通過AIDL建立bound service,需要以下步驟:

  1. 建立.aidl檔案

    這個檔案定義了接口的函數原型

  2. 實作這個接口

    Android SDK工具會根據你的.aidl檔案生成一個java接口。這個接口有一個Stub内部抽象類,其繼承了Binder,并實作了你的AIDL接口。你必須繼承Stub類并實作相關方法

  3. 向調用方暴露接口

    實作bound service,并通過onBind()方法傳回Stub類的對象

注意:在AIDL接口釋出以後,你對它做的任何修改,都需要相容以前的版本,以避免使用你的service的應用崩潰

2.1 建立.aidl檔案

你可以在AIDL檔案中聲明接口、一個或多個方法、方法的參數和傳回值。其中,方法的參數和傳回值可以是任何類型,甚至是其他AIDL接口。

.aidl檔案使用的文法是java文法,其内容就是一個java接口。

預設情況下,AIDL支援以下資料類型:

  • java基本類型,如int,long,char,boolean等
  • String
  • CharSequence
  • List

    List中的所有元素都必須是目前聲明的資料類型的一部分、或者其他AIDL接口、或者你聲明的parcelable對象。List支援泛型。在調用方收到的總是ArrayList(雖然service聲明的是List)

  • Map

    Map中的所有元素都必須是目前聲明的資料類型的一部分、或者其他AIDL接口、或者你聲明的parcelable對象。Map不支援泛型。在調用方收到的總是HashMap(雖然service聲明的是Map)

對于上面沒有提到的其他類型,你需要通過import在AIDL檔案中聲明,即使它們在同一個包内。

在定義你的service接口前,你需要知道:

  • 方法可以沒有參數,也可以傳回空
  • 所有非基本類型參數需要聲明方向标記,可以是in,out或者inout。基本類型預設為in,但是可以聲明為其他

    注意:你應該根據實際需要設定這些方向标記,因為序列化這些參數非常消耗資源

  • .aidi檔案中的代碼注釋會移植到IBinder接口中(除import和包名之前的注釋)

下面是一個.aidl檔案的示例:

// IRemoteService.aidl
package com.example.android;
// 非預設類型資料需要在這裡通過import導入,即使它們在同一包中
/** service interface 示例 */
interface IRemoteService {
    /** 擷取service的process ID */
    int getPid();
    /** 此方法隻是為了說明你可以使用的基本資料類型,沒有特殊含義
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}
           

将.aidl檔案儲存在src/目錄,當build時,Android SDK工具會根據它生成IBinder接口,并儲存在gen/目錄。生成的檔案名跟.aidl檔案名相同,隻是字尾換為.java

2.2 實作接口

當build應用時,Android SDK工具會根據.aidl生成.java的接口。生成的接口包含一個Stub,其是一個抽象類,實作了父類接口,并聲明了所有.aidl檔案的方法。

注意:Stub還定義了一些有用的方法,如asInterface(),此方法接受一個IBinder(通常是傳遞到onServiceConnected()方法的那個IBinder),傳回一個stub接口的執行個體

你需要繼承生成的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
    }
};
           

在實作AIDL接口時,你需要知道以下知識:

  • 對接口方法的調用不一定在主線程,你需要考慮多線程情況,并使你的service線程安全
  • 預設情況下,遠端調用是同步的。如果你知道service需要一定時間才能完成請求,那麼你不應該在activity的主線程調用AIDL方法
  • 你抛出的異常不會被發送給調用方

2.3 向調用方暴露接口

暴露接口的方法為:繼承Service并實作onBind()方法,傳回你實作的繼承了Stub的類的執行個體。下面是一個示例,其向調用方暴露了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
        }
    };
}
           

然後,當調用方調用bindService()綁定service時,調用方的onServiceConnected()方法會收到service的onBind()方法傳回的mBinder執行個體。

由于調用方與service在不同的應用中,是以調用方要通路mBinder中的接口,還需要将src/路徑的.aidl檔案複制一份。

當調用方收到IBinder對象時,必須調用YourServiceInterface.Stub.asInterface(service)方法來将IBinder強轉為YourServiceInterface。示例如下:

IRemoteService mIRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
    // 當與service的連接配接建立成功時會調用此方法
    public void onServiceConnected(ComponentName className, IBinder service) {
        // 擷取IRemoteInterface執行個體,你可以通過它調用service的方法
        mIRemoteService = IRemoteService.Stub.asInterface(service);
    }
    // 當與service的連接配接意外中斷時調用
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "Service has unexpectedly disconnected");
        mIRemoteService = null;
    }
};
           

三、通過程序間通信傳遞對象

通過程序間通信,你可以在程序間傳遞對象。但是,你首先得確定你的類對于IPC通道的另一端是可見的,并且你的類需要實作Parcelable接口。實作Parcelable接口意味着允許Android系統将你的對象分解為原始資料以傳遞到另一個程序。

要建立支援Parcelable協定的類,你需要以下幾步:

  1. 實作Parcelable接口
  2. 實作writeToParcel方法,此方法會将對象的目前狀态寫入到Parcel中
  3. 添加靜态域CREATOR,其是實作了Parcelable.Creator接口的類的執行個體
  4. 建立.aidl檔案并聲明你的可序列化類

    如果你使用自己定制的build過程,那麼不要将.aidl檔案加入到你的build過程中。.aidl類似于C語言的頭檔案,是不會被編譯的

系統會根據你在AIDL中聲明的方法和域,序列化/反序列化你的對象。

下面是一個Rect.aidl檔案的示例,其聲明了一個可序列化的Rect類:

package android.graphics;
// 聲明Rect類,這樣AIDL才能找到它,并知道它已經支援了可序列化協定
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();
    }
}
           

四、調用IPC方法

調用方要調用AIDL中的遠端接口,需要實作以下步驟:

  1. 将.aidl檔案放置在src/目錄
  2. 聲明一個IBinder接口的執行個體(根據AIDL生成)
  3. 實作ServiceConnection
  4. 調用Context.bindService()并傳入ServiceConnection
  5. 在你的onServiceConnected()方法中,你會收到IBinder執行個體,調用YourInterfaceName.Stub.asInterface((IBinder)service)将傳回的IBinder強轉為YourInterface類型
  6. 調用YourInterface執行個體中的方法。當連接配接斷開時,這些方法會抛出DeadObjectException異常,你可以捕獲它們。DeadObjectException異常是遠端方法會抛出的唯一異常
  7. 通信完成後,使用Context.unbindService()方法解綁

調用IPC service的一些說明:

  • 對象的引用是跨程序的(Objects are reference counted across processes)
  • 你可以使用匿名對象作為參數

下面是調用基于AIDL的bound service的示例:

public static class Binding extends Activity {
    /** 我們将要調用的service的接口 */
    IRemoteService mService = null;
    /** service的另一個接口 */
    ISecondary mSecondaryService = null;
    Button mKillButton;
    TextView mCallbackText;
    private boolean mIsBound;
    /**
     * activity标準初始化過程:設定UI然後等待使用者與UI互動
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.remote_service_binding);
        //監聽button點選事件
        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) {
                // 在這裡,service在我們操作以前就崩潰了,我們很快會收到連接配接
                // 斷開回調,在那裡我們可以嘗試重連
            }
            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) {
            // 通過接口名稱與service建立連接配接。這樣的話其他應用可以通過使用
            // 相同的接口名稱來替換遠端service
            Intent intent = new Intent(Binding.this, RemoteService.class);
            intent.setAction(IRemoteService.class.getName());
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
            intent.setAction(ISecondary.class.getName());
            bindService(intent, 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) {
            // 要kill掉service所在的程序,需要知道此程序的id
            if (mSecondaryService != null) {
                try {
                    int pid = mSecondaryService.getPid();
                    // 注意,雖然你可以使用下面的API根據PID申請kill任何程序,
                    // 但是kernel會限制你實際上可以kill哪些程序。隻有你的應用
                    // 所在的程序、你的應用建立的附加程序、與你的應用有相同UID
                    // 的程序可以被你kill
                    Process.killProcess(pid);
                    mCallbackText.setText("Killed service process.");
                } catch (RemoteException ex) {
                    Toast.makeText(Binding.this,
                            R.string.remote_call_failed,
                            Toast.LENGTH_SHORT).show();
                }
            }
        }
    };
    // ----------------------------------------------------------------------
    // 處理回調的相關代碼
    // ----------------------------------------------------------------------
    /**
     * 這個接口用于擷取遠端service的回調
     */
    private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
        /**
         * 當遠端service要通知你資料有變化時會調用此方法。注意,跨程序的調用
         * 請求會被發送到每個程序的線程池中,是以下面的代碼不是在主線程運作的,
         * 是以,你需要使用handler更新UI
         */
        public void valueChanged(int value) {
            mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, ));
        }
    };
    private static final int BUMP_MSG = ;
    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);
            }
        }
    };
}
           

繼續閱讀