IPC機(jī)制(Serializable、Parcelable)——深度學(xué)習(xí)

一.Android IPC簡(jiǎn)介

IPC 是 Inter-Progress Communication 的縮寫(xiě),意思為進(jìn)程間通信或者跨進(jìn)程通信是指兩個(gè)進(jìn)程之間進(jìn)行數(shù)據(jù)相互交換的過(guò)程。

1.說(shuō)起進(jìn)程間通信,我們先應(yīng)了解什么是進(jìn)程?什么是線程?

按照操作系統(tǒng)中的描述,線程時(shí) CPU 調(diào)度最小的單元,同時(shí)線程是一種有限的系統(tǒng)資源;二進(jìn)程一般指一個(gè)執(zhí)行單元,在 PC 或者移動(dòng)設(shè)備上指一個(gè)程序或一個(gè)應(yīng)用。

一個(gè)進(jìn)程可包括多個(gè)線程。簡(jiǎn)單情況下,一個(gè)進(jìn)程可以只有一個(gè)線程,即主線程,又叫UI線程,在 UI 線程才能操作界面元素。

如果在UI線程執(zhí)行大量耗時(shí)任務(wù)會(huì)造成界面無(wú)法響應(yīng),嚴(yán)重影響用戶體驗(yàn),該情況在PC和移動(dòng)設(shè)備都會(huì)存在,Android 中管他叫 ANR 異常(Application Not Responding),即應(yīng)用無(wú)響應(yīng)。解決這個(gè)問(wèn)題就要把一些耗時(shí)的任務(wù)放在線程中。

2.IPC 機(jī)制存在于每一種操作系統(tǒng)

IPC 并不是 Android 獨(dú)有的,任何操作系統(tǒng)都需要有對(duì)應(yīng)的 IPC 機(jī)制。例如Windows 上可通過(guò)剪切板,管道等進(jìn)行進(jìn)程間通信;Linux 上可以通過(guò)命名管道,共享共存進(jìn)行進(jìn)程間通信。

對(duì)于 Android 來(lái)說(shuō),它是一種基于 Linux 內(nèi)核的移動(dòng)操作系統(tǒng),但它的進(jìn)程間方式并不完全繼承自 Linux。在 Android 中最有特色的進(jìn)程間通信方式就是 Binder 了,通過(guò)它可輕松實(shí)現(xiàn)進(jìn)程間通信;此外 Android 還支持 Socket,通過(guò)它可實(shí)現(xiàn)任意兩個(gè)終端之間的通信,當(dāng)然他也可實(shí)現(xiàn)同一設(shè)備上兩個(gè)人進(jìn)程之間的通信。

3.多進(jìn)程的使用情景

多進(jìn)程使用大致分兩種情景。

①第一種情況是由于應(yīng)用自身原因要采取多進(jìn)程。原因有很多,比如有的模塊由于特殊原因需運(yùn)行在單獨(dú)的進(jìn)程中;又比如為了加大一個(gè)應(yīng)用可使用的內(nèi)存,需要通過(guò)多進(jìn)程獲取多分內(nèi)存空間。(Android 對(duì)單個(gè)應(yīng)用所使用的最大內(nèi)存做了限制早起一些版本是 16MB,不同設(shè)備大小不同。)

②另一種情況是當(dāng)前應(yīng)用需要向其他應(yīng)用獲取數(shù)據(jù),由于是兩個(gè)應(yīng)用,所以必須采取跨進(jìn)程的方式。甚至說(shuō)我們通過(guò)系統(tǒng)提供的 ContentProvider 去查詢數(shù)據(jù)的時(shí)候,就是一種進(jìn)程間通信,只不過(guò)通信細(xì)節(jié)被系統(tǒng)內(nèi)部屏蔽了,所以我們無(wú)法感知而已。


二. Android 中的多進(jìn)程模式

