1. 程式人生 > >Android 進階7:程序通訊之 AIDL 的使用

Android 進階7:程序通訊之 AIDL 的使用

讀完本文你將瞭解:

記得 2015 年實習面試,筆試題裡就有這道題:請介紹下 AIDL。

當時的我是懵逼的,只好老老實實空著。沒想到後來面試時面試官大哥嘿嘿一笑說他也沒用過這玩意,真是夠實誠的。

筆試完查了這個知識點,似懂非懂也沒深究。去年看《安卓開發藝術探索》時也學了這部分內容,但是可能當時水平不夠,或者只是看起來努力,沒有真正理解精髓,沒多久就又忘了個七八成。

這次複習,還是老老實實敲出來,總結成文字吧,方便以後回顧。

AIDL 是什麼

AIDL(Android 介面定義語言) 是 Android 提供的一種程序間通訊 (IPC) 機制。

我們可以利用它定義客戶端與服務使用程序間通訊 (IPC) 進行相互通訊時都認可的程式設計介面。

在 Android 上,一個程序通常無法訪問另一個程序的記憶體。 儘管如此,程序需要將其物件分解成作業系統能夠識別的原語,並將物件編組成跨越邊界的物件。

編寫執行這一編組操作的程式碼是一項繁瑣的工作,因此 Android 會使用 AIDL 來處理。

通過這種機制,我們只需要寫好 aidl 介面檔案,編譯時系統會幫我們生成 Binder 介面。

AIDL 支援的資料型別

共 4 種:

  1. Java 的基本資料型別
  2. List 和 Map
    • 元素必須是 AIDL 支援的資料型別
    • Server 端具體的類裡則必須是 ArrayList 或者 HashMap
  3. 其他 AIDL 生成的介面
  4. 實現 Parcelable 的實體

AIDL 如何編寫

AIDL 的編寫主要為以下三部分:

  1. 建立 AIDL
    • 建立要操作的實體類,實現 Parcelable 介面,以便序列化/反序列化
    • 新建 aidl 資料夾,在其中建立介面 aidl 檔案以及實體類的對映 aidl 檔案
    • Make project ,生成 Binder 的 Java 檔案
  2. 服務端
    • 建立 Service,在其中建立上面生成的 Binder 物件例項,實現介面定義的方法
    • onBind() 中返回
  3. 客戶端
    • 實現 ServiceConnection 介面,在其中拿到 AIDL 類
    • bindService()
    • 呼叫 AIDL 類中定義好的操作請求

AIDL 例項

下面以例項程式碼演示一個 AIDL 的編寫。

1.建立 AIDL

①建立要操作的實體類,實現 Parcelable 介面,以便序列化/反序列化


package net.sxkeji.shixinandroiddemo2.bean;

import android.os.Parcel;
import android.os.Parcelable;

public class Person implements Parcelable {
    private String mName;

    public Person(String name) {
        mName = name;
    }

    protected Person(Parcel in) {
        mName = in.readString();
    }

    public static final Creator<Person> CREATOR = new Creator<Person>() {
        @Override
        public Person createFromParcel(Parcel in) {
            return new Person(in);
        }

        @Override
        public Person[] newArray(int size) {
            return new Person[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mName);
    }

    @Override
    public String toString() {
        return "Person{" +
                "mName='" + mName + '\'' +
                '}';
    }
}

實現 Parcelable 介面是為了後序跨程序通訊時使用。

注意 實體類所在的包名。

②新建 aidl 資料夾,在其中建立介面 aidl 檔案以及實體類的對映 aidl 檔案

在 main 資料夾下新建 aidl 資料夾,使用的包名要和 java 資料夾的包名一致:

shixiznhang

先建立實體類的對映 aidl 檔案,Person.aidl:

// Person.aidl
package net.sxkeji.shixinandroiddemo2.bean;

//還要和宣告的實體類在一個包裡
parcelable Person;

在其中宣告對映的實體類名稱與型別

注意,這個 Person.aidl 的包名要和實體類包名一致。

然後建立介面 aidl 檔案,IMyAidl.aidl:

// IMyAidl.aidl
package net.sxkeji.shixinandroiddemo2;

// Declare any non-default types here with import statements
import net.sxkeji.shixinandroiddemo2.bean.Person;

interface IMyAidl {
    /**
     * 除了基本資料型別,其他型別的引數都需要標上方向型別:in(輸入), out(輸出), inout(輸入輸出)
     */
    void addPerson(in Person person);

    List<Person> getPersonList();
}

在介面 aidl 檔案中定義將來要在跨程序進行的操作,上面的介面中定義了兩個操作:

