Android程式間通訊之AIDL

_小馬快跑_發表於2017-12-15

本文例子中的原始碼地址: Github:程式間通訊之AIDL

AIDL(Android 介面定義語言)是定義客戶端與服務使用程式間通訊 (IPC) 進行相互通訊時都認可的程式設計介面,一個程式通常無法訪問另一個程式的記憶體,但是可以通過AIDL進行程式間通訊。**AIDL可以讓客戶端以IPC(程式間通訊)方式訪問服務端,並且服務端可以處理多執行緒;如果不需要處理多執行緒,則可以使用Messenger類來實現介面;如果只是需要本地的Service,不需要IPC過程,則只需要通過實現一個Binder類就可以了。**使用AIDL的前提必須瞭解繫結Service,Service為我們建立Binder驅動,Binder驅動是服務端與客戶端通訊的橋樑。AIDL通過我們寫的.aidl檔案,生成了一個介面,一個Stub類用於服務端,一個Proxy類用於客戶端,不熟悉Service的可以看下官方文件:Service

定義AIDL介面步驟:

1.建立.aidl檔案 在服務端的src/ 目錄內建立.aidl型別的檔案,如果客戶端和服務端不在一個應用內,則需要將.aidl檔案複製到客戶端。 .aidl檔案中可以定義一個或多個介面方法,方法引數和返回值可以是任意型別的,預設情況下,AIDL支援下面的幾種型別:

  • Java 程式語言中的所有原語型別(如 int、long、char、boolean 等等)
  • String
  • CharSequence
  • List (List中的所有元素都必須是以上列表中支援的資料型別、其他 AIDL 生成的介面或宣告的可打包型別。 可選擇將List用作“通用”類(如List)。另一端實際接收的具體類始終是ArrayList,但生成的方法使用的是List介面)
  • Map (Map中的所有元素都必須是以上列表中支援的資料型別、其他 AIDL 生成的介面或宣告的可打包型別。 不支援通用 Map(如 Map<String,Integer>形式的 Map)。 另一端實際接收的具體類始終是HashMap,但生成的方法使用的是 Map介面)

注意:如果在.aidl檔案中使用的是自定義物件,即使此型別檔案和.aidl檔案在同一個資料夾中,也必須使用import語句匯入,並且必須實現Parcelable介面。另外,非原語型別的引數需要指示資料走向的方向標記,可以是 in(輸入)、out(輸出) 或 inout(輸入輸出),預設是in,一定要正確規定方向,因為編組引數的開銷極大。

比如宣告一個自定義物件Rect.java:

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();
    }
}
複製程式碼

建立一個宣告可打包類的 .aidl 檔案,與宣告的自定義物件同名,並且這兩個同名的類和.aidl檔案放在同一個包下,如 Rect.aidl:

package android.graphics;

//注意這裡是小寫的parcelable來宣告
parcelable Rect;
複製程式碼

上面把Rect類放在.aidl目錄中時,編譯會提示找不到這個類,因為Android Studio預設會去java目錄下找,需要在build.gradle檔案 android{ } 中間增加一段程式碼,讓aidl目錄裡面的java檔案也能被識別:

 sourceSets{
        main{
            java.srcDirs=['src/main/java','src/main/aidl']
        }
    }
複製程式碼

以下是一個 IRemoteService.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);
}
複製程式碼

2.實現介面 SDK會根據.aidl檔案自動生成一個介面,介面中有一個名為Stub的內部抽象類,用於擴充套件Binder類並實現AIDL中的方法,服務端需要覆寫Stub類並實現方法。此外,Stub類中還有一個asInterface()方法,該方法帶IBinder(通常便是傳給客戶端 onServiceConnected()回撥方法的引數)並返回存根介面例項。

以下是一個使用匿名例項實現名為 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),用於定義服務的 RPC 介面。 在下一步中,將向客戶端公開該例項,以便客戶端能與服務進行互動。 在實現 AIDL 介面時應注意遵守以下這幾個規則:

  • 由於不能保證在主執行緒上執行傳入呼叫,因此一開始就需要做好多執行緒處理準備,並將您的服務正確地編譯為執行緒安全服務。
  • 預設情況下,RPC 呼叫是同步呼叫。如果明知服務完成請求的時間不止幾毫秒,就不應該從 Activity 的主執行緒呼叫服務,因為這樣做可能會使應用掛起(Android 可能會顯示“Application is Not Responding”對話方塊)— 您通常應該從客戶端內的單獨執行緒呼叫服務。
  • 引發的任何異常都不會回傳給呼叫方。

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
        }
    };
}
複製程式碼

