讀書筆記-藝術探索- IPC機制(基礎)

3. IPC機制(基礎)

3.0 前言

本文總結自任玉剛老師的《Android開發(fā)藝術探索》,文章中的【示例】在這里

3.1 Android IPC簡介

IPC (Inter-Process Communication粱快,含義為進程間通信或跨進程通信)右犹,是指兩個進程之間進行數(shù)據(jù)交換的過程控妻。Bundle赠法、文件共享震束、AIDL(Android Interface definition language一種android內(nèi)部進程通信接口的描述語言对雪,通過它我們可以定義進程間的通信接口河狐,涉及Binder連接池的概念)、Messenger瑟捣、ContentProvider馋艺、Socket都是進程間通信的方式。

進程:和線程是截然不同的概念迈套。線程CPU調(diào)度的最小單元捐祠,同時線程是一種有限的系統(tǒng)資源。而進程一般指一個執(zhí)行單元桑李,在PC和移動設備上指一個程序或者一個應用踱蛀。一個進程可以包含多個線程(是包含的關系)窿给。最簡單的情況下,一個進程中可以只有一個線程率拒,即主線程崩泡。Android中主線程也叫UI線程,在UI線程里面才能操作頁面元素(耗時的任務在主線程中執(zhí)行會造成界面無法響應:ANR猬膨,所以要把耗時的線程放在其他線程中)枝誊。

任何一個操作系統(tǒng)都需要有相應的IPC機制竭望,Android中最有特色的進程間通信方式就是Binder了,通過Binder可以輕松地實現(xiàn)進程間通信。除了Binder還有Socket止剖,也可以實現(xiàn)任意兩個終端之間的通信。

3.2 Android中的多進程模式

說到IPC的使用場景就必須提到多線程般渡,只有面對多進程的場景下蚕脏,才需要考慮進程間通信。多進程的情況分為2種:

  • 應用因為某些原因自身需要采用多進程模式來實現(xiàn)(有些模塊由于特殊原因需要運行在單獨的進程中污它、 為了加大一個應用可使用的內(nèi)存所以需要通過多線程來獲取多份內(nèi)存空間等)剖踊。
  • 當前應用需要向其他應用獲取數(shù)據(jù),由于是兩個應用衫贬,所以必須采用跨進程的方式來獲取所需的數(shù)據(jù)(用ContentProvider去查詢數(shù)據(jù)等)

不管由于何種原因德澈,我們采用了多進程的設計方法,應用中必須妥善處理進程間通信的各種問題固惯。

3.2.1 開啟多進程模式

正常情況下梆造,Android中多進程是指一個應用中存在多個進程的情況(暫不討論兩個應用之間的多進程情況,因為這樣操作起來比較方便葬毫,如果是不同進程間通信镇辉,那么它們分別是否屬于兩個應用沒有區(qū)別,原理一樣)贴捡。

Android中使用多進程只有一種方法忽肛,那就是給四大組件(Activity、Service烂斋、Recevier屹逛、ContentProvider)在AndroidMenifest中指定android:process屬性,除此之外沒有其他方法汛骂,也就是說我們無法給一個線程或者一個實體類指定其運行時所在的進程(其實還有另外一種非常規(guī)的多進程方法罕模,那就是通過JNI在native層去fork一個新的進程)。下面示例如何在Android中創(chuàng)建多進程:

<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屬性帘瞭,并且屬性值不同淑掌,這意味著當前應用又增加了兩個新進程。SecondActivity啟動時蝶念,系統(tǒng)會為它創(chuàng)建一個單獨的進程抛腕,進程名為“com.example.learn_002_ipc:remote”(當前包名為“com.example.learn_002_ipc”)诈悍;當ThirdActivity啟動時,系統(tǒng)也會為它創(chuàng)建一個單獨的進程兽埃,進程名為“com.example.learn_002_ipc.remote”同時入口Activity是MainActivity侥钳,沒有為它指定process屬性,那么它運行在默認進程中柄错,默認的進程名是包名舷夺,如圖:

image

