3. IPC機(jī)制(基礎(chǔ))
3.0 前言
本文總結(jié)自任玉剛老師的《Android開發(fā)藝術(shù)探索》,文章中的【示例】在這里
3.1 Android IPC簡介
IPC (Inter-Process Communication,含義為進(jìn)程間通信或跨進(jìn)程通信),是指兩個進(jìn)程之間進(jìn)行數(shù)據(jù)交換的過程。Bundle、文件共享、AIDL(Android Interface definition language一種android內(nèi)部進(jìn)程通信接口的描述語言,通過它我們可以定義進(jìn)程間的通信接口,涉及Binder連接池的概念)、Messenger、ContentProvider、Socket都是進(jìn)程間通信的方式。
進(jìn)程:和線程是截然不同的概念。線程是CPU調(diào)度的最小單元,同時線程是一種有限的系統(tǒng)資源。而進(jìn)程一般指一個執(zhí)行單元,在PC和移動設(shè)備上指一個程序或者一個應(yīng)用。一個進(jìn)程可以包含多個線程(是包含的關(guān)系)。最簡單的情況下,一個進(jìn)程中可以只有一個線程,即主線程。Android中主線程也叫UI線程,在UI線程里面才能操作頁面元素(耗時的任務(wù)在主線程中執(zhí)行會造成界面無法響應(yīng):ANR,所以要把耗時的線程放在其他線程中)。
任何一個操作系統(tǒng)都需要有相應(yīng)的IPC機(jī)制,Android中最有特色的進(jìn)程間通信方式就是Binder了,通過Binder可以輕松地實(shí)現(xiàn)進(jìn)程間通信。除了Binder還有Socket,也可以實(shí)現(xiàn)任意兩個終端之間的通信。
3.2 Android中的多進(jìn)程模式
說到IPC的使用場景就必須提到多線程,只有面對多進(jìn)程的場景下,才需要考慮進(jìn)程間通信。多進(jìn)程的情況分為2種:
- 應(yīng)用因?yàn)槟承┰蜃陨硇枰捎枚噙M(jìn)程模式來實(shí)現(xiàn)(有些模塊由于特殊原因需要運(yùn)行在單獨(dú)的進(jìn)程中、 為了加大一個應(yīng)用可使用的內(nèi)存所以需要通過多線程來獲取多份內(nèi)存空間等)。
- 當(dāng)前應(yīng)用需要向其他應(yīng)用獲取數(shù)據(jù),由于是兩個應(yīng)用,所以必須采用跨進(jìn)程的方式來獲取所需的數(shù)據(jù)(用ContentProvider去查詢數(shù)據(jù)等)
不管由于何種原因,我們采用了多進(jìn)程的設(shè)計方法,應(yīng)用中必須妥善處理進(jìn)程間通信的各種問題。
3.2.1 開啟多進(jìn)程模式
正常情況下,Android中多進(jìn)程是指一個應(yīng)用中存在多個進(jìn)程的情況(暫不討論兩個應(yīng)用之間的多進(jìn)程情況,因?yàn)檫@樣操作起來比較方便,如果是不同進(jìn)程間通信,那么它們分別是否屬于兩個應(yīng)用沒有區(qū)別,原理一樣)。
Android中使用多進(jìn)程只有一種方法,那就是給四大組件(Activity、Service、Recevier、ContentProvider)在AndroidMenifest中指定android:process屬性,除此之外沒有其他方法,也就是說我們無法給一個線程或者一個實(shí)體類指定其運(yùn)行時所在的進(jìn)程(其實(shí)還有另外一種非常規(guī)的多進(jìn)程方法,那就是通過JNI在native層去fork一個新的進(jìn)程)。下面示例如何在Android中創(chuàng)建多進(jìn)程:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
android:configChanges="screenLayout"
android:label="@string/app_name"
android:process=":remote"/>
<activity
android:name=".ThirdActivity"
android:configChanges="screenLayout"
android:label="@string/app_name"
android:process="com.example.learn_002_ipc.remote"/>
上例分別為SecondActivity和ThirdActivity指定了process屬性,并且屬性值不同,這意味著當(dāng)前應(yīng)用又增加了兩個新進(jìn)程。SecondActivity啟動時,系統(tǒng)會為它創(chuàng)建一個單獨(dú)的進(jìn)程,進(jìn)程名為“com.example.learn_002_ipc:remote”(當(dāng)前包名為“com.example.learn_002_ipc”);當(dāng)ThirdActivity啟動時,系統(tǒng)也會為它創(chuàng)建一個單獨(dú)的進(jìn)程,進(jìn)程名為“com.example.learn_002_ipc.remote”同時入口Activity是MainActivity,沒有為它指定process屬性,那么它運(yùn)行在默認(rèn)進(jìn)程中,默認(rèn)的進(jìn)程名是包名,如圖:
進(jìn)程列表末尾存在3個進(jìn)程,進(jìn)程id分別是6612、6680、6704,這說明應(yīng)用成功使用了多進(jìn)程技術(shù)。關(guān)于上面的":remote"和"com.example.learn_002_ipc.remote"其實(shí)是有區(qū)別的:“:”的含義是指要在當(dāng)前的進(jìn)程名前面附加上當(dāng)前的包名(簡寫),對于SecondActivity來說,它完整的進(jìn)程名為com.example.learn_002_ipc:remote;對于ThirdActivity中的聲明方式來說,它是一種完整的命名方式。并且,以“:”開頭的進(jìn)程屬于當(dāng)前應(yīng)用的私有進(jìn)程,其他應(yīng)用的組件不可以和它跑在同一個進(jìn)程中,而進(jìn)程名不以“:”開頭的進(jìn)程為全局進(jìn)程,其他應(yīng)用可以通過ShareUID方式可以和它跑在同一進(jìn)程中。
Android系統(tǒng)會為每個應(yīng)用分配一個唯一的UID,具有相同的UID的應(yīng)用才能共享數(shù)據(jù),而兩個應(yīng)用通過ShareUID跑在同一個進(jìn)程中要求要有相同的ShareUID并且簽名相同才可以。在這種情況下,它們可以互相訪問對方的私有數(shù)據(jù),比如data目錄、組件信息,共享內(nèi)存數(shù)據(jù)...或者說它們看起來像是一個應(yīng)用的兩個部分。
3.2.2 多進(jìn)程模式的運(yùn)行機(jī)制
“當(dāng)應(yīng)用開啟了多線程后,各種奇怪的現(xiàn)象都出現(xiàn)了",這是用來形容多線程的。
這里舉一個例子,在上例中新建UserManager類,這個類中有個public的靜態(tài)成員變量:public static int sUserId = 1;在MainActivity的onCreate中把這個sUserId重新賦值為2,打印出這個靜態(tài)變量的值后再啟動SecondActivity,在其中再打印一下sUserId的值,在Android Device Monitor中可以看出在MainActivity中輸出的sUserId是2,在SecondActivity中輸出的是1。
出現(xiàn)上述問題的原因是SecondActivity運(yùn)行在一個單獨(dú)的進(jìn)程中,Android為每一個應(yīng)用分配了一個獨(dú)立的虛擬機(jī)(即每一個進(jìn)程都分配一個獨(dú)立的虛擬機(jī)),不同的虛擬機(jī)在內(nèi)存分配上會有不同的地址空間,導(dǎo)致不同的虛擬機(jī)中訪問同一個對象會產(chǎn)生多份副本。該例中進(jìn)程com.example.learn_002_ipc和進(jìn)程com.example.learn_002_ipc:remote都存在一個UserManager類,并且這兩個類互不干擾,在一個進(jìn)程中修改sUserId的值只會影響當(dāng)前進(jìn)程,對其他進(jìn)程不會造成任何影響。
所有運(yùn)行在不同進(jìn)程中的四大組件,只要它們之間需要同內(nèi)存來共享數(shù)據(jù),都會共享失敗,這也是多進(jìn)程所帶來的主要影響。正常情況下,四大組件中間不可能不通過一個中間層來共享數(shù)據(jù),那么通過簡單地指定進(jìn)程名來開啟多線程都會無法正確運(yùn)行。當(dāng)然特殊情況下,某些組件之間不需要共享數(shù)據(jù),這時可以直接指定android:process屬性來開啟多線程,但是這種場景不常見,幾乎所有情況都需要共享數(shù)據(jù)。
一般來說多進(jìn)程會造成如下幾方面問題:
- 靜態(tài)成員和單例模式完全失效。(上例)
- 線程同步機(jī)制完全失效。(不是同一塊內(nèi)存了,不管鎖對象還是鎖全局類都無法保證線程同步,因?yàn)椴煌M(jìn)程鎖的不是同一個對象)
- SharedPreferences可靠性下降。(SharedPreferences不支持兩個進(jìn)程同時執(zhí)行寫操作否則會導(dǎo)致一定機(jī)率的數(shù)據(jù)丟失,這是因?yàn)镾haredPreferences是通過讀/寫XML文件實(shí)現(xiàn)的,并發(fā)讀/寫都有可能出問題)
- Applicaion會多次創(chuàng)建。(新建一個MyApplication類,注意在Manifest內(nèi)設(shè)置android:name,然后在MyApplication的onCreate方法中每次啟動都打印一次當(dāng)前線程的名字,再在上例中分別啟動3個Activity,locat中可以看出的Application創(chuàng)建了3次,而且每次的進(jìn)程名稱和進(jìn)程id都不一樣)
總結(jié):多進(jìn)程模式中,不同進(jìn)程的組件擁有獨(dú)立的虛擬機(jī)、Application、內(nèi)存空間。這樣理解同一個應(yīng)用間的多進(jìn)程:相當(dāng)于兩個不同的應(yīng)用采用了SharedUID的模式。為了解決這個問題,系統(tǒng)提供了很多跨進(jìn)程通信方法(雖然不能直接地共享內(nèi)存,但是還是可以實(shí)現(xiàn)數(shù)據(jù)交互):使用Intent來傳遞數(shù)據(jù)、共享文件和SharedPreferences、基于Binder的Messenger和AIDL、Socket等。
3.3 IPC基礎(chǔ)概念介紹
以下若干名詞都是IPC的基礎(chǔ)概念,先大概了解一下:Serializable和Parcelable接口可以完成對象的序列化過程,當(dāng)我們需要通過Intent和Binder傳輸數(shù)據(jù)時就需要使用Parcelable或者Serilizable。還有的時候我們需要把對象持久化到存儲設(shè)備上或者通過網(wǎng)絡(luò)傳輸給其他客戶端,也需要Serializable來完成對象的持久化。
序列化 (Serialization):將對象的狀態(tài)信息轉(zhuǎn)換為可以存儲或傳輸?shù)男问降倪^程。在序列化期間,對象將其當(dāng)前狀態(tài)寫入到臨時或持久性存儲區(qū)。以后,可以通過從存儲區(qū)中讀取或反序列化對象的狀態(tài),重新創(chuàng)建該對象。
3.3.1 Serializable接口
java.io.Serializable/public interface : Marks classes that can be serialized by ObjectOutputStream and deserialized by ObjectInputStream.
標(biāo)記可以由ObjectOutputStream序列化并由ObjectInputStream反序列化的類。
該接口是Java提供的一個序列化接口,它是一個空接口,為對象提供標(biāo)準(zhǔn)的序列化和反序列化操作。使用Serializable來實(shí)現(xiàn)序列化相當(dāng)簡單,只需要令該類實(shí)現(xiàn)Serializable接口,并在其聲明中指定一個標(biāo)識(serialVersionUID,但這一步甚至不是必須的)即可自動實(shí)現(xiàn)默認(rèn)的序列化過程:
public class User implements Serializable {
private static final long serialVersionUID = 909090L;
public int userId;
public String userName;
public boolean isMale;
}
如果不實(shí)現(xiàn)這個接口那么序列化的時候會遇到一個很耿直的異常:
java.io.WriteAbortedException: writing aborted.io.NotSerializableException
通過Serializable方式來實(shí)現(xiàn)對象的序列化,實(shí)現(xiàn)起來非常簡單,幾乎所有工作都被系統(tǒng)自動完成了。進(jìn)行對象的序列化和反序列化也非常簡單,只需采用ObjectOutputStream和ObjectInputStream即可輕松實(shí)現(xiàn):
//序列化過程(The serialization process)
User user = new User(0, "jake", true);
try {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeObject(user);
out.close();
} catch (IOException e) {
e.printStackTrace();
}
//反序列化過程(The deserialization process)
try {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));
User newUser = (User)in.readObject();
in.close();
} catch (Exception e) {
e.printStackTrace();
}
上述演示了Serializable方式序列化對象的典型過程,只需把實(shí)現(xiàn)了Serializable接口的User對象寫到文件中就可以快速恢復(fù)了,恢復(fù)后的對象newUser和user的內(nèi)容完全一樣,但是兩者不是同一個對象。
關(guān)于serialVersionUID:這是用來輔助序列化和反序列化過程的(有用),原則上序列化后的數(shù)據(jù)中的serialVersionUID只有和當(dāng)前類的serialVersionUID相同才能正常地被反序列化。serialVersionUID的工作機(jī)制是:
序列化的時候系統(tǒng)把當(dāng)前類的serialVersionUID寫入序列化的文件中(也有可能是其他中介),當(dāng)反序列化的時候就回去檢測文件中的serialVersionUID,看它是否和當(dāng)前類的serialVersionUID一致,如果一致就說明序列化的類的版本和當(dāng)前類的版本是相同的,這時候就可以成功反序列化;否則說明當(dāng)前類和序列化的類相比發(fā)生了某些變換,比如成員變量的數(shù)量、類型可能發(fā)生了改變,這個時候是無法正常反序列化的(報java.io.InvalidClassException)。
一般來說,應(yīng)該手動指定serialVersionUID的值,比如1L,也可以讓AS根據(jù)當(dāng)前類的結(jié)構(gòu)自動生成它的hash值(本質(zhì)一樣),這樣序列化和反序列化時兩者的serialVersionUID是相同的,因此可以正常進(jìn)行反序列化。
如果不手動指定serialVersionUID的值,反序列化時當(dāng)前類有所改變,比如增加或刪除了某些成員變量,系統(tǒng)會重新計算當(dāng)前類的hash值并把它賦值給serialVersionUID,這時候類的serialVersionUID就和序列化數(shù)據(jù)中的serialVersionUID不一致,于是反序列化失敗,程序就會出現(xiàn)crash,所以手動指定serialVersionUID可以很大程度避免反序列化過程的失敗。
另外,系統(tǒng)的默認(rèn)序列化過程也是可以改變的(重寫writeObject和readObject方法)。
3.3.2 Parcelable接口
android.os.Parcelable/public interface : Interface for classes whose instances can be written to and restored from a Parcel. Classes implementing the Parcelable interface must also have a static field called CREATOR, which is an object implementing the Parcelable.Creator interface.
其實(shí)例可以寫入Parcel并從中恢復(fù)的類的接口。實(shí)現(xiàn)Parcelable接口的類還必須有一個名為CREATOR的靜態(tài)字段,該字段是實(shí)現(xiàn)Parcelable.Creator接口的對象。
java.lang.Object ? android.os.Parcel* / public final class*: Container for a message (data and object references) that can be sent through an IBinder. A Parcel can contain both flattened data that will be unflattened on the other side of the IPC (using the various methods here for writing specific types, or the general Parcelable interface), and references to live IBinder objects that will result in the other side receiving a proxy IBinder connected with the original IBinder in the Parcel.
可以通過IBinder發(fā)送的消息(數(shù)據(jù)和對象引用)的容器。一個Parcel可以包含在IPC的另一側(cè)(使用這里的各種方法來編寫特定類型,或者通用的Parcelable接口)的平坦數(shù)據(jù),以及引用另一方接收的活I(lǐng)Binder對象的引用一個代理IBinder與Parcel中的原始IBinder連接。
在Android中也提供了新的序列化方式,就是Parcelable接口,使用Parcelabel接口來實(shí)現(xiàn)對象的序列化,其過程要稍微復(fù)雜一些,只要實(shí)現(xiàn)這個接口,一個類的對象就可以實(shí)現(xiàn)序列化并可以通過Intent和Binder傳遞,下例是一個典型用法:
public class User2 implements Parcelable {
public int userId;
public String userName;
public boolean isMale;
public User user;
public User2(int userId, String userName, boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
//序列化過程(The serialization process)
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(userId);
dest.writeString(userName);
dest.writeByte((byte) (isMale ? 1 : 0));
}
//反序列化過程(The deserialization process)
public static final Creator<User2> CREATOR = new Creator<User2>() {
@Override
public User2 createFromParcel(Parcel in) {
return new User2(in);
}
@Override
public User2[] newArray(int size) {
return new User2[size];
}
};
protected User2(Parcel in) {
userId = in.readInt();
userName = in.readString();
isMale = in.readByte() != 0;
}
//內(nèi)容描述過程(content description)
@Override
public int describeContents() {
return 0;
}
}
Parcel內(nèi)部包裝了可序列化的數(shù)據(jù),可以在Binder中自由傳輸,從上述代碼中可看出,序列化過程中需要實(shí)現(xiàn)的功能有序列化、反序列化和內(nèi)容描述。
- 序列化功能由writeToParcel方法來完成,最終是通過Parcel中的一系列write方法**來完成的。
- 反序列化功能由CREATOR來完成,其內(nèi)部標(biāo)明了如何創(chuàng)建序列化對象和數(shù)組,并通過Parcel的一系列read方法**來完成反序列化過程。
- 內(nèi)容描述功能由describeContents方法來完成**,幾乎所有情況下該方法都應(yīng)該返回0,僅當(dāng)當(dāng)前對象中存在文件描述符時,此方法返回1。
需要注意的是,在User2(Parcel in) 方法中,由于user是另一個可序列化對象。以它的反序列化過程需要傳遞當(dāng)前線程的上下文類加載器,否則會無法找到類的錯誤。下面是Parcelable的方法說明:
方法 | 功能 | 標(biāo)記位 |
---|---|---|
createFromParcel(Parcel in) | 從序列化后的對象中創(chuàng)建原始對象 | |
new Array(Parcel in) | 創(chuàng)建指定長度的原始對象數(shù)組 | |
writeToParcel(Parcel out, int flags) | 將當(dāng)前對象寫入序列化結(jié)構(gòu)中,其中flags標(biāo)識有2種值:0或1。1時標(biāo)識當(dāng)前對象需要作為返回值返回,不能立即釋放資源,幾乎所有情況都為0 | PARCELABLE_WRITE_RETURN_VALUE |
descriveContents | 返回當(dāng)前對象的內(nèi)容描述,如果含有文件描述符,返回1,否則返回0 | CONTENTS_FILE_DESCRIPTOR |
系統(tǒng)已經(jīng)為我們提供了很多實(shí)現(xiàn)了Parcelable接口的類,他們都是可以直接序列化的,比如Intent、Bundle、Bitmap等,同時List和Map也可以序列化,前提是它們里面的每個元素都是可序列化的。
關(guān)于Parcelable和Serializable的區(qū)別:
- Serializable是Java中的序列化接口,使用簡單但是開銷很大(序列化的時候會產(chǎn)生大量的臨時變量,從而引起頻繁的GC),序列化和反序列化過程都需要大量I/O操作,主要用于將對象序列化到存儲設(shè)備(磁盤)中或者將對象序列化后通過網(wǎng)絡(luò)傳輸,因?yàn)镻arcelable的過程較復(fù)雜。
- Parcelable是Android中的序列化方式,因此更適合用在Android平臺上,缺點(diǎn)是操作稍微麻煩,但是效率高,更適合用在Android平臺上。主要用在內(nèi)存序列化上。
3.3.3 Binder
java.lang.Object ? android.os.Binder* / public class / extends Object / implements IBinder* : Base class for a remotable object, the core part of a lightweight remote procedure call mechanism defined by IBinder. This class is an implementation of IBinder that provides the standard support creating a local implementation of such an object.Most developers will not implement this class directly, instead using the aidl tool to describe the desired interface, having it generate the appropriate Binder subclass. You can, however, derive directly from Binder to implement your own custom RPC protocol or simply instantiate a raw Binder object directly to use as a token that can be shared across processes.
Binder : 可遠(yuǎn)程對象的基類,是由IBinder定義的輕量級遠(yuǎn)程過程調(diào)用機(jī)制的核心部分。該類是IBinder的一個實(shí)現(xiàn),它提供了創(chuàng)建此類對象的本地實(shí)現(xiàn)的標(biāo)準(zhǔn)支持。大多數(shù)開發(fā)人員不會直接實(shí)現(xiàn)這個類,而是使用aidl工具來描述所需的接口,讓它生成適當(dāng)?shù)腂inder子類。但是,您可以直接從Binder派生實(shí)現(xiàn)您自己的自定義RPC協(xié)議,或直接實(shí)例化原始Binder對象以用作可跨進(jìn)程共享的令牌(token)。
android.os.IBinder* / public interface* : Base interface for a remotable object, the core part of a lightweight remote procedure call mechanism designed for high performance when performing in-process and cross-process calls. This interface describes the abstract protocol for interacting with a remotable object. Do not implement this interface directly, instead extend from Binder.
IBinder : 可遠(yuǎn)程對象的基礎(chǔ)接口,輕量級遠(yuǎn)程過程調(diào)用機(jī)制的核心部分,專為執(zhí)行進(jìn)程內(nèi)和跨進(jìn)程調(diào)用時的高性能而設(shè)計。該接口描述了與可遠(yuǎn)程對象交互的抽象協(xié)議。不要直接實(shí)現(xiàn)這個接口,而是從Binder擴(kuò)展。
從IPC角度來說,Binder是Android中的一種跨進(jìn)程通信方式,Binder還可以理解為一種虛擬的物理設(shè)備,它的設(shè)備驅(qū)動是/dev/binder,該通信方式在Linux中沒有;
從Android Framework角度來說,Binder是ServiceManager鏈接各種Manager(ActivityManager、WindowManager等等)和相應(yīng)ManagerService的橋梁;
從Android應(yīng)用層來說,Binder是客戶端和服務(wù)端進(jìn)行同行的媒介,當(dāng)bindService的時候,服務(wù)端會返回一個包含了服務(wù)端業(yè)務(wù)調(diào)用的Binder對象,通過這個Binder對象,客戶端就可以獲取服務(wù)端提供的服務(wù)或者數(shù)據(jù),這里的服務(wù)包括普通服務(wù)和基于AIDL(Android Interface Definition Language )的服務(wù)。
Android開發(fā)中,Binder主要用在Service中,包括AIDL和Messenger,而Messenger的底層其實(shí)是AIDL(其中普通Service中的Binder不涉及進(jìn)程間通信,較為簡單不涉及Binder的核心)。
所以這里選擇用AIDL來分析Binder的工作機(jī)制。新建Java包c(diǎn)om.example.learn_002_ipc.aidl,創(chuàng)建以下3個文件:
package com.example.learn_002_ipc.aidl;
import android.os.Parcel;
import android.os.Parcelable;
//Book.java
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
@Override
public int describeContents() { //內(nèi)容描述
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) { //序列化操作
dest.writeInt(bookId);
dest.writeString(bookName);
}
public static final Creator<Book> CREATOR = new Creator<Book>() { //反序列化操作
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
protected Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
}
`
//Book.aidl
package com.example.learn_002_ipc.aidl;
parcelable Book;
`
//IBookManager.aidl
package com.example.learn_002_ipc.aidl;
import com.example.learn_002_ipc.aidl.Book;
interface IBookManager { //圖書管理員
List<Book> getBookList();
void addBook(in Book book);
}
上面3個文件中,Book.java是一個表示圖書信息的類,它實(shí)現(xiàn)了Parcelable接口。Book.aidl是Book類在AIDL中的聲明。IBookManager.aidl是我們定義的一個接口,里面有2個方法:getBookList和addBook,其中g(shù)etBookList用于從遠(yuǎn)程服務(wù)端獲取圖書列表,而addBook用于往圖書列表中添加一本書。可以看到,盡管Book類和IBookManager位于相同的包中,但是再IBookManager中仍然要導(dǎo)入Book類,這就是AIDL的特別之處(如果忘記導(dǎo)入了或者輸入的包地址錯誤,會導(dǎo)致意料之外的錯誤)。
下面是系統(tǒng)為IBookManager.aidl生成的Binder類(在gen目錄下的com.example.learn_002_ipc.aidl包中的IBookManager.java):
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: F:\\AndroidStudioProjects\\Learn_002_ipc\\app\\src\\main\\aidl\\com\\example\\learn_002_ipc\\aidl\\IBookManager.aidl
*/
package com.example.learn_002_ipc.aidl;
public interface IBookManager extends android.os.IInterface {
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.learn_002_ipc.aidl.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.example.learn_002_ipc.aidl.IBookManager";
/** Construct the stub at attach it to the interface. */
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.learn_002_ipc.aidl.IBookManager interface,
* generating a proxy if needed.
*
* 用于將服務(wù)端的Binder對象轉(zhuǎn)換成客戶端所需的AIDL接口類型的對象。
*/
public static com.example.learn_002_ipc.aidl.IBookManager asInterface(android.os.IBinder obj) {
if ((obj**null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.learn_002_ipc.aidl.IBookManager))) { //如果客戶端和服務(wù)端位于同一進(jìn)程,那么此方法返回的就是服務(wù)端的Stub對象本身。
return ((com.example.learn_002_ipc.aidl.IBookManager)iin);
}
return new com.example.learn_002_ipc.aidl.IBookManager.Stub.Proxy(obj); //否則返回的是系統(tǒng)封裝后的Stub.proxy對象。
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.example.learn_002_ipc.aidl.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.example.learn_002_ipc.aidl.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.example.learn_002_ipc.aidl.Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.example.learn_002_ipc.aidl.IBookManager {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public java.util.List<com.example.learn_002_ipc.aidl.Book> getBookList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.example.learn_002_ipc.aidl.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.learn_002_ipc.aidl.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.example.learn_002_ipc.aidl.Book book) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book!=null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.util.List<com.example.learn_002_ipc.aidl.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.example.learn_002_ipc.aidl.Book book) throws android.os.RemoteException;
}
對于這個類可以看到它繼承了IInterface這個接口(Binder接口的基類。只有一個抽象方法asBinder(),定義新接口時,必須從IInterface派生它),同時它自己也還是個接口,所有可以在Binder中傳輸?shù)慕涌诙夹枰^承IInterface接口。
android.os.IInterface / public interface :Base class for Binder interfaces. When defining a new interface, you must derive it from IInterface.
這個類剛開始看起來邏輯混亂,但是實(shí)際上還是很清晰的通過它我們可以清楚地了解到Binder的工作機(jī)制:
這個類結(jié)構(gòu)其實(shí)很簡單,首先它++聲明了兩個方法getBookList和addBook,顯然這是我們在IBookManager.aidl中所聲明的方法++,同時它還聲明了兩個整型的id分別用于標(biāo)識這兩個方法,這兩個id用于標(biāo)識在transact(辦理)過程中客戶端所請求的到底是哪個方法。接著,它聲明了一個內(nèi)部類Stub,這個Stub就是一個Binder類,當(dāng)客戶端和服務(wù)端都位于同一個進(jìn)程時,方法調(diào)用不會走跨進(jìn)程的transact過程,而當(dāng)兩者位于不同進(jìn)程時,方法調(diào)用需要走transcat過程,這個邏輯由Stub的內(nèi)部代理類Proxy來完成。
可以認(rèn)識到,這個接口的核心實(shí)現(xiàn)就是它的內(nèi)部類Stub和Stub的內(nèi)部實(shí)現(xiàn)類Proxy,下面介紹這兩個類的每個方法的含義:
DESCRIPTOR:Binder的唯一標(biāo)識,一般用當(dāng)前Binder的類名,如本例中的"com.example.learn_002_ipc.aidl.IBookManager"。
asInterface(android.os.IBinder obj):用于將服務(wù)端的Binder對象轉(zhuǎn)換成客戶端所需的AIDL接口類型的對象,這種轉(zhuǎn)換是區(qū)分進(jìn)程的,如果客戶端和服務(wù)端位于同一進(jìn)程,那么此方法返回的就是服務(wù)端的Stub對象本身,否則返回的是系統(tǒng)封裝后的Stub.proxy對象**。
asBinder :用于返回當(dāng)前Binder對象
onTranscat :該方法運(yùn)行在服務(wù)端中的Binder線程池中,當(dāng)客戶端發(fā)起跨進(jìn)程請求時,遠(yuǎn)程請求會通過系統(tǒng)底層封裝后交由此方法來處理。服務(wù)端通過code可以確定客戶端所請求的目標(biāo)方法是什么,接著從data中取出目標(biāo)方法所需的參數(shù)(如果目標(biāo)方法有返回值的話)。
The key IBinder API is transact() matched by Binder.onTransact(). These methods allow you to send a call to an IBinder object and receive a call coming in to a Binder object, respectively. This transaction API is synchronous, such that a call to transact() does not return until the target has returned from Binder.onTransact(); this is the expected behavior when calling an object that exists in the local process, and the underlying inter-process communication (IPC) mechanism ensures that these same semantics apply when going across processes.
IBinder API的關(guān)鍵是通過Binder.onTransact()進(jìn)行transact()匹配。這些方法允許您將調(diào)用發(fā)送到IBinder對象,并分別接收進(jìn)入Binder對象的調(diào)用。這個事務(wù)API是同步的,因此,直到目標(biāo)從Binder.onTransact()返回后,對transact()的調(diào)用才會返回。這是調(diào)用本地進(jìn)程中存在的對象時的預(yù)期行為,并且基礎(chǔ)進(jìn)程間通信(IPC)機(jī)制可確保在跨進(jìn)程時應(yīng)用這些相同的語義。
(IBinder) public abstract boolean transact (int code, Parcel data, Parcel reply, int flags) : Perform a generic operation with the object.
使用對象執(zhí)行通用操作。
(Binder) protected boolean onTransact (int code, Parcel data, Parcel reply, int flags) : Default implementation is a stub that returns false. You will want to override this to do the appropriate unmarshalling of transactions.If you want to call this, call transact().
默認(rèn)實(shí)現(xiàn)是一個返回false的存根。您需要重寫此操作以執(zhí)行相應(yīng)的事務(wù)解組。如果你想調(diào)用這個,請調(diào)用transact()。
-
Proxy#getBookList:該方法運(yùn)行在客戶端**,當(dāng)客戶端遠(yuǎn)程調(diào)用此方法時,它的內(nèi)部實(shí)現(xiàn)是這樣的:
- 首先創(chuàng)建該方法所需要的輸入型Parcel對象_data、輸出型Parcel對象_reply、返回值對象List(_result);
- 然后把該方法的參數(shù)信息寫入_data中(如果有參數(shù)的話);
- 接著調(diào)用transcat方法(android.os.IBinder.transact(...))來發(fā)起RPC(遠(yuǎn)程過程調(diào)用)請求,同時當(dāng)前線程掛起;
- 然后服務(wù)端的onTranscat方法會被調(diào)用直到RPC過程返回后,當(dāng)前線程繼續(xù)執(zhí)行,并從_reply中取出RPC過程的返回結(jié)果;
- 最后返回_reply中的數(shù)據(jù)。
- Proxy#addBook:該方法運(yùn)行在客戶端,執(zhí)行過程個getBookList一樣,addBook沒有返回值所以不需要從_reply中取出返回值。
說明一下:
- 當(dāng)客戶端發(fā)起遠(yuǎn)程請求時,由于++當(dāng)前線程會被掛起++直至服務(wù)器進(jìn)程返回數(shù)據(jù),所以如果一個遠(yuǎn)程方法是很耗時的,那么不能在UI線程中發(fā)起此遠(yuǎn)程請求;
- 由于服務(wù)端的Binder方法運(yùn)行在Binder的線程池中,所以Binder方法不管是否耗時都應(yīng)該采用同步的方式去實(shí)現(xiàn)。因?yàn)樗呀?jīng)運(yùn)行在一個線程中了。
[圖片上傳失敗...(image-7114d0-1525872465108)]
【Binder的工作機(jī)制圖】
從上述分析過程來看,我們完全不可以提供AIDL文件即可實(shí)現(xiàn)Binder,之所以提供AIDL文件,是為了方便系統(tǒng)為我們生成代碼。現(xiàn)在參考IBookManager.java這個類的代碼寫一個一模一樣的類出來:
首先寫一個Binder的服務(wù)端:
//寫一個Binder的服務(wù)端(write a binder's server)
private final IBookManager.Stub mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
synchronized (mBookList) {
return mBookList;
}
}
@Override
public void addBook(Book book) throws RemoteException {
synchronized (mBookList) {
if (!mBookList.contains(book)) {
mBookList.add(book);
}
}
}
};
首先我們會實(shí)現(xiàn)一個創(chuàng)建了一個Stub對象并在內(nèi)部實(shí)現(xiàn)IBookManager的接口方法,然后在Service的onBind中返回這個Stub對象。因此,從這一點(diǎn)來看,我們完全可以把Stub類提取出來直接作為一個獨(dú)立的Binder類來實(shí)現(xiàn),這樣IBookManager中就只剩接口本身了,這種分離的方式可以讓它的接口變得清晰點(diǎn)。
根據(jù)上面的思想,手動實(shí)現(xiàn)一個Binder可以通過以下步驟來完成:
- 聲明一個AIDL性質(zhì)的可口,只需要繼承IInterface接口即可,IInterface接口中有一個asBinder方法。這個接口的實(shí)現(xiàn)如下:
public interface IBookManager extends IInterface {
static final String DESCRIPTOR = "com.example.learn_002_ipc.manualbinder.IBookManager";
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
public List<Book> getBookList() throws RemoteException;
public void addBook(Book book) throws RemoteException;
}
- 實(shí)現(xiàn)Stub類和Stub類中的Proxy代理類,這段代碼我們可以自己寫,但是寫出來后會發(fā)現(xiàn)和系統(tǒng)自動生成的代碼是一樣的,因此這個Stub類只需要參考系統(tǒng)生成的代碼即可:
package com.example.learn_002_ipc.manualbinder;
import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
import android.os.RemoteException;
import java.util.List;
/**
* Created by WaxBerry on 2018/3/22.
*/
public class BookManagerImpl extends Binder implements IBookManager {
/** Construct the stub at attach it to the interface. */
public BookManagerImpl() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into IBookManager interface,
* generating a proxy if needed.
*/
public static IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof IBookManager))) {
return ((IBookManager)iin);
}
return new BookManagerImpl.Proxy(obj);
}
@Override
public IBinder asBinder() {
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
Book _arg0;
if ((0!=data.readInt())) {
_arg0 = Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
@Override
public List<Book> getBookList() throws RemoteException {
//TODO 待實(shí)現(xiàn)
return null;
}
@Override
public void addBook(Book book) throws RemoteException {
//TODO 待實(shí)現(xiàn)
}
private static class Proxy implements IBookManager {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override public java.util.List<Book> getBookList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addBook(Book book) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book!=null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
}
實(shí)際開發(fā)中完全可以通過AIDL文件讓系統(tǒng)自動生成,手動去寫的意義在于可以讓我們更加理解Binder的工作原理,同時也提供了一種不通過AIDL文件來實(shí)現(xiàn)Binder的新方式。也就是說,AIDL并不是實(shí)現(xiàn)Binder的必須品。如果使我們手寫的Binder,那么服務(wù)端只需要創(chuàng)建一個BookManagerImpl的對象并在Service的onBindd方法中返回即可。最后,是否手動實(shí)現(xiàn)Binder沒有本質(zhì)區(qū)別,二者的工作原理完全一樣,AIDL文件的本質(zhì)是系統(tǒng)為我們提供了一種快速實(shí)現(xiàn)Binder的工具僅此而已。
接下來是Binder的兩個很重要的方法:linkToDeath和unlinkToDeath。Binder運(yùn)行在服務(wù)端進(jìn)程,如果服務(wù)端進(jìn)程由于某種原因被異常終止,這個時候我們到服務(wù)端的Binder連接斷裂(稱之為Binder死亡),會導(dǎo)致我們的遠(yuǎn)程調(diào)用失敗。更為關(guān)鍵的是,如果我們不知道Binder連接已經(jīng)斷裂,那么客戶端的功能就會收到影響。為了解決這個問題,Binder中提供了兩個配對的方法linkToDeath和unlinkToDeath,通過linkToDeath我們可以為Binder設(shè)置一個死亡代理,當(dāng)Binder死亡時,我們會收到通知,這個時候我們就可以重新發(fā)起連接請求從而恢復(fù)連接。
為Binder設(shè)置死亡代理的方法:首先聲明一個DeathRecipient對象。DeathRecipient是一個接口,其內(nèi)部只有一個方法binderDied,我們需要實(shí)現(xiàn)這個方法,當(dāng)Binder死亡的時候,系統(tǒng)就會回調(diào)binderDied方法,然后我們就可以移除之前綁定的binder代理并重新綁定遠(yuǎn)程服務(wù):
//給Binder設(shè)置死亡代理(set a Death Recipient for a binder)
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (mBookManager == null) {
return;
}
mBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mBookManager = null;
//TODO : 這里重新綁定遠(yuǎn)程Service
}
};
其次,在客戶端綁定遠(yuǎn)程服務(wù)成功后,給binder設(shè)置死亡代理:
mService = IMessageBoxManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient, 0);
其中l(wèi)inkToDeath的第二個參數(shù)為是個標(biāo)記位,我們直接設(shè)為0即可。經(jīng)過上面兩個步驟,就給我們的Binder設(shè)置了死亡代理,當(dāng)Binder死亡的時候既可以收到通知了。另外,通過binder的方法isBinderAlive也可以判斷Binder是否死亡。