IPC機制
1.介紹
IPC是Inter-Progress Communication的縮寫,含義爲進程間通信或者跨進程通信。是指兩個進程之間進行數據交換的過程。
Android裏面最有特色的進程間的通信方式就是Binder,通過它可以輕鬆的實現進程間的通信,Android也支持Socket,通過Socket也可以實現任意兩個終端之間的通信,當然一個設備上的兩個進程通過Socket通信自然也是可以的。
2.Android中的多進程模式
正常情況下Android中多進程指一個應用中存在多個進程的情況。在Android中使用多進程只有一種方式,那就是給四大組件在Manifest中指定android:progress屬性。
就是說不能爲一個線程,一個實體類指定其運行時候所在的進程。(其實還有一種方式,就是使用JNI在Native層去fork一個新的進程,這中方法屬於特殊情況,也不是常用的創建多進程的方式)。
<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" />
</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.ryg.chapter_2.remote" />
上面的例子中假如當前的應用的包名爲com.ryg.chapter_2,那麼當SecondActivity啓動的時候系統會爲它創建一個單獨的進程,進程名爲com.ryg.chapter_2:remote,當ThirdActivity啓動的時候系統也會爲它創建一個單獨的進程com.ryg.chapter_2.remote,由於MainActivity沒有指定Progress屬性,運行在默認的進程中,默認的進程是包名。
使用 : 和直接命名的區別:
1. : 的含義是在當前進程名的前面加上當前的包名,這是一種簡單的寫法,而另外一種則是完整的命名方式,不會附加包名信息。
2.使用 : 的進程表示它屬於當前應用的私有進程,其他應用的組件不可以和它跑在同一個進程中,不使用 : 的進程屬於全局進程,其他應用通過ShareUID方式可以和它跑在同一個進程中。
注意:
如果兩個進程有相同的ShareUID並且簽名相同,它們可以互相訪問對方的私有數據,比如data目錄,組件信息等。
如果跑在同一個進程中(前提是有相同的ShareUID並且前面相同),除了能共享data目錄,組件信息,還可以共享內存數據或者說它們看起來就像一個應用的兩個部分。
Android爲每一個應用分配了一個獨立的虛擬機,或者說爲每一個進程都分配了一個獨立的虛擬機,不同的虛擬機在內存分配上有不同的地址空間,這就導致不同的虛擬機中訪問同一個類的對象會產生多份副本。
public class UserManager {
public static int sUserId = 1;
}
在MainActivity的onCreate中把這個值賦值爲2,啓動SecondActivity,發現值任然爲1,可以看到多進程絕非僅僅指定一個android:progress屬性那麼簡單。
比如,在進程com.ryg.chapter_2和進程com.ryg.chapter_2:remote中都存在一個UserManager類,並且這兩個類是互不干擾的,在同一個進程中修改sUserId的值,只會影響當前的進程。對其他進程不會造成任何影響。
所有運行在不同進程中的四大組件,只要他們之間需要通過內存來共享數據,都會共享失敗,這也是多進程帶
來的主要影響。正常情況下,四大組件中間不可能不通過一些中間層來共享數據,那麼簡單的指定進程名來開啓多進程都會無法正確的運行。
當然,特殊情況下,某些組件之間不需要共享數據,這個時候可以指定android:progress來開啓多進程,但是這種場景是不常見的,幾乎所有的情況都需要共享數據。
一般來說,使用多進程會造成如下幾方面的問題:
(1)靜態成員和單例模式完全失效
(2)線程同步機制完全失效
(3)SharePreferences的可靠性下降
(4)Application多次創建
3.IPC基礎概念介紹
(1)Serializable接口
Serializable是java提供的一個序列化接口,是一個空接口,爲對象提供標準的序列化和反序列化操作。
通過Serializable方式實現對象的序列化,實現起來非常簡單,幾乎所有的工作都被系統自動完成了。
反序列化也非常簡單,需要採用ObjectOutputStream和ObjectInputStream即可。
// 序列化
User user = new User(0,"jake",true);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.dat"));
out.write(user);
out.close();
// 反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.dat"));
User newUser = (User) in.readObject();
in.close();
serialVersionUID用來輔助序列化和反序列化的,在反序列化的時候會去檢測文件中serialVersionUID和當前對象中的serialVersionUID是否相同,相同纔可以反序列化。
所以我們應該手動的指定serialVersionUID,這樣即使刪除了一些變量或者增加了一些變量任然可以成功的反序列化。
(2)Parcelable接口
Book類:
package com.ryg.chapter_2.aidl;
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book() {
}
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel out, int flags) {
out.writeInt(bookId);
out.writeString(bookName);
}
public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
public Book createFromParcel(Parcel in) {
return new Book(in);
}
public Book[] newArray(int size) {
return new Book[size];
}
};
private Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
@Override
public String toString() {
return String.format("[bookId:%s, bookName:%s]", bookId, bookName);
}
}
User類:
package com.ryg.chapter_2.model;
import java.io.Serializable;
import com.ryg.chapter_2.aidl.Book;
import android.os.Parcel;
import android.os.Parcelable;
public class User implements Parcelable, Serializable {
private static final long serialVersionUID = 519067123721295773L;
public int userId;
public String userName;
public boolean isMale;
public Book book;
public User() {
}
public User(int userId, String userName, boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel out, int flags) {
out.writeInt(userId);
out.writeString(userName);
out.writeInt(isMale ? 1 : 0);
out.writeParcelable(book, 0);
}
public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
public User createFromParcel(Parcel in) {
return new User(in);
}
public User[] newArray(int size) {
return new User[size];
}
};
private User(Parcel in) {
userId = in.readInt();
userName = in.readString();
isMale = in.readInt() == 1;
book = in
.readParcelable(Thread.currentThread().getContextClassLoader());
}
@Override
public String toString() {
return String.format(
"User:{userId:%s, userName:%s, isMale:%s}, with child:{%s}",
userId, userName, isMale, book);
}
}
Parcel內部包裝了可序列化的數據,可以在Binder中自由傳輸。序列化功能由writeToParcel方法來完成,最終是通過Parcel中的一系列write方法
來完成的。反序列化功能由CREATOR來完成。其內部標明瞭如何創建序列化對象和數組,並通過Parcel的一系列的read方法來完成反序列化過程。
內容描述功能由describeContents方法來完成,幾乎在所有情況下這個方法都應該返回0,僅僅噹噹前的對象中存在文件描述符時,此方法返回1。
Parcelable的方法說明:
createFromParcel(Parcel in) 從序列化的對象中創建原始對象
newArray(int size)創建指定長度的原始對象數組
User(Parcel in)從序列化後的對象中創建原始對象
writeToParcel(Parcel out, int flags)將當前對象寫入序列化結構中
describeContents返回當前對象的內容描述
區別:
Serializable是java中的序列化接口,使用起來簡單但是開銷很大,序列化和反序列化過程中需要大量的I/O操作。
Parcelable是Android中的序列化方式,因此更適合用在Android平臺上,缺點就是使用起來稍微麻煩點。但是它的效率很高,這是Android推薦的序列換方式。
因此我們要首選Parcelable。
Parcelable主要用在內存序列化上,通過Parcelable將對象序列化在存儲設備中或者將對象序列化以後通過網絡傳輸也都是可以的,但是這個過程會稍顯複雜。
因此在這兩種情況下建議使用Serializable。
(3)Binder
直觀來說Binder是Android中的一個類,它繼承Ibinder接口。從IPC角度來說,Binder是Android中的一種跨進程通信的方式,Binder還可以理解爲一種虛擬的物理設備,它的設備驅動是/dev/binder,該通信方式在Linux中沒有。
從Android Framework的角度來說,Binder是ServiceManager連接各種Manager(ActivityManager、WindowManager,等等)和相應ManagerService的橋樑。
從Android應用層來說,Binder是客戶端和服務端進行通信的媒介,當bindService的時候,服務端會返回一個包含了服務端業務調用的Binder對象,通過這個Binder對象,客戶端就可以獲取服務端提供的服務或者數據,這裏的服務包括普通服務和基於AIDL的服務。
Android開發中,Binder主要用在Service中,包括AIDL和Messenger,其中普通Service中的Binder不涉及進程間通信。Messenger的核心其實是AIDL。
IBookManager.aidl類:
package com.ryg.chapter_2.aidl;
import com.ryg.chapter_2.aidl.Book;
import com.ryg.chapter_2.aidl.IOnNewBookArrivedListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
在功gen裏面自動生成的java類:
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: E:\\Project\\android-art-res-master\\Chapter_2\\src\\com\\ryg\\chapter_2\\aidl\\IBookManager.aidl
*/
package com.ryg.chapter_2.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.ryg.chapter_2.aidl.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.ryg.chapter_2.aidl.IBookManager";
/** Construct the stub at attach it to the interface. */
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.ryg.chapter_2.aidl.IBookManager
* interface, generating a proxy if needed.
*/
public static com.ryg.chapter_2.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.ryg.chapter_2.aidl.IBookManager))) {
return ((com.ryg.chapter_2.aidl.IBookManager) iin);
}
return new com.ryg.chapter_2.aidl.IBookManager.Stub.Proxy(obj);
}
@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.ryg.chapter_2.aidl.Book> _result = this
.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.ryg.chapter_2.aidl.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.ryg.chapter_2.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.ryg.chapter_2.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.ryg.chapter_2.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.ryg.chapter_2.aidl.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data,
_reply, 0);
_reply.readException();
_result = _reply
.createTypedArrayList(com.ryg.chapter_2.aidl.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.ryg.chapter_2.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.ryg.chapter_2.aidl.Book> getBookList()
throws android.os.RemoteException;
public void addBook(com.ryg.chapter_2.aidl.Book book)
throws android.os.RemoteException;
}
每個方法的含義:
DESCRIPTOR
Binder的唯一標識,一般用當前Binder的類名錶示。
asInterface(android.os.IBinder obj)
用於將服務端的Binder對象轉換成客戶端所需要的AIDL接口類型的對象,這種轉換過程是區分進程的,如果客戶端和服務端位於同一進程,那麼此方法返回的就是服務端的Stub對象本身,否則返回的是系統封裝後的Stub.proxy對象。
asBinder
此方法用於返回當前Binder對象
onTransact
這個方法運行在服務端中的Binder線程池中,當客戶端發起跨進程請求的時候,遠程請求會通過系統底層封裝後交給此方法處理。該方法的原型是protected boolean onTransact( int code, Parcel data, Parcel reply,int flags)。
服務端通過code可以確定客戶端所請求的目標方法是什麼,接着從data中取出目標方法所需要的參數(如果目標方法有參數的話)然後執行目標方法。
當目標方法執行完畢後,就向reply中寫入返回值(如果目標方法有返回值的話),onTransact的執行過程就是這樣。
如果此方法返回false,那麼客戶端的請求會失敗,因此我們可以利用這個特性來做權限驗證,畢竟不希望隨便一個進程都能遠程調用我們的服務。
Proxy#getBookList
這個方法運行在客戶端,當客戶端遠程調用此方法時,它的內部實現是這樣的:
首先創建該方法所需要的輸入型Parcel對象_data,輸出型Parcel象_reply和返回值對象List;
然後把該方法的參數信息寫入_data中(如果有參數的話);
接着調用transact方法來發起RPC(遠程過程調用)請求,同時當前線程掛起;
然後服務端的onTransact方法會被調用,直到RPC過程返回後,當前線程繼續執行,並從_reply中取出RPC過程的返回結果;
最後返回_reply中的數據。
Proxy#addBook
這個方法執行在客戶端,執行過程和getBookList是一樣的,addBook沒有返回值,所以不需要從_reply中取出返回值。
注意:
1、當客戶端發起遠程請求時,由於當前線程會被掛起直到服務端進程返回數據,所以如果一個遠程方法是很耗時的,那麼不能在UI線程中發起遠程請求;
2、由於服務端的Binder運行在Binder的線程池中,所以Binder方法不管是否耗時都應該採用同步的方式去實現,因爲它已經運行在一個線程中了。
從上面的分析過程來看,完全可以不提供AIDL文件即可實現Binder,之所以提供AIDL文件,是爲了方便系統爲我們生成代碼。
自己實現:
(1)聲明一個AIDL性質的接口,只需要繼承IInterface接口即可,IInterface接口中只有一個asBinder方法。
getBookList() throws RemoteException;
public void addBook(Book book) throws RemoteException;
}" data-snippet-id="ext.8959e280c3537b44b989b164a2449b90" data-snippet-saved="false" data-csrftoken="DomgGXra-YyvWOSduWrjaHO6xprIlq1KKIU8" data-codota-status="done">package com.ryg.chapter_2.manualbinder;
import java.util.List;
import android.os.IBinder;
import android.os.IInterface;
import android.os.RemoteException;
public interface IBookManager extends IInterface {
static final String DESCRIPTOR = "com.ryg.chapter_2.manualbinder.IBookManager";
static final int TRANSACTION_getBookList = (IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (IBinder.FIRST_CALL_TRANSACTION + 1);
public List<Book> getBookList() throws RemoteException;
public void addBook(Book book) throws RemoteException;
}
可以看到,在接口中聲明瞭一個Binder描述符和另外兩個id,這兩個id分別表示getBookList(),和addBook()方法。
(2)實現Stub類和Stub類中的Proxy代理類
package com.ryg.chapter_2.manualbinder;
import java.util.List;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
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 an IBookManager interface, generating a proxy
* if needed.
*/
public static IBookManager asInterface(IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.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, Parcel data, Parcel reply, int flags)
throws RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface(DESCRIPTOR);
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 待實現
return null;
}
@Override
public void addBook(Book book) throws RemoteException {
// TODO 待實現
}
private static class Proxy implements IBookManager {
private IBinder mRemote;
Proxy(IBinder remote) {
mRemote = remote;
}
@Override
public IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public List<Book> getBookList() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
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 RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = 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();
}
}
}
}
手動去寫的意義在於更加理解Binder的工作原理,同時提供了一種不通過AIDL文件來實現Binder的新方式。如果手寫的Binder,那麼在服務端只需要創建一個BookManagerImpl的對象,並在Service中返回即可。
(4)Binder中兩個很重要的方法
linkToDeath,unlinkToDeath
Binder運行在服務端進程中,如果服務端的進程由於某種原因異常終止,這個時候我們到服務端的Binder連接斷裂(稱之爲Binder死亡),會導致我們的遠程調用失敗,更爲關鍵的是,由於客戶端不知道Binder連接已經斷裂,那麼客戶端的功能就會受到影響。
爲了解決這個問題,Binder中提供了兩個配對的方法linkToDeath,unlinkToDeath,通過linkToDeath我們可以給Binder設置一個死亡代理,當Binder死亡時,我們就會收到通知,這個時候我們就可以重新發起連接請求從而恢復連接。
方法:
首先聲明一個DeathRecipient對象,DeathRecipient是一個接口,內部只有一個方法,binderDied,我們需要實現這個方法,當Binder死亡的時候,系統就會回調binderDied方法,然後我們就可以移除之前綁定的binder代理並重新綁定遠程服務。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log. d(TAG, "binder died. tname:" + Thread.currentThread().getName());
if ( mRemoteBookManager == null)
return;
mRemoteBookManager.asBinder(). unlinkToDeath(mDeathRecipient, 0);
mRemoteBookManager = null;
// TODO:這裏重新綁定遠程Service
}
};
其次在客戶端綁定遠程服務成功之後,給Binder設置死亡代理:
mRemoteBookManager .asBinder().linkToDeath(mDeathRecipient, 0);
其中linkToDeath的第二個參數,是個標記位,我們直接設置爲0即可。
通過上面的步驟就給Binder設置了死亡代理,當Binder死亡的時候就可以收到通知了,另外通過Binder的方法isBinderAlive也可以判斷Binder是否死亡。