進程列表末尾存在3個進程,進程id分別是6612售貌、6680给猾、6704,這說明應用成功使用了多進程技術颂跨。關于上面的":remote"和"com.example.learn_002_ipc.remote"其實是有區(qū)別的:“:”的含義是指要在當前的進程名前面附加上當前的包名(簡寫)敢伸,對于SecondActivity來說,它完整的進程名為com.example.learn_002_ipc:remote恒削;對于ThirdActivity中的聲明方式來說池颈,它是一種完整的命名方式。并且钓丰,以“:”開頭的進程屬于當前應用的私有進程躯砰,其他應用的組件不可以和它跑在同一個進程中,而進程名不以“:”開頭的進程為全局進程携丁,其他應用可以通過ShareUID方式可以和它跑在同一進程中琢歇。

Android系統(tǒng)會為每個應用分配一個唯一的UID,具有相同的UID的應用才能共享數(shù)據(jù)梦鉴,而兩個應用通過ShareUID跑在同一個進程中要求要有相同的ShareUID并且簽名相同才可以李茫。在這種情況下,它們可以互相訪問對方的私有數(shù)據(jù)肥橙,比如data目錄魄宏、組件信息,共享內(nèi)存數(shù)據(jù)...或者說它們看起來像是一個應用的兩個部分快骗。

3.2.2 多進程模式的運行機制