  • addPerson: 新增 Person
  • getPersonList:獲取 Person 列表

需要注意的是:

  • 非基本型別的資料需要匯入,比如上面的 Person,需要匯入它的全路徑。
    • 這裡的 Person 我理解的是 Person.aidl,然後通過 Person.aidl 又找到真正的實體 Person 類。
  • 方法引數中,除了基本資料型別,其他型別的引數都需要標上方向型別
    • in(輸入), out(輸出), inout(輸入輸出)

③Make Project ,生成 Binder 的 Java 檔案

AIDL 真正的強大之處就在這裡,通過簡單的定義 aidl 介面,然後編譯,就會為我們生成複雜的 Java 檔案。

點選 Build -> Make Project,然後等待構建完成。

然後就會在 build/generated/source/aidl/你的 flavor/ 下生成一個 Java 檔案:

shixinzhang

現在我們有了跨程序 Client 和 Server 的通訊媒介,接著就可以編寫客戶端和服務端程式碼了。

我們先跑通整個過程,這個檔案的內容下篇文章介紹。

2.編寫服務端程式碼

建立 Service,在其中建立上面生成的 Binder 物件例項,實現介面定義的方法;然後在 onBind() 中返回

建立將來要執行在另一個程序的 Service,在其中實現了 AIDL 介面中定義的方法:

public class MyAidlService extends Service {
    private final String TAG = this.getClass().getSimpleName();

    private ArrayList<Person> mPersons;

    /**
     * 建立生成的本地 Binder 物件,實現 AIDL 制定的方法
     */
    private IBinder mIBinder = new IMyAidl.Stub() {

        @Override
        public void addPerson(Person person) throws RemoteException {
            mPersons.add(person);
        }

        @Override
        public List<Person> getPersonList() throws RemoteException {
            return mPersons;
        }
    };

    /**
     * 客戶端與服務端繫結時的回撥,返回 mIBinder 後客戶端就可以通過它遠端呼叫服務端的方法,即實現了通訊
     * @param intent
     * @return
     */
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        mPersons = new ArrayList<>();
        LogUtils.d(TAG, "MyAidlService onBind");
        return mIBinder;
    }
}

上面的程式碼中,建立的物件是一個 IMyAidl.Stub() ,它是一個 Binder,具體為什麼是它我們下篇文章介紹。

別忘記在 Manifest 檔案中宣告:

<service
    android:name="net.sxkeji.shixinandroiddemo2.service.MyAidlService"
    android:enabled="true"
    android:exported="true"
    android:process=":aidl"/>

服務端實現了介面,在 onBind() 中返回這個 Binder,客戶端拿到就可以操作資料了。

3.編寫客戶端程式碼

這裡我們以一個 Activity 為客戶端。

①實現 ServiceConnection 介面,在其中拿到 AIDL 類

private IMyAidl mAidl;

private ServiceConnection mConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        //連線後拿到 Binder,轉換成 AIDL,在不同程序會返回個代理
        mAidl = IMyAidl.Stub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mAidl = null;
    }
};

在 Activity 中建立一個服務連線物件,在其中呼叫 IMyAidl.Stub.asInterface() 方法將 Binder 轉為 AIDL 類。

②接著繫結服務

Intent intent1 = new Intent(getApplicationContext(), MyAidlService.class);
bindService(intent1, mConnection, BIND_AUTO_CREATE);

要執行 IPC,必須使用 bindService() 將應用繫結到服務上。

注意:

5.0 以後要求顯式呼叫 Service,所以我們無法通過 action 或者 filter 的形式呼叫 Service,具體內容可以看這篇文章 Android 進階:Service 的一些細節

③拿到 AIDL 類後,就可以呼叫 AIDL 類中定義好的操作,進行跨程序請求

@OnClick(R.id.btn_add_person)
public void addPerson() {
    Random random = new Random();
    Person person = new Person("shixin" + random.nextInt(10));

    try {
        mAidl.addPerson(person);
        List<Person> personList = mAidl.getPersonList();
        mTvResult.setText(personList.toString());
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

執行結果

shixinzhang

可以看到,Activity 與 另外一個程序的 Service 通訊成功了。

總結

這篇文章介紹了 AIDL 的簡單編寫流程,其中也踩過一些坑,比如檔案所在包的路徑不統一,繫結服務收不到回撥等問題。

到最後雖然跨程序通訊成功,但是我們還是有很多疑問的,比如:

  • AIDL 生成的檔案內容?
  • 什麼是 Binder?
  • 為什麼要這麼寫?

知其然還要知其所以然,這一切都要從 Binder 講起,且聽下一回合介紹。

程式碼地址

Thanks