正式介紹進(jìn)程間通信前,我們需理解 Android 中多進(jìn)程模式。通過(guò)給四大組件指定 android:process 屬性,我們可以輕易開(kāi)啟多進(jìn)程模式,但看起來(lái)簡(jiǎn)單,實(shí)則暗藏殺機(jī),有時(shí)通過(guò)多進(jìn)程帶給我們的好處遠(yuǎn)不足以彌補(bǔ)使用多進(jìn)程帶給我們的代碼層面的負(fù)面影響。

開(kāi)啟多進(jìn)程模式

正常情況下,在 Android 中多進(jìn)程是指在一個(gè)應(yīng)用中存在多個(gè)進(jìn)程的情況,因此這里不討論兩個(gè)應(yīng)有之間的多進(jìn)程情況。首先,在 Android 中使用多進(jìn)程只有一種方法,那就是通過(guò)給四大組件(Activity,Service,Receiver,ContentProvider)指定 android:process 屬性,我們無(wú)法給一個(gè)線程或?qū)嶓w類(lèi)指定其運(yùn)行所在的進(jìn)程。其實(shí)還有另一種非常規(guī)的多進(jìn)程方法,那就是通過(guò)JNI在native層去fork一個(gè)新的進(jìn)程,但這種方法屬于特殊情況,并不常用,所欲暫時(shí)不用考慮這種方式。


<activity

        android:name="com.practive.xingxinyu.myapplication.MainActivity"

        android:configChanges="orientation|keyboardHidden">

      <intent-filter>

        <action android:name="android.intent.action.MAIN"/>

        <category android:name="android.intent.category.LAUNCHER"/>

      </intent-filter>

      </activity>

    <activity

        android:name="com.practive.xingxinyu.myapplication.SecondActivity"

        android:process=":remote"/>

    <activity

        android:name="com.practive.xingxinyu.myapplication.ThirdActivity"

        android:process="com.practive.xingxinyu.myapplication.remote"/>

上面的 SecondActivity 和 ThirdActivity 分別指定了 process 屬性,且他們的屬性值不同,這一位當(dāng)前應(yīng)用增加了兩個(gè)新的進(jìn)程。假設(shè)當(dāng)前包名為“com.practive.xingxinyu.myapplication”,當(dāng) SecondActivity 啟動(dòng),系統(tǒng)會(huì)為他創(chuàng)建一個(gè)單獨(dú)的進(jìn)程,進(jìn)程名叫:“com.practive.xingxinyu.myapplication:remote”;當(dāng) ThirdActivity 啟動(dòng),系統(tǒng)會(huì)為他創(chuàng)建一個(gè)單獨(dú)的進(jìn)程,進(jìn)程名叫:“com.practive.xingxinyu.myapplication.remote”。同時(shí)入口 Activity 是 MainActivity ,沒(méi)有為它指定 process 屬性,那么它運(yùn)行在默認(rèn)進(jìn)程中,默認(rèn)進(jìn)程名為包名。

關(guān)于私有進(jìn)程&公有進(jìn)程:SecondActivity 的 android:process 屬性為 “:remote”,這里的“:”的含義是是指在當(dāng)前的進(jìn)程名前附加上當(dāng)前的包名,這是一種簡(jiǎn)寫(xiě)的方法。其次,進(jìn)程名以“.”或“:”開(kāi)頭的進(jìn)程屬于當(dāng)前應(yīng)用的私有進(jìn)程,其他應(yīng)用的組件不可以和它跑在同一進(jìn)程中;而進(jìn)程名以小寫(xiě)字母開(kāi)頭的進(jìn)程屬于全局進(jìn)程,其他應(yīng)用通過(guò) ShareUID 方式可以和它跑在同一個(gè)進(jìn)程中。

Android 系統(tǒng)會(huì)為每個(gè)應(yīng)用分配一個(gè)唯一的 UID,具有相同UID的應(yīng)用才能共享數(shù)據(jù)。我要說(shuō)的是,兩個(gè)應(yīng)用通過(guò) ShareUID 跑在統(tǒng)一進(jìn)程中是有要求的,需要這兩個(gè)應(yīng)用有相同的 ShareUID 且簽名相同才行。這種情況下,無(wú)論他們能否泡在同一進(jìn)程中,他們都能互相訪問(wèn)對(duì)方私有數(shù)據(jù),例如:data 目錄、組件信息等。當(dāng)然,如果他們跑在統(tǒng)一進(jìn)程中,他們還可以除了能共享 data 目錄、組件信息,還能共享內(nèi)存數(shù)據(jù)。