“當應用開啟了多線程后娜庇,各種奇怪的現(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運行在一個單獨的進程中汁掠,Android為每一個應用分配了一個獨立的虛擬機(即每一個進程都分配一個獨立的虛擬機),不同的虛擬機在內(nèi)存分配上會有不同的地址空間集币,導致不同的虛擬機中訪問同一個對象會產(chǎn)生多份副本考阱。該例中進程com.example.learn_002_ipc和進程com.example.learn_002_ipc:remote都存在一個UserManager類,并且這兩個類互不干擾鞠苟,在一個進程中修改sUserId的值只會影響當前進程乞榨,對其他進程不會造成任何影響。

所有運行在不同進程中的四大組件当娱,只要它們之間需要同內(nèi)存來共享數(shù)據(jù)吃既,都會共享失敗,這也是多進程所帶來的主要影響跨细。正常情況下鹦倚,四大組件中間不可能不通過一個中間層來共享數(shù)據(jù),那么通過簡單地指定進程名來開啟多線程都會無法正確運行冀惭。當然特殊情況下震叙,某些組件之間不需要共享數(shù)據(jù),這時可以直接指定android:process屬性來開啟多線程散休,但是這種場景不常見捐友,幾乎所有情況都需要共享數(shù)據(jù)。

一般來說多進程會造成如下幾方面問題

  • 靜態(tài)成員和單例模式完全失效溃槐。(上例)
  • 線程同步機制完全失效匣砖。(不是同一塊內(nèi)存了,不管鎖對象還是鎖全局類都無法保證線程同步昏滴,因為不同進程鎖的不是同一個對象)
  • SharedPreferences可靠性下降猴鲫。(SharedPreferences不支持兩個進程同時執(zhí)行寫操作否則會導致一定機率的數(shù)據(jù)丟失,這是因為SharedPreferences是通過讀/寫XML文件實現(xiàn)的谣殊,并發(fā)讀/寫都有可能出問題)
  • Applicaion會多次創(chuàng)建拂共。(新建一個MyApplication類,注意在Manifest內(nèi)設置android:name姻几,然后在MyApplication的onCreate方法中每次啟動都打印一次當前線程的名字宜狐,再在上例中分別啟動3個Activity,locat中可以看出的Application創(chuàng)建了3次蛇捌,而且每次的進程名稱和進程id都不一樣)

總結:多進程模式中抚恒,不同進程的組件擁有獨立的虛擬機、Application络拌、內(nèi)存空間俭驮。這樣理解同一個應用間的多進程:相當于兩個不同的應用采用了SharedUID的模式。為了解決這個問題春贸,系統(tǒng)提供了很多跨進程通信方法(雖然不能直接地共享內(nèi)存混萝,但是還是可以實現(xiàn)數(shù)據(jù)交互):使用Intent來傳遞數(shù)據(jù)遗遵、共享文件SharedPreferences、基于Binder的MessengerAIDL逸嘀、Socket车要。

3.3 IPC基礎概念介紹

以下若干名詞都是IPC的基礎概念,先大概了解一下:Serializable和Parcelable接口可以完成對象的序列化過程崭倘,當我們需要通過Intent和Binder傳輸數(shù)據(jù)時就需要使用Parcelable或者Serilizable翼岁。還有的時候我們需要把對象持久化到存儲設備上或者通過網(wǎng)絡傳輸給其他客戶端,也需要Serializable來完成對象的持久化绳姨。

序列化 (Serialization):將對象的狀態(tài)信息轉換為可以存儲或傳輸?shù)男问降倪^程登澜。在序列化期間,對象將其當前狀態(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.

標記可以由ObjectOutputStream序列化并由ObjectInputStream反序列化的類谴仙。

該接口是Java提供的一個序列化接口,它是一個空接口碾盐,為對象提供標準的序列化和反序列化操作晃跺。使用Serializable來實現(xiàn)序列化相當簡單,只需要令該類實現(xiàn)Serializable接口毫玖,并在其聲明中指定一個標識(serialVersionUID掀虎,但這一步甚至不是必須的)即可自動實現(xiàn)默認的序列化過程:

public class User implements Serializable {
    private static final long serialVersionUID = 909090L;
    public int userId;
    public String userName;
    public boolean isMale;
}

如果不實現(xiàn)這個接口那么序列化的時候會遇到一個很耿直的異常:

java.io.WriteAbortedException: writing aborted.io.NotSerializableException

通過Serializable方式來實現(xiàn)對象的序列化,實現(xiàn)起來非常簡單付枫,幾乎所有工作都被系統(tǒng)自動完成了烹玉。進行對象的序列化和反序列化也非常簡單,只需采用ObjectOutputStream和ObjectInputStream即可輕松實現(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方式序列化對象的典型過程阐滩,只需把實現(xiàn)了Serializable接口的User對象寫到文件中就可以快速恢復了二打,恢復后的對象newUser和user的內(nèi)容完全一樣,但是兩者不是同一個對象掂榔。

關于serialVersionUID:這是用來輔助序列化和反序列化過程的(有用)继效,原則上序列化后的數(shù)據(jù)中的serialVersionUID只有和當前類的serialVersionUID相同才能正常地被反序列化。serialVersionUID的工作機制是:

序列化的時候系統(tǒng)把當前類的serialVersionUID寫入序列化的文件中(也有可能是其他中介)装获,當反序列化的時候就回去檢測文件中的serialVersionUID瑞信,看它是否和當前類的serialVersionUID一致喧伞,如果一致就說明序列化的類的版本和當前類的版本是相同的绩郎,這時候就可以成功反序列化潘鲫;否則說明當前類和序列化的類相比發(fā)生了某些變換肋杖,比如成員變量的數(shù)量、類型可能發(fā)生了改變状植,這個時候是無法正常反序列化的(報java.io.InvalidClassException)浊竟。

一般來說津畸,應該手動指定serialVersionUID的值,比如1L后频,也可以讓AS根據(jù)當前類的結構自動生成它的hash值(本質(zhì)一樣)卑惜,這樣序列化和反序列化時兩者的serialVersionUID是相同的驻售,因此可以正常進行反序列化。

如果不手動指定serialVersionUID的值毫痕,反序列化時當前類有所改變消请,比如增加或刪除了某些成員變量瘤旨,系統(tǒng)會重新計算當前類的hash值并把它賦值給serialVersionUID存哲,這時候類的serialVersionUID就和序列化數(shù)據(jù)中的serialVersionUID不一致祟偷,于是反序列化失敗,程序就會出現(xiàn)crash贺辰,所以手動指定serialVersionUID可以很大程度避免反序列化過程的失敗。

另外莽鸭,系統(tǒng)的默認序列化過程也是可以改變的(重寫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.

其實例可以寫入Parcel并從中恢復的類的接口巢块。實現(xiàn)Parcelable接口的類還必須有一個名為CREATOR的靜態(tài)字段族奢,該字段是實現(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的另一側(使用這里的各種方法來編寫特定類型南片,或者通用的Parcelable接口)的平坦數(shù)據(jù)庭敦,以及引用另一方接收的活IBinder對象的引用一個代理IBinder與Parcel中的原始IBinder連接。

在Android中也提供了新的序列化方式伞广,就是Parcelable接口嚼锄,使用Parcelabel接口來實現(xiàn)對象的序列化区丑,其過程要稍微復雜一些修陡,只要實現(xiàn)這個接口魄鸦,一個類的對象就可以實現(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中自由傳輸正卧,從上述代碼中可看出穗酥,序列化過程中需要實現(xiàn)的功能有序列化惠遏、反序列化和內(nèi)容描述节吮。

  • 序列化功能由writeToParcel方法來完成透绩,最終是通過Parcel中的一系列write方法**來完成的壁熄。
  • 反序列化功能由CREATOR來完成草丧,其內(nèi)部標明了如何創(chuàng)建序列化對象和數(shù)組昌执,并通過Parcel的一系列read方法**來完成反序列化過程。
  • 內(nèi)容描述功能由describeContents方法來完成**煤禽,幾乎所有情況下該方法都應該返回0檬果,僅當當前對象中存在文件描述符時选脊,此方法返回1知牌。

需要注意的是斤程,在User2(Parcel in) 方法中,由于user是另一個可序列化對象沮峡。以它的反序列化過程需要傳遞當前線程的上下文類加載器亿柑,否則會無法找到類的錯誤望薄。下面是Parcelable的方法說明:

方法 功能 標記位
createFromParcel(Parcel in) 從序列化后的對象中創(chuàng)建原始對象
new Array(Parcel in) 創(chuàng)建指定長度的原始對象數(shù)組
writeToParcel(Parcel out痕支, int flags) 將當前對象寫入序列化結構中卧须,其中flags標識有2種值:0或1笋籽。1時標識當前對象需要作為返回值返回车海,不能立即釋放資源容劳,幾乎所有情況都為0 PARCELABLE_WRITE_RETURN_VALUE
descriveContents 返回當前對象的內(nèi)容描述竭贩,如果含有文件描述符莺禁,返回1哟冬,否則返回0 CONTENTS_FILE_DESCRIPTOR

系統(tǒng)已經(jīng)為我們提供了很多實現(xiàn)了Parcelable接口的類浩峡,他們都是可以直接序列化的翰灾,比如Intent、Bundle平斩、Bitmap等绘面,同時List和Map也可以序列化揭璃,前提是它們里面的每個元素都是可序列化的塘辅。

關于Parcelable和Serializable的區(qū)別:

  • Serializable是Java中的序列化接口扣墩,使用簡單但是開銷很大(序列化的時候會產(chǎn)生大量的臨時變量扛吞,從而引起頻繁的GC)滥比,序列化和反序列化過程都需要大量I/O操作盲泛,主要用于將對象序列化到存儲設備(磁盤)中或者將對象序列化后通過網(wǎng)絡傳輸寺滚,因為Parcelable的過程較復雜村视。
  • Parcelable是Android中的序列化方式,因此更適合用在Android平臺上奶赔,缺點是操作稍微麻煩站刑,但是效率高绞旅,更適合用在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 : 可遠程對象的基類蝎土,是由IBinder定義的輕量級遠程過程調(diào)用機制的核心部分誊涯。該類是IBinder的一個實現(xiàn)暴构,它提供了創(chuàng)建此類對象的本地實現(xiàn)的標準支持。大多數(shù)開發(fā)人員不會直接實現(xiàn)這個類耗绿,而是使用aidl工具來描述所需的接口误阻,讓它生成適當?shù)腂inder子類究反。但是精耐,您可以直接從Binder派生實現(xiàn)您自己的自定義RPC協(xié)議黍氮,或直接實例化原始Binder對象以用作可跨進程共享的令牌(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 : 可遠程對象的基礎接口专执,輕量級遠程過程調(diào)用機制的核心部分本股,專為執(zhí)行進程內(nèi)和跨進程調(diào)用時的高性能而設計拄显。該接口描述了與可遠程對象交互的抽象協(xié)議。不要直接實現(xiàn)這個接口棘街,而是從Binder擴展遭殉。

從IPC角度來說险污,Binder是Android中的一種跨進程通信方式蛔糯,Binder還可以理解為一種虛擬的物理設備渤闷,它的設備驅動是/dev/binder,該通信方式在Linux中沒有蜒灰;

從Android Framework角度來說强窖,Binder是ServiceManager鏈接各種Manager(ActivityManager翅溺、WindowManager等等)和相應ManagerService的橋梁咙崎;

從Android應用層來說吨拍,Binder是客戶端和服務端進行同行的媒介羹饰,當bindService的時候,服務端會返回一個包含了服務端業(yè)務調(diào)用的Binder對象昼浦,通過這個Binder對象关噪,客戶端就可以獲取服務端提供的服務或者數(shù)據(jù)色洞,這里的服務包括普通服務和基于AIDL(Android Interface Definition Language )的服務火诸。

Android開發(fā)中置蜀,Binder主要用在Service中盯荤,包括AIDLMessenger秋秤,而Messenger的底層其實是AIDL(其中普通Service中的Binder不涉及進程間通信灼卢,較為簡單不涉及Binder的核心)鞋真。

所以這里選擇用AIDL來分析Binder的工作機制涩咖。新建Java包com.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是一個表示圖書信息的類零院,它實現(xiàn)了Parcelable接口告抄。Book.aidl是Book類在AIDL中的聲明打洼。IBookManager.aidl是我們定義的一個接口募疮,里面有2個方法:getBookList和addBook阿浓,其中getBookList用于從遠程服務端獲取圖書列表芭毙,而addBook用于往圖書列表中添加一本書退敦〕薨伲可以看到钝域,盡管Book類和IBookManager位于相同的包中网梢,但是再IBookManager中仍然要導入Book類战虏,這就是AIDL的特別之處(如果忘記導入了或者輸入的包地址錯誤烦感,會導致意料之外的錯誤)手趣。

下面是系統(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.
         *
         * 用于將服務端的Binder對象轉換成客戶端所需的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))) {  //如果客戶端和服務端位于同一進程姜胖,那么此方法返回的就是服務端的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.

這個類剛開始看起來邏輯混亂敷存,但是實際上還是很清晰的通過它我們可以清楚地了解到Binder的工作機制:

這個類結構其實很簡單,首先它++聲明了兩個方法getBookListaddBook帝雇,顯然這是我們在IBookManager.aidl中所聲明的方法++摊求,同時它還聲明了兩個整型的id分別用于標識這兩個方法室叉,這兩個id用于標識在transact(辦理)過程中客戶端所請求的到底是哪個方法茧痕。接著恼除,它聲明了一個內(nèi)部類Stub踪旷,這個Stub就是一個Binder類,當客戶端和服務端都位于同一個進程時豁辉,方法調(diào)用不會走跨進程的transact過程令野,而當兩者位于不同進程時,方法調(diào)用需要走transcat過程徽级,這個邏輯由Stub的內(nèi)部代理類Proxy來完成气破。

可以認識到餐抢,這個接口的核心實現(xiàn)就是它的內(nèi)部類Stub和Stub的內(nèi)部實現(xiàn)類Proxy现使,下面介紹這兩個類的每個方法的含義:

  • DESCRIPTOR:Binder的唯一標識,一般用當前Binder的類名旷痕,如本例中的"com.example.learn_002_ipc.aidl.IBookManager"碳锈。

  • asInterface(android.os.IBinder obj)用于將服務端Binder對象轉換成客戶端所需的AIDL接口類型的對象抖剿,這種轉換是區(qū)分進程的滥嘴,如果客戶端和服務端位于同一進程,那么此方法返回的就是服務端的Stub對象本身监憎,否則返回的是系統(tǒng)封裝后的Stub.proxy對象**绞呈。

  • asBinder :用于返回當前Binder對象

  • onTranscat該方法運行在服務端中的Binder線程池中团滥,當客戶端發(fā)起跨進程請求時,遠程請求會通過系統(tǒng)底層封裝后交由此方法來處理报强。服務端通過code可以確定客戶端所請求的目標方法是什么,接著從data中取出目標方法所需的參數(shù)(如果目標方法有返回值的話)拱燃。

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的關鍵是通過Binder.onTransact()進行transact()匹配秉溉。這些方法允許您將調(diào)用發(fā)送到IBinder對象,并分別接收進入Binder對象的調(diào)用。這個事務API是同步的召嘶,因此父晶,直到目標從Binder.onTransact()返回后,對transact()的調(diào)用才會返回弄跌。這是調(diào)用本地進程中存在的對象時的預期行為甲喝,并且基礎進程間通信(IPC)機制可確保在跨進程時應用這些相同的語義。

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

默認實現(xiàn)是一個返回false的存根埠胖。您需要重寫此操作以執(zhí)行相應的事務解組。如果你想調(diào)用這個淳玩,請調(diào)用transact()直撤。

  • Proxy#getBookList:該方法運行在客戶端**,當客戶端遠程調(diào)用此方法時蜕着,它的內(nèi)部實現(xiàn)是這樣的:
    • 首先創(chuàng)建該方法所需要的輸入型Parcel對象_data谋竖、輸出型Parcel對象_reply、返回值對象List(_result)承匣;
    • 然后把該方法的參數(shù)信息寫入_data中(如果有參數(shù)的話)蓖乘;
    • 接著調(diào)用transcat方法(android.os.IBinder.transact(...))來發(fā)起RPC(遠程過程調(diào)用)請求,同時當前線程掛起韧骗;
    • 然后服務端的onTranscat方法會被調(diào)用直到RPC過程返回后嘉抒,當前線程繼續(xù)執(zhí)行,并從_reply中取出RPC過程的返回結果;
    • 最后返回_reply中的數(shù)據(jù)宽闲。
  • Proxy#addBook:該方法運行在客戶端众眨,執(zhí)行過程個getBookList一樣,addBook沒有返回值所以不需要從_reply中取出返回值容诬。

說明一下:

  1. 當客戶端發(fā)起遠程請求時娩梨,由于++當前線程會被掛起++直至服務器進程返回數(shù)據(jù),所以如果一個遠程方法是很耗時的览徒,那么不能在UI線程中發(fā)起此遠程請求狈定;
  2. 由于服務端的Binder方法運行在Binder的線程池中,所以Binder方法不管是否耗時都應該采用同步的方式去實現(xiàn)习蓬。因為它已經(jīng)運行在一個線程中了纽什。

[圖片上傳失敗...(image-7114d0-1525872465108)]
【Binder的工作機制圖】

從上述分析過程來看,我們完全不可以提供AIDL文件即可實現(xiàn)Binder躲叼,之所以提供AIDL文件芦缰,是為了方便系統(tǒng)為我們生成代碼。現(xiàn)在參考IBookManager.java這個類的代碼寫一個一模一樣的類出來:

首先寫一個Binder的服務端

//寫一個Binder的服務端(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);
                }
            }
        }
    };

