一.Android IPC簡介
IPC 是 Inter-Progress Communication 的縮寫,意思為進程間通信或者跨進程通信是指兩個進程之間進行數據相互交換的過程。
1.說起進程間通信,我們先應了解什么是進程?什么是線程?
按照操作系統(tǒng)中的描述,線程時 CPU 調度最小的單元,同時線程是一種有限的系統(tǒng)資源;二進程一般指一個執(zhí)行單元,在 PC 或者移動設備上指一個程序或一個應用。
一個進程可包括多個線程。簡單情況下,一個進程可以只有一個線程,即主線程,又叫UI線程,在 UI 線程才能操作界面元素。
如果在UI線程執(zhí)行大量耗時任務會造成界面無法響應,嚴重影響用戶體驗,該情況在PC和移動設備都會存在,Android 中管他叫 ANR 異常(Application Not Responding),即應用無響應。解決這個問題就要把一些耗時的任務放在線程中。
2.IPC 機制存在于每一種操作系統(tǒng)
IPC 并不是 Android 獨有的,任何操作系統(tǒng)都需要有對應的 IPC 機制。例如Windows 上可通過剪切板,管道等進行進程間通信;Linux 上可以通過命名管道,共享共存進行進程間通信。
對于 Android 來說,它是一種基于 Linux 內核的移動操作系統(tǒng),但它的進程間方式并不完全繼承自 Linux。在 Android 中最有特色的進程間通信方式就是 Binder 了,通過它可輕松實現進程間通信;此外 Android 還支持 Socket,通過它可實現任意兩個終端之間的通信,當然他也可實現同一設備上兩個人進程之間的通信。
3.多進程的使用情景
多進程使用大致分兩種情景。
①第一種情況是由于應用自身原因要采取多進程。原因有很多,比如有的模塊由于特殊原因需運行在單獨的進程中;又比如為了加大一個應用可使用的內存,需要通過多進程獲取多分內存空間。(Android 對單個應用所使用的最大內存做了限制早起一些版本是 16MB,不同設備大小不同。)
②另一種情況是當前應用需要向其他應用獲取數據,由于是兩個應用,所以必須采取跨進程的方式。甚至說我們通過系統(tǒng)提供的 ContentProvider 去查詢數據的時候,就是一種進程間通信,只不過通信細節(jié)被系統(tǒng)內部屏蔽了,所以我們無法感知而已。
二. Android 中的多進程模式
正式介紹進程間通信前,我們需理解 Android 中多進程模式。通過給四大組件指定 android:process 屬性,我們可以輕易開啟多進程模式,但看起來簡單,實則暗藏殺機,有時通過多進程帶給我們的好處遠不足以彌補使用多進程帶給我們的代碼層面的負面影響。
開啟多進程模式
正常情況下,在 Android 中多進程是指在一個應用中存在多個進程的情況,因此這里不討論兩個應有之間的多進程情況。首先,在 Android 中使用多進程只有一種方法,那就是通過給四大組件(Activity,Service,Receiver,ContentProvider)指定 android:process 屬性,我們無法給一個線程或實體類指定其運行所在的進程。其實還有另一種非常規(guī)的多進程方法,那就是通過JNI在native層去fork一個新的進程,但這種方法屬于特殊情況,并不常用,所欲暫時不用考慮這種方式。
<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 屬性,且他們的屬性值不同,這一位當前應用增加了兩個新的進程。假設當前包名為“com.practive.xingxinyu.myapplication”,當 SecondActivity 啟動,系統(tǒng)會為他創(chuàng)建一個單獨的進程,進程名叫:“com.practive.xingxinyu.myapplication:remote”;當 ThirdActivity 啟動,系統(tǒng)會為他創(chuàng)建一個單獨的進程,進程名叫:“com.practive.xingxinyu.myapplication.remote”。同時入口 Activity 是 MainActivity ,沒有為它指定 process 屬性,那么它運行在默認進程中,默認進程名為包名。
關于私有進程&公有進程:SecondActivity 的 android:process 屬性為 “:remote”,這里的“:”的含義是是指在當前的進程名前附加上當前的包名,這是一種簡寫的方法。其次,進程名以“.”或“:”開頭的進程屬于當前應用的私有進程,其他應用的組件不可以和它跑在同一進程中;而進程名以小寫字母開頭的進程屬于全局進程,其他應用通過 ShareUID 方式可以和它跑在同一個進程中。
Android 系統(tǒng)會為每個應用分配一個唯一的 UID,具有相同UID的應用才能共享數據。我要說的是,兩個應用通過 ShareUID 跑在統(tǒng)一進程中是有要求的,需要這兩個應用有相同的 ShareUID 且簽名相同才行。這種情況下,無論他們能否泡在同一進程中,他們都能互相訪問對方私有數據,例如:data 目錄、組件信息等。當然,如果他們跑在統(tǒng)一進程中,他們還可以除了能共享 data 目錄、組件信息,還能共享內存數據。
多進程模式的運行機制
Android 為每一個應用分配了一個獨立的虛擬機,也可以說為每一個進程分配了一個獨立的虛擬機,不同的虛擬機在內存上有不同的地址空間,這就導致在不同的虛擬機中訪問同一個類的對象會產生多份副本,所以各進程間是互不干擾的。舉個例子:當在一個進程內對一個 public 的靜態(tài)成員變量的值進行修改,此操作對其他進程不會造成影響,其他進程中這個 public 的靜態(tài)成員變量的值并不會發(fā)生改變。
鑒于此機制,所有運行在不同進程的四大組件,只要他們之間需要通過內存來共享數據,都會共享失敗,這也是多進程帶來的主要影響。
一般來說,使用多進程會造成如下幾個問題:
1.靜態(tài)成員和單例模式完全失效。
2.線程同步機制完全失效。
3.SharedPreferences 的可靠性下降。
4.Application 會多次重建。
第1個問題已經舉例分析。第2個問題和第1個問題本質是一致的,既然不是一塊內存了,那么無論是鎖對象還是鎖全局類都無法保證線程同步,因為不同進程鎖的是不同對象。第3個問題是由于 SharedPreferences 不支持多個進程同時進行操作,否則有一定幾率丟失數據,因為 SharedPreferences 底層是通過讀寫 XML 文件實現的,所以并發(fā)讀/寫都有可能出現問題。第4個問題,當一個組件泡在一個新的進程,由于系統(tǒng)要創(chuàng)建新的進程并分配獨立的虛擬機,所以這個過程就是啟動一個應用的過程,也就是系統(tǒng)有吧這個應用重新啟動了一遍,既然重啟了,那么自然會新建一個 Application 了。
這里我們分析了一些多進程帶來的問題,為了解決這些問題系統(tǒng)提供了很多跨進程通信的方法。比如通過 Intent 來傳遞數據、共享文件和 SharedPreferences ;基于 Binder 的 Messenger 和 AIDL 以及 Socket 等。
IPC 基礎概念介紹
IPC 主要包含 Serializable 接口、Parcelable 接口以及 Binder。
Serializable 接口和 Parcelable 接口可以完成對象的序列化,當我們需要通過 Intent 和 Binder 傳輸數據時就需要使用到Serializable 接口和 Palrcelable 接口。還有的時候我們需要把對象持久化保存到設備上或網絡傳輸給其他客戶端,可以用到 Serializable 接口來完成對象的持久化。
Serializable 接口
Serializable 是 JAVA 提供的一個序列化接口,是一個空接口,可以為對象提供標準的序列化和反序列化操作。使用方法很簡單,只用在類的聲明中制定一個類似下面的標識即可自動實現默認序列化過程。
private static final long serialVersionUID = 6286579102573831569L;
如上所示想讓一個對象實現序列化,只需要這個類實現 Serializable 接口并聲明一個 serialVersionUID 即可。
serialVersionUID可以手動編寫,也可以自動生成。方法是先下載插件:Android studio -》 preferences -》 Plugins —》 GenerateSerialVersionUID 點擊 install。(如下圖所示)