多進(jìn)程模式的運(yùn)行機(jī)制

Android 為每一個(gè)應(yīng)用分配了一個(gè)獨(dú)立的虛擬機(jī),也可以說(shuō)為每一個(gè)進(jìn)程分配了一個(gè)獨(dú)立的虛擬機(jī),不同的虛擬機(jī)在內(nèi)存上有不同的地址空間,這就導(dǎo)致在不同的虛擬機(jī)中訪問(wèn)同一個(gè)類(lèi)的對(duì)象會(huì)產(chǎn)生多份副本,所以各進(jìn)程間是互不干擾的。舉個(gè)例子:當(dāng)在一個(gè)進(jìn)程內(nèi)對(duì)一個(gè) public 的靜態(tài)成員變量的值進(jìn)行修改,此操作對(duì)其他進(jìn)程不會(huì)造成影響,其他進(jìn)程中這個(gè) public 的靜態(tài)成員變量的值并不會(huì)發(fā)生改變。

鑒于此機(jī)制,所有運(yùn)行在不同進(jìn)程的四大組件,只要他們之間需要通過(guò)內(nèi)存來(lái)共享數(shù)據(jù),都會(huì)共享失敗,這也是多進(jìn)程帶來(lái)的主要影響。

一般來(lái)說(shuō),使用多進(jìn)程會(huì)造成如下幾個(gè)問(wèn)題:

  • 1.靜態(tài)成員和單例模式完全失效。

  • 2.線程同步機(jī)制完全失效。

  • 3.SharedPreferences 的可靠性下降。

  • 4.Application 會(huì)多次重建。

第1個(gè)問(wèn)題已經(jīng)舉例分析。第2個(gè)問(wèn)題和第1個(gè)問(wèn)題本質(zhì)是一致的,既然不是一塊內(nèi)存了,那么無(wú)論是鎖對(duì)象還是鎖全局類(lèi)都無(wú)法保證線程同步,因?yàn)椴煌M(jìn)程鎖的是不同對(duì)象。第3個(gè)問(wèn)題是由于 SharedPreferences 不支持多個(gè)進(jìn)程同時(shí)進(jìn)行操作,否則有一定幾率丟失數(shù)據(jù),因?yàn)?SharedPreferences 底層是通過(guò)讀寫(xiě) XML 文件實(shí)現(xiàn)的,所以并發(fā)讀/寫(xiě)都有可能出現(xiàn)問(wèn)題。第4個(gè)問(wèn)題,當(dāng)一個(gè)組件泡在一個(gè)新的進(jìn)程,由于系統(tǒng)要?jiǎng)?chuàng)建新的進(jìn)程并分配獨(dú)立的虛擬機(jī),所以這個(gè)過(guò)程就是啟動(dòng)一個(gè)應(yīng)用的過(guò)程,也就是系統(tǒng)有吧這個(gè)應(yīng)用重新啟動(dòng)了一遍,既然重啟了,那么自然會(huì)新建一個(gè) Application 了。

這里我們分析了一些多進(jìn)程帶來(lái)的問(wèn)題,為了解決這些問(wèn)題系統(tǒng)提供了很多跨進(jìn)程通信的方法。比如通過(guò) Intent 來(lái)傳遞數(shù)據(jù)、共享文件和 SharedPreferences ;基于 Binder 的 Messenger 和 AIDL 以及 Socket 等。


IPC 基礎(chǔ)概念介紹

IPC 主要包含 Serializable 接口、Parcelable 接口以及 Binder。

Serializable 接口和 Parcelable 接口可以完成對(duì)象的序列化,當(dāng)我們需要通過(guò) Intent 和 Binder 傳輸數(shù)據(jù)時(shí)就需要使用到Serializable 接口和 Palrcelable 接口。還有的時(shí)候我們需要把對(duì)象持久化保存到設(shè)備上或網(wǎng)絡(luò)傳輸給其他客戶端,可以用到 Serializable 接口來(lái)完成對(duì)象的持久化。