首先我們會實現(xiàn)一個創(chuàng)建了一個Stub對象并在內(nèi)部實現(xiàn)IBookManager的接口方法枫慷,然后在Service的onBind中返回這個Stub對象让蕾。因此浪规,從這一點來看,我們完全可以把Stub類提取出來直接作為一個獨立的Binder類來實現(xiàn)探孝,這樣IBookManager中就只剩接口本身了笋婿,這種分離的方式可以讓它的接口變得清晰點。

根據(jù)上面的思想顿颅,手動實現(xiàn)一個Binder可以通過以下步驟來完成:

  • 聲明一個AIDL性質(zhì)的可口缸濒,只需要繼承IInterface接口即可,IInterface接口中有一個asBinder方法粱腻。這個接口的實現(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;
    }
  • 實現(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 待實現(xiàn)
            return null;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            //TODO 待實現(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();
                }
            }
        }
    }

實際開發(fā)中完全可以通過AIDL文件讓系統(tǒng)自動生成讨永,手動去寫的意義在于可以讓我們更加理解Binder的工作原理,同時也提供了一種不通過AIDL文件來實現(xiàn)Binder的新方式遇革。也就是說卿闹,AIDL并不是實現(xiàn)Binder的必須品。如果使我們手寫的Binder萝快,那么服務端只需要創(chuàng)建一個BookManagerImpl的對象并在Service的onBindd方法中返回即可锻霎。最后,是否手動實現(xiàn)Binder沒有本質(zhì)區(qū)別揪漩,二者的工作原理完全一樣旋恼,AIDL文件的本質(zhì)是系統(tǒng)為我們提供了一種快速實現(xiàn)Binder的工具僅此而已