下一步是設置提示:Android studio -》 preferences -》 Inspections -》 勾選中下圖幾項即可。

下面所示的 User 類是一個實現了 Serializable 接口的類,它是可以被序列化和反序列化的。
public class User implements Serializable {
private static final long serialVersionUID = 6286579102573831569L;
public int age;
public String name;
public boolean isMale;
}
如何進行對象的序列化和反序列化非常簡單,只需要采用 ObjectOutputStream 和 ObjectInputStream 即可輕松實現。下面簡單舉例。
//序列化過程
User user = new User(12,"jack",true);
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("cache.txt"));
out.writeObject(user);
out.close();
//反序列化過程
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("cache.txt"));
User newUser = (User) in.readObject();
in.close();
上述代碼采用 Serializable 方式序列化對象,只需把實現了 Serializable 接口的 User 對象寫到文件中就可以快速恢復了,回復后的對象 newUser 和 user 內容完全一樣,但不是同一對象。
其實,即使不指定 serialVersionUID 也可以實現序列化。但是系統(tǒng)既然提供了這個 serialVersionUID ,那么它必定是有用的。
這個 serialVersionUID 是用來輔助序列化和反序列化過程的,原則上序列化后數據中的 serialVersionUID 只有和當前類的 serialVersionUID 相同才能正常地被反序列化。
Serializable 的詳細工作機制是這樣的:序列化時,系統(tǒng)會把當前類的 serialVersionUID 寫入序列化文件中,當反序列化時系統(tǒng)回去檢測文件中的 serialVersionUID 是否和當前類的 serialVersionUID 一致,若一致,說明序列化的類和當前類的版本是相同的,這時就可以成功反序列化;否則就說明當前類和序列化的類相比發(fā)生了某些變化,比如修改了成員變量、類型或修改了類名,這時候都是無法正常反序列化的,反序列化失敗,程序就會 crash。
Parcelable 接口
Parcelable 也是一個接口,只要實現這個接口,一個類的對象就可以實現序列化并通過 Intent 和Binder 傳遞。下面實力一個典型用法。
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 內部包裝了可序列化的數據,在序列化過程中需要實現的功能有:序列化、反序列化和內容描述。
序列化功能由 writeToParcel 方法來完成,最終是通過 Parcel 中的一系列 write 方法完成的;反序列化功能由 CREATOR 來完成,其內部標明了如何創(chuàng)建序列化對象和數組,并通過 Parcel 的一系列 read 方法完成反序列化過程;內容描述由 describeContents 方法完成,幾乎所有情況下該方法的返回都為0,僅在當前對象中存在文件描述時此方法返回為1。特別需要注意的是,PUser(Parcel in) 方法中,因為book是另一個可序列化對象,所以它的反序列化過程需要傳遞當前線程的上下文加載器,否則會報無法找到類的錯誤。
系統(tǒng)為我們提供了許多實現了 Parcelable 接口的類,他們都是可以直接序列化的,例如:Intent 、 Bundle 、 Bitmap 等,同時 List 和 Map 也可以序列化,前提是他們里面的每一個元素也是序列化的。
那么在實現序列化的時候,我們在 Serializable 和 Parcelable 二者之間該如何選去呢?—— Serializable 和 Parcelable 之間的區(qū)別
Serializable 是 Java 中的序列化接口,其使用簡單但開銷較大,序列化和反序列化過程需要大量的 I/O 操作。
而 Parcelable 是 Android 中的序列化方式,因此更適合用在 Android 平臺,它的缺點是使用起來稍微麻煩,但效率很高,因此我們要首選 Parcelable。 Parcelable 主要用在內存序列化上;通過 Parcelable 將對象序列化到儲存設備中或將對象序列化通過網絡傳輸也是可以的,但這個過程會稍顯復雜,因此這兩種情況下建議使用 Serializable。
幫忙點個贊再走嘛~
點贊暴富- 【個人主頁】 點擊關注我,TuTu兔 會持續(xù)更新分享更多姿勢喲~ (????) ~