Serializable 接口

Serializable 是 JAVA 提供的一個(gè)序列化接口,是一個(gè)空接口,可以為對(duì)象提供標(biāo)準(zhǔn)的序列化和反序列化操作。使用方法很簡(jiǎn)單,只用在類(lèi)的聲明中制定一個(gè)類(lèi)似下面的標(biāo)識(shí)即可自動(dòng)實(shí)現(xiàn)默認(rèn)序列化過(guò)程。


private static final long serialVersionUID = 6286579102573831569L;

如上所示想讓一個(gè)對(duì)象實(shí)現(xiàn)序列化,只需要這個(gè)類(lèi)實(shí)現(xiàn) Serializable 接口并聲明一個(gè) serialVersionUID 即可。

serialVersionUID可以手動(dòng)編寫(xiě),也可以自動(dòng)生成。方法是先下載插件:Android studio -》 preferences -》 Plugins —》 GenerateSerialVersionUID 點(diǎn)擊 install。(如下圖所示)

serialVersionUID插件安裝

下一步是設(shè)置提示:Android studio -》 preferences -》 Inspections -》 勾選中下圖幾項(xiàng)即可。

設(shè)置serialVersionUID生成提示.png

下面所示的 User 類(lèi)是一個(gè)實(shí)現(xiàn)了 Serializable 接口的類(lèi),它是可以被序列化和反序列化的。


public class User implements Serializable {

  private static final long serialVersionUID = 6286579102573831569L;



  public int age;

  public String name;

  public boolean isMale;

}

如何進(jìn)行對(duì)象的序列化和反序列化非常簡(jiǎn)單,只需要采用 ObjectOutputStream 和 ObjectInputStream 即可輕松實(shí)現(xiàn)。下面簡(jiǎn)單舉例。


//序列化過(guò)程

    User user = new User(12,"jack",true);

    ObjectOutputStream out = new ObjectOutputStream(

        new FileOutputStream("cache.txt"));

    out.writeObject(user);

    out.close();



    //反序列化過(guò)程

    ObjectInputStream in = new ObjectInputStream(

        new FileInputStream("cache.txt"));

    User newUser = (User) in.readObject();

    in.close();

上述代碼采用 Serializable 方式序列化對(duì)象,只需把實(shí)現(xiàn)了 Serializable 接口的 User 對(duì)象寫(xiě)到文件中就可以快速恢復(fù)了,回復(fù)后的對(duì)象 newUser 和 user 內(nèi)容完全一樣,但不是同一對(duì)象。

其實(shí),即使不指定 serialVersionUID 也可以實(shí)現(xiàn)序列化。但是系統(tǒng)既然提供了這個(gè) serialVersionUID ,那么它必定是有用的。

這個(gè) serialVersionUID 是用來(lái)輔助序列化和反序列化過(guò)程的,原則上序列化后數(shù)據(jù)中的 serialVersionUID 只有和當(dāng)前類(lèi)的 serialVersionUID 相同才能正常地被反序列化。

Serializable 的詳細(xì)工作機(jī)制是這樣的:序列化時(shí),系統(tǒng)會(huì)把當(dāng)前類(lèi)的 serialVersionUID 寫(xiě)入序列化文件中,當(dāng)反序列化時(shí)系統(tǒng)回去檢測(cè)文件中的 serialVersionUID 是否和當(dāng)前類(lèi)的 serialVersionUID 一致,若一致,說(shuō)明序列化的類(lèi)和當(dāng)前類(lèi)的版本是相同的,這時(shí)就可以成功反序列化;否則就說(shuō)明當(dāng)前類(lèi)和序列化的類(lèi)相比發(fā)生了某些變化,比如修改了成員變量、類(lèi)型或修改了類(lèi)名,這時(shí)候都是無(wú)法正常反序列化的,反序列化失敗,程序就會(huì) crash。


Parcelable 接口