接下來是Binder的兩個很重要的方法:linkToDeathunlinkToDeath奄容。Binder運行在服務端進程冰更,如果服務端進程由于某種原因被異常終止,這個時候我們到服務端的Binder連接斷裂(稱之為Binder死亡)昂勒,會導致我們的遠程調(diào)用失敗蜀细。更為關鍵的是,如果我們不知道Binder連接已經(jīng)斷裂戈盈,那么客戶端的功能就會收到影響奠衔。為了解決這個問題,Binder中提供了兩個配對的方法linkToDeath和unlinkToDeath塘娶,通過linkToDeath我們可以為Binder設置一個死亡代理归斤,當Binder死亡時,我們會收到通知刁岸,這個時候我們就可以重新發(fā)起連接請求從而恢復連接脏里。

為Binder設置死亡代理的方法:首先聲明一個DeathRecipient對象。DeathRecipient是一個接口虹曙,其內(nèi)部只有一個方法binderDied膝宁,我們需要實現(xiàn)這個方法鸦难,當Binder死亡的時候,系統(tǒng)就會回調(diào)binderDied方法员淫,然后我們就可以移除之前綁定的binder代理并重新綁定遠程服務:

//給Binder設置死亡代理(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 : 這里重新綁定遠程Service
        }
    };

