(《Android開發藝術探索》讀書筆記)
Android IPC基本概念:
IPC:Iner-Process Communication。意思爲進程間通信,兩個進程之間進行數據交換的過程。
IPC的使用場景:
一個應用因爲某些原因自身需要採用多進程模式來實現(比如某個特殊模塊需單獨運行在進程中,亦或大應用需獲取多份內存空間)
當前應用需要向其他應用獲取數據(比如ContentProvider,只不過系統屏蔽了通信細節)
Android中的多進程模式:
1、如何簡單的開啓多進程模式
常規的方法是給四大組件在AndroidMenifest中指定android:process屬性:
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize"
android:label="@string/app_name"
android:launchMode="standard" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.android.ipc.Second"
android:configChanges="screenLayout"
android:process=":remote" />
<activity
android:name="com.android.ipc.Third"
android:configChanges="screenLayout"
android:process="com.android.ipc.remote" />
非常規方法通過JNI在native層去fork一個新的進程。
可以看出當前應用新增了兩個進程。
以“:”開頭的含義是在當前的進程名前面附加上當前的包名,並且是屬於當前應用的私有進程。而另一個是完整的命名方式,不會附加包名信息,並且屬於全局進程。,其他應用通過ShareUID方式可以和它跑在同一個進程中。
多進程模式的運行機制:
每個進程都分配一個獨立的虛擬機、Application和內存空間,不同的虛擬機在內存分配上有不同的地址空間,這會導致在不同的虛擬機中訪問同一個類的的對象會產生多份副本。
所有運行在不同進程中的四大組件,只要他們通過內存來共享數據,都會共享失敗。
所以使用多進程會造成下面四個問題:
靜態成員和單例模式完全失效;
線程同步機制完全失效;
(原因:不同進程鎖不是同一個對象)
SharedPreferencce的可靠性下降;
(原因:SharedPreferencce不支持兩個進程同時去執行寫操作)
Application會多次創建。
(原因:系統創建新的進程會同時分配獨立的虛擬機,這個過程爲啓動一個應用的過程)
IPC基礎知識:
1、Serializable接口:
首先聲明一個標識:
public class User implements Serializable {
public static final long serialVersionUID = 212345678909876543L;
public int id;
public String name;
public boolean isMan;
public User() {
super();
}
public User(int id, String name, boolean isMan) {
super();
this.id = id;
this.name = name;
this.isMan = isMan;
}
}
然後通過ObjectOutputStream和ObjectInputStream即可完成:
// 序列化過程
User user= new User(0, "Tom", true);
try {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeObject(user);
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
// 反序列化過程
try {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));
User newUser = (User) in.readObject();
in.close();
} catch (StreamCorruptedException e) {
e.printStackTrace();
} catch (OptionalDataException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
serialVersionUID的工作機制:序列化的時候系統會把當前類的serialVersionUID寫入序列化的文件中,當反序列化的時候系統會檢測文件中serialVersionUID,看它是否和當前類的serialVersionUID一致,如果一致就說明序列化的類版本和當前類的版本是相同的,這時就可以成功反序列化。
特別需要注意的兩點:一是靜態成員屬於類不屬於對象,所以不會參與序列化過程;其次用transient關鍵字標記的成員變量不參與序列化過程。
Parcelable接口:
實現這個接口,一個類的對象就可以實現序列化並通過Intent和Binder傳遞。
典型用法:
public class User implements Parcelable {
public int id;
public String name;
public boolean isMan;
public Book book;
public User(int id, String name, boolean isMan) {
super();
this.id = id;
this.name = name;
this.isMan = isMan;
}
// 返回當前對象的內容描述。如果含有文件描述符,返回1,幾乎所有情況返回0
@Override
public int describeContents() {
return 0;
}
// 將當前對象寫入序列化結構中
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(name);
dest.writeInt(isMan ? 1:0);
dest.writeParcelable(book, 0);
}
// 反序列化
public static final Parcelable.Creator<User> CREATOR = new Creator<User>() {
// 創建指定長度的原始對象數組
@Override
public User[] newArray(int size) {
return new User[size];
}
// 從序列化後的對象中創建原始對象
@Override
public User createFromParcel(Parcel source) {
return new User(source);
}
};
// 通過read方法完成反序列化過程
public User(Parcel source) {
id = source.readInt();
name = source.readString();
isMan = source.readInt() == 1;
// book是另一個可序列化對象,所以需要傳遞當前線程的上下文類加載器。
book = source.readParcelable(Thread.currentThread().getContextClassLoader());
}
}
Serializable和Parcelable使用場景:
Serializable:使用簡單但是開銷很大,需要大量的I/O操作。適用於將對象序列化到存儲設備中或者將對象序列化後通過網絡傳輸。
Parcelable:使用複雜但效率很高。適用於內存序列化上,這是Android推薦的序列化方式。
Binder
原理沒看明白,這裏只記錄下怎麼使用:
Binder是Android中一種跨進程通信方式(IPC),是ServiceManager連接各種Manager和相應ManagerService的橋樑(Framework),是客戶端和服務端進行通信的媒介(應用層)。
Binder主要用在Service中,包括AIDL和Messenger。這裏用AIDL的方式示例:
// Book.java
package com.android.ipc.aidl;
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable {
public int id;
public String name;
public Book(int id, String name) {
super();
this.id = id;
this.name = name;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(name);
}
public static final Parcelable.Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book[] newArray(int size) {
return new Book[size];
}
@Override
public Book createFromParcel(Parcel source) {
return new Book(source);
}
};
private Book(Parcel source) {
id = source.readInt();
name = source.readString();
}
}
// Book.aidl
package com.android.ipc.aidl;
parcelable Book;
// IBookManager .aidl
package com.android.ipc.aidl;
import com.android.ipc.aidl.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
系統便會自動生成IBookManager.java。AIDL文件的本質是系統爲我們提供了一種快速實現Binder的工具。
linkToDeath和unlinkToDeath:
Binder運行在服務端進程,如果服務端進程由於某種原因異常終止,導致我們的遠程調用失敗,通過linkToDeath設置一個死亡代理,當Binder死亡時,我們就會收到通知,這是就可以重新發起連接請求從而恢復連接。
private IBookManager mBookManager;
private IBinder.DeathRecipient mDeathRecipient = new DeathRecipient() {
// Binder死亡的時候調用這個方法
@Override
public void binderDied() {
if (mBookManager == null)
return;
mBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mBookManager = null;
// TODO:這裏重新綁定遠程Service
}
};
然後,在客戶端綁定遠程服務成功後,給Binder設置死亡代理:
mService = IBookManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient, 0);
Android中的IPC方式
1、Bundle
在一個進程中啓動另一個進程的Activity、Servie、Receiver,我們就可以在Bundle中附加需要傳輸給遠程進程的消息並通過Intent發送出去。所傳輸的數據必須能夠序列化。
2、使用文件共享
兩個進程通過讀寫同一個文件來交換數據。適用於無併發訪問情形,交換簡單的數據,實時性不高的場景。
3、使用Messenger
在Message中放入需要傳遞的對象,便可實現數據的進程間傳遞。適用於低併發的一對多即時通信。
4、使用AIDL
處理大量的併發請求
5、使用ContentProvider
在數據源訪問方面功能強大。適用於一對多的進程間的數據共享
6、使用Socket
網絡數據交換