Parcelable 也是一個(gè)接口,只要實(shí)現(xiàn)這個(gè)接口,一個(gè)類(lèi)的對(duì)象就可以實(shí)現(xiàn)序列化并通過(guò) Intent 和Binder 傳遞。下面實(shí)力一個(gè)典型用法。


public class PUser implements Parcelable {

  public int userId;

  public String userName;

  public boolean isMale;

  public Book book;

  public PUser(int userId, String userName, boolean isMale) {

    this.userId = userId;

    this.userName = userName;

    this.isMale = isMale;

  }

  public static final Creator<PUser> CREATOR = new Creator<PUser>() {

    @Override

    public PUser createFromParcel(Parcel in) {

      return new PUser(in);

    }

    @Override

    public PUser[] newArray(int size) {

      return new PUser[size];

    }

  };

  @Override

  public int describeContents() {

    return 0;

  }

  @Override

  public void writeToParcel(Parcel dest, int flags) {

    dest.writeInt(userId);

    dest.writeString(userName);

    dest.writeByte((byte) (isMale ? 1 : 0));

  }

  protected PUser(Parcel in) {

    userId = in.readInt();

    userName = in.readString();

    isMale = in.readByte() != 0;

    book = in.readParcelableArray(Thread.currentThread().getContextClassLoader());

  }

}

從上述代碼可看出,Parcel 內(nèi)部包裝了可序列化的數(shù)據(jù),在序列化過(guò)程中需要實(shí)現(xiàn)的功能有:序列化、反序列化和內(nèi)容描述。

序列化功能由 writeToParcel 方法來(lái)完成,最終是通過(guò) Parcel 中的一系列 write 方法完成的;反序列化功能由 CREATOR 來(lái)完成,其內(nèi)部標(biāo)明了如何創(chuàng)建序列化對(duì)象和數(shù)組,并通過(guò) Parcel 的一系列 read 方法完成反序列化過(guò)程;內(nèi)容描述由 describeContents 方法完成,幾乎所有情況下該方法的返回都為0,僅在當(dāng)前對(duì)象中存在文件描述時(shí)此方法返回為1。特別需要注意的是,PUser(Parcel in) 方法中,因?yàn)閎ook是另一個(gè)可序列化對(duì)象,所以它的反序列化過(guò)程需要傳遞當(dāng)前線程的上下文加載器,否則會(huì)報(bào)無(wú)法找到類(lèi)的錯(cuò)誤。

系統(tǒng)為我們提供了許多實(shí)現(xiàn)了 Parcelable 接口的類(lèi),他們都是可以直接序列化的,例如:Intent 、 Bundle 、 Bitmap 等,同時(shí) List 和 Map 也可以序列化,前提是他們里面的每一個(gè)元素也是序列化的。

那么在實(shí)現(xiàn)序列化的時(shí)候,我們?cè)?Serializable 和 Parcelable 二者之間該如何選去呢?—— Serializable 和 Parcelable 之間的區(qū)別

Serializable 是 Java 中的序列化接口,其使用簡(jiǎn)單但開(kāi)銷(xiāo)較大,序列化和反序列化過(guò)程需要大量的 I/O 操作。

而 Parcelable 是 Android 中的序列化方式,因此更適合用在 Android 平臺(tái),它的缺點(diǎn)是使用起來(lái)稍微麻煩,但效率很高,因此我們要首選 Parcelable。 Parcelable 主要用在內(nèi)存序列化上;通過(guò) Parcelable 將對(duì)象序列化到儲(chǔ)存設(shè)備中或?qū)?duì)象序列化通過(guò)網(wǎng)絡(luò)傳輸也是可以的,但這個(gè)過(guò)程會(huì)稍顯復(fù)雜,因此這兩種情況下建議使用 Serializable。

  • 幫忙點(diǎn)個(gè)贊再走嘛~


    點(diǎn)贊暴富
  • 【個(gè)人主頁(yè)】 點(diǎn)擊關(guān)注我,TuTu兔 會(huì)持續(xù)更新分享更多姿勢(shì)喲~ (????) ~
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容