其次,在客戶端綁定遠程服務成功后击敌,給binder設置死亡代理:

mService = IMessageBoxManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient, 0);

其中l(wèi)inkToDeath的第二個參數(shù)為是個標記位介返,我們直接設為0即可。經(jīng)過上面兩個步驟沃斤,就給我們的Binder設置了死亡代理圣蝎,當Binder死亡的時候既可以收到通知了。另外衡瓶,通過binder的方法isBinderAlive也可以判斷Binder是否死亡徘公。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市哮针,隨后出現(xiàn)的幾起案子关面,更是在濱河造成了極大的恐慌,老刑警劉巖十厢,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件等太,死亡現(xiàn)場離奇詭異,居然都是意外死亡蛮放,警方通過查閱死者的電腦和手機缩抡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來包颁,“玉大人瞻想,你說我怎么就攤上這事∶浣溃” “怎么了蘑险?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長待锈。 經(jīng)常有香客問我漠其,道長,這世上最難降的妖魔是什么竿音? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任和屎,我火速辦了婚禮,結果婚禮上春瞬,老公的妹妹穿的比我還像新娘柴信。我一直安慰自己,他們只是感情好宽气,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布随常。 她就那樣靜靜地躺著潜沦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绪氛。 梳的紋絲不亂的頭發(fā)上唆鸡,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天,我揣著相機與錄音枣察,去河邊找鬼争占。 笑死,一個胖子當著我的面吹牛序目,可吹牛的內(nèi)容都是我干的臂痕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼猿涨,長吁一口氣:“原來是場噩夢啊……” “哼握童!你這毒婦竟也來了?” 一聲冷哼從身側響起叛赚,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤澡绩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后红伦,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體英古,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年昙读,在試婚紗的時候發(fā)現(xiàn)自己被綠了召调。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蛮浑,死狀恐怖唠叛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情沮稚,我是刑警寧澤艺沼,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站蕴掏,受9級特大地震影響障般,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜盛杰,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一挽荡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧即供,春花似錦定拟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽株依。三九已至,卻和暖如春延窜,著一層夾襖步出監(jiān)牢的瞬間恋腕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工逆瑞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吗坚,地道東北人。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓呆万,卻偏偏與公主長得像,于是被迫代替她去往敵國和親车份。 傳聞我的和親對象是個殘疾皇子谋减,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353