現在,當客戶端(如 Activity)呼叫bindService()以連線此服務時,客戶端的onServiceConnected()回撥會接收服務的onBind()方法返回的 mBinder例項。 客戶端還必須具有對 interface 類的訪問許可權,因此如果客戶端和服務在不同的應用內,則客戶端的應用 src/目錄內必須包含 .aidl檔案(它生成android.os.Binder介面 — 為客戶端提供對 AIDL 方法的訪問許可權)的副本。 當客戶端在onServiceConnected()回撥中收到IBinder時,它必須呼叫 YourServiceInterface.Stub.asInterface(service)以將返回的引數轉換成YourServiceInterface型別。例如:

IRemoteService mIRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
    public void onServiceConnected(ComponentName className, IBinder service) {
        mIRemoteService = IRemoteService.Stub.asInterface(service);
    }

    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "Service has unexpectedly disconnected");
        mIRemoteService = null;
    }
};
複製程式碼

客戶端執行IPC過程:

  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()。

上面說的是整個流程,下面來舉個例子,先看效果圖:

aidl.gif

服務端程式碼

Apple.aidl:

package org.ninetripods.mq.multiprocess_sever;
parcelable Apple;
複製程式碼

其中Apple類已經實現Parcelable介面了,這裡就不再貼出來了,接著宣告IAidlCallBack.aidl 提供客戶端呼叫的方法:

// IAidlCallBack.aidl
package org.ninetripods.mq.multiprocess_sever;

// Declare any non-default types here with import statements
import org.ninetripods.mq.multiprocess_sever.Apple;
interface IAidlCallBack {
    Apple getAppleInfo();
}
複製程式碼

然後編寫RemoteService.java類:

public class RemoteService extends Service {
    public RemoteService() {
    }
    @Override
    public IBinder onBind(Intent intent) {
         //通過onBind()方法返回Binder例項供客戶端呼叫
        return mBinder;
    }

    private IAidlCallBack.Stub mBinder = new IAidlCallBack.Stub() {
        @Override
        public Apple getAppleInfo() throws RemoteException {
            return new Apple("蛇果", 20f, getString(R.string.respose_info));
        }
    };
}
複製程式碼

最後在AndroidManifest.xml裡宣告一下:

<service
   android:name=".RemoteService"
   android:enabled="true"
   android:exported="true">
     <intent-filter>
       <action android:name="android.mq.common.service" />
       <category android:name="android.intent.category.DEFAULT" />
     </intent-filter>
 </service>
複製程式碼

客戶端程式碼:AidlActivity.java

首先實現ServiceConnection,在onServiceConnected()中通過IAidlCallBack.Stub.asInterface(service) 拿到Binder例項,然後就可以呼叫服務端方法了,程式碼如下:

private ServiceConnection mCommonConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            isCommonBound = true;
            mCommonService = IAidlCallBack.Stub.asInterface(service);
            if (mCommonService != null) {
                try {
                    Apple apple = mCommonService.getAppleInfo();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            isCommonBound = false;
            mCommonService = null;
            showMessage("\n連線遠端服務端失敗...");
        }
    };
複製程式碼

最後是繫結服務:

Intent commonIntent = new Intent();
commonIntent.setAction("android.mq.common.service");
commonIntent.setPackage("org.ninetripods.mq.multiprocess_sever");
bindService(commonIntent, mCommonConnection, Context.BIND_AUTO_CREATE);
複製程式碼

例子中還有個觀察者模式,跟在同一程式中使用觀察者模式是有區別的,要用到RemoteCallbackList,具體使用方法程式碼中有,就不再詳述了~ 恩~整個過程差不多是這樣了,完整程式碼已上傳至 Github:程式間通訊之AIDL,如果對您有幫助,給個star吧,感激不盡~(注:專案中有兩個專案,請確保先啟動Server端,否則看不到效果

相關文章