原文出處點擊[這里](http://blog.csdn.net/qy274770068/article/details/49228441)
[續集](http://blog.csdn.net/qy274770068/article/details/49228663)
IPC機制(IPC介紹)
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方法。
~~~
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是否死亡。
## 二、IPC機制續(IPC方式)
具體方式有很多,比如可以在Intent中附加Extra來傳遞信息,或者通過共享文件的方式來共享數據,還可以采用Binder方式來跨進程通信,另外,Content Provider天生就是支持跨進程訪問的,因此,我們也可以使用它來進行IPC,另外通過網絡通信也是可以實現數據傳遞的,所以Socket也可以實現IPC。
### 1.使用Bundle
由于Bundle實現了Parcelable接口,所以它可以方便地在不同進程間傳輸。
除了直接傳遞數據這種典型的使用場景,他還有一種特殊的使用場景,如A進程正在進行計算,計算完成之后需要把結果傳遞給B進程,但是這個結果不支持放入Bundle中,那么可以這樣考慮,A中,通過Intent啟動B進程的一個Service組件(如IntentService),讓Service進行后臺計算,計算完畢之后,再啟動B進程中真正想要啟動的組件由于Service也在B進程中,所以目標組件就可以直接獲取結果。
findViewById(R.id. button).setOnClickListener( new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setClass(MainActivity. this, SecondActivity.class);
User user = new User(0, "jake", true);
user. book = new Book();
intent.putExtra( "extra_user", (Serializable) user);
startActivity( intent);
}
});
### 2.使用文件共享
兩個進程通過讀寫同一個文化夾來交換數據,比如A進程把數據寫進文件,B進程通過讀取這個文件來獲取數據。Linux使得并發讀寫文件可以沒有限制,甚至兩個線程同時對一個文件進行讀寫都是運行的。
希望在ManActivity中的onResume中序列化一個User對象到SDk卡上面的一個文件里面,在SecondActivity的onResume中去反序列化。
MainActivity:onResume執行下面的方法
private void persistToFile() {
new Thread( new Runnable() {
@Override
public void run() {
User user = new User(1, "hello world", false);
File dir = new File(MyConstants. CHAPTER_2_PATH);
if (! dir.exists()) {
dir.mkdirs();
}
File cachedFile = new File(MyConstants. CACHE_FILE_PATH );
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = new ObjectOutputStream(
new FileOutputStream(cachedFile));
objectOutputStream.writeObject( user);
Log. d(TAG, "persist user:" + user);
} catch (IOException e) {
e.printStackTrace();
} finally {
MyUtils. close(objectOutputStream);
}
}
}).start();
}
SecondActivity中取:
private void recoverFromFile() {
new Thread(new Runnable() {
@Override
public void run() {
User user = null;
File cachedFile = new File(MyConstants. CACHE_FILE_PATH);
if ( cachedFile.exists()) {
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(
new FileInputStream( cachedFile));
user = (User) objectInputStream.readObject();
Log. d(TAG, "recover user:" + user);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
MyUtils. close(objectInputStream);
}
}
}
}).start();
}
當然這種不支持并發,如果想要并發,需要使用線程同步機制來解決。SharePreferences是個特例,通過鍵值對來存儲數據,底層采用xml來存儲鍵值對,位置在/data/data/packagename/shared_prefs目錄下面,從本質來說SharePreferences也屬于文件的一種,但是由于系統對它的讀寫有一定的緩存策略,即在內存里面有一份SharePreferences文件的緩存,因此在多進程模式下,系統對他的讀寫變得不可靠,當面對高并發的讀寫訪問就有很大幾率丟失數據,因此不建議進程間通信使用SP。
### 3.使用Messenger
Messenger是一種輕量級的IPC方案,它的底層實現是AIDL。從構造方法可以很明顯的看出AIDL的痕跡。
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
Messenger的使用方法很簡單,它對AIDL做了封裝,使得我們可以更簡單地進行線程間通信,同時由于它一次處理一個請求,因此在服務端我們不用考慮線程同步的問題,這個是因為服務端不存在并發執行的情況。
步驟:
1.服務端進程,首先我們要創建一個Service來處理客戶端的請求,同時創建一個Handler并通過它來創建一個Messenger對象,在Service的onBind里面返回這個Messenger對象底層的Binder即可。
2.客戶端進程,首先要綁定服務端的Service,綁定成功之后用服務端返回的IBinder對象創建一個Messenger,通過這個Messenger就可以向服務端發送消息了發送消息類型為Message對象。
如果要服務端能夠回應客戶端,就和服務端一樣,需要創建一個Handler并創建一個新的Messenger,并把這個Messenger對象通過Message的replyTo參數傳遞給服務端,服務端通過這個replyTo參數就可以回應客戶端。
服務端:
~~~
public class MessengerService extends Service {
private static final String TAG = "MessengerService";
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch ( msg. what) {
case MyConstants. MSG_FROM_CLIENT:
Log. i(TAG, "receive msg from Client:" + msg.getData().getString( "msg"));
Messenger client = msg. replyTo;
Message relpyMessage = Message. obtain(null, MyConstants.MSG_FROM_SERVICE );
Bundle bundle = new Bundle();
bundle.putString( "reply", "嗯,你的消息我已經收到,稍后會回復你。" );
relpyMessage.setData( bundle);
try {
client. send(relpyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage( msg);
}
}
}
private final Messenger mMessenger = new Messenger( new MessengerHandler());
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand( intent, flags, startId);
}
}
~~~
客戶端:
~~~
public class MessengerActivity extends Activity {
private static final String TAG = "MessengerActivity";
private Messenger mService;
private Messenger mGetReplyMessenger = new Messenger( new MessengerHandler());
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch ( msg. what) {
case MyConstants. MSG_FROM_SERVICE:
Log. i(TAG, "receive msg from Service:" + msg.getData().getString( "reply"));
break;
default:
super.handleMessage( msg);
}
}
}
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mService = new Messenger( service);
Log. d(TAG, "bind service");
Message msg = Message. obtain(null, MyConstants.MSG_FROM_CLIENT );
Bundle data = new Bundle();
data.putString( "msg", "hello, this is client.");
msg.setData( data);
msg. replyTo = mGetReplyMessenger;
try {
mService.send( msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName className) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate( savedInstanceState);
setContentView(R.layout. activity_messenger);
Intent intent = new Intent( "com.ryg.MessengerService.launch");
bindService( intent, mConnection, Context. BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
unbindService( mConnection);
super.onDestroy();
}
}
~~~
Mainfest里面:
<activity
android:name= ".messenger.MessengerActivity"
android:label= "@string/title_activity_messenger" >
<intent-filter >
<action android:name ="android.intent.action.MAIN" />
</intent-filter >
</activity >
<service
android:name= ".messenger.MessengerService"
android:process= ":remote" >
<intent-filter >
<action android:name ="com.ryg.MessengerService.launch" />
</intent-filter >
</service >
> **注意**:
> 通過Messenger來傳遞Message,Message中能用的載體只有what,arg1,arg2,Bundle以及replyTo。Message中的另外一個字段object在同一個進程中是很實用的,但是在跨進程間通信的時候,在Android2.2以前object字段不支持跨進程傳輸,
> 即使2.2以后,也僅僅是系統提供的實現了Parcelable接口的對象才能通過它來傳輸。這就意味著我們自定義的Parcelable對象是無法通過object字段來傳輸的。
Messenger的工作原理圖:
Messenger的工作原理
### 4.使用AIDL
由于Messenger的主要作用還是傳遞消息,有時候可能需要跨進程調用服務端的方法,那么Messenger就不行了。
使用AIDL進行跨進程通信也分為客戶端和服務端兩個方面:
#### (1)服務端
服務端首先要創建一個Service用來監聽客戶端的連接請求,然后創建一個AIDL文件,將暴露給客戶端的接口咋這個AIDL文件中聲明,最后在Service中實現這個AIDL接口即可。
#### (2)客戶端
首先綁定服務端的Service,綁定成功之后,將服務端返回的Binder對象轉換成AIDL接口所屬的類型,接著就可以調用AIDL中的方法了。
#### (3)AIDL接口的創建
~~~
package com.ryg.chapter_2.aidl;
import com.ryg.chapter_2.aidl.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
~~~
AIDL文件支持的數據類型:
* 基本數據類型(int,long,char,boolean,double等)
* String和CharSquence
* List,只支持ArrayList,里面的每個元素都要必須能被AIDL支持
* Map,只支持HashMap,里面的每個元素都必須被AIDL支持, 包括Key和Value
* Parcelable,所有實現了Parcelable接口的對象
* AIDL,所有的AIDL接口本身也可以在AIDL文件中使用
以上的6種數據類型就是AIDL所支持的所有類型,其中自定義的Parcelable對象和AIDL對象必須要顯式import進來,不管是否和當前的AIDL文件位于同一個包里面。
* * * * *
**另外,如果AIDL文件里面用到了自定義的Parcelable對象,那么必須新建一個和它同名的AIDL文件,并在里面聲明它為Parcelable類型。**
* * * * *
在IBookManager.aidl文件中使用到了自定義的Book對象,所以必須創建Book.aidl在里面添加:
~~~
package com.ryg.chapter_2.aidl;
parcelable Book;
~~~
AIDL中每個實現了Parcelable接口的類型的類,都需要像上面那樣去聲明,創建對應的AIDL文件,并聲明那個類為parcelable。除此之外,AIDL中除了基本數據類型,其他類型的參數必須標上方向,in,out或者inout,in表示輸入型參數,out表示輸出型參數,inout表示輸入輸出型參數。AIDL接口中只支持方法,不支持靜態常量。
in,out,inout區別:
http://hold-on.iteye.com/blog/2026138
inout區別
inout區別
為了方便開發,建議把所有的和AIDL相關的類和文件全部放入同一個包中,這樣的好處是,當客戶端是另外的應用時,我們可以直接把整個包復制放入到客戶端工程中。
AIDL包結構,在客戶端和服務端要一致,否則會運行出錯,這是因為客戶端需要反序列化服務端中和AIDL接口相關的所有類。
#### (4)遠程服務端的Service實現
~~~
public class BookManagerService extends Service {
private static final String TAG = "BMS";
private CopyOnWriteArrayList<Book> mBookList =
new CopyOnWriteArrayList<Book>();
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add( book);
}
};
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1, "Android"));
mBookList.add(new Book(2, "Ios"));
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
<service
android:name= ".aidl.BookManagerService"
android:process= ":remote" >
</service >
~~~
采用CopyOnWriteArrayList,它支持并發讀寫,AIDL方法是在服務端的Binder線程池中執行的,因此當多個客戶端同時連接的時候,會存在多個線程同時訪問的問題,所以要在AIDL方法中處理線程同步,這里使用它來進行自動的線程同步。
服務端可以使用CopyOnWriteArrayList和ConcurrentHashMap來進行自動線程同步,客戶端拿到的依然是ArrayList和HashMap。
#### (5)客戶端的實現
客戶端首先綁定遠程服務,綁定成功之后將服務端返回的Binder對象轉換成為AIDL接口,然后就可以通過這個接口去調用服務端的遠程方法了。
~~~
public class BookManagerActivity extends Activity {
private static final String TAG = "BookManagerActivity";
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
List<Book> list = bookManager.getBookList();
Log. i(TAG, "query book list, list type:"
+ list.getClass().getCanonicalName());
Log. i(TAG, "query book list:" + list.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName className) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate( savedInstanceState);
setContentView(R.layout. activity_book_manager);
Intent intent = new Intent( this, BookManagerService. class);
bindService( intent, mConnection, Context. BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
unbindService( mConnection);
super.onDestroy();
}
}
~~~
#### (6)監聽
比如現在想服務端有新書的時候通知客戶端,那么必須要監聽了需要使用RemoteCallbackList,存儲我們自定義的監聽器,它是一個泛型,支持管理任意的AIDL接口。它的內部是Map結構,key是IBinder類型,value是Callback類型。
* * * * *
**注意:**
服務端和客戶端之間做監聽器,服務端需要使用RemoteCallbackList,否則客戶端的監聽器無法收到通知(因為服務端實質還是一份新的序列化后的監聽器實例,并不是客戶端那份)
* * * * *
RemoteCallbackList的beginBroadcast和finishBroadcast必須配對使用,哪怕我們僅僅需要獲取RemoteCallbackList中的元素個數。
#### (7)不要在客戶端的ui線程里面調用服務端的耗時方法
客戶端調用遠程服務方法時,因為遠程方法運行在服務端的binder線程池中(服務端方法可以執行大量耗時操作,不需要開線程執行異步任務的);
同時客戶端線程會被掛起,所以如果該方法過于耗時,而客戶端又是UI線程,會導致ANR,所以當確認該遠程方法是耗時操作時,應避免客戶端在UI線程中調用該方法。
同理,當服務器調用客戶端的listener方法時,該方法也運行在客戶端的binder線程池中,所以如果該方法也是耗時操作,請確認運行在服務端的非UI線程中。
另外,因為客戶端的回調listener運行在binder線程池中,所以更新UI需要用到handler。
#### (8)服務端進程意外終止
客戶端通過IBinder.DeathRecipient來監聽Binder死亡,也可以在onServiceDisconnected中監聽并重連服務端。區別在于前者是在binder線程池中,訪問UI需要用Handler,后者則是UI線程。
#### (9)權限驗證
可通過自定義權限在onBind或者onTransact中進行權限驗證。
onBind中驗證,驗證不通過返回null,驗證方式可以使用permission驗證,首先在manifest里面注冊。
~~~
<permission
android:name= "com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE"
android:protectionLevel= "normal" />
~~~
就可以在onBind里面驗證了。
@Override
public IBinder onBind(Intent intent) {
int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" );
Log. d(TAG, "onbind check=" + check);
if ( check == PackageManager. PERMISSION_DENIED) {
return null;
}
return mBinder;
}
一個應用來綁定我們的服務的時候,會驗證這個應用的權限,沒有權限就返回null。這個方法同樣適用于Messenger中。
我們自己內部的應用想要綁定我們的服務,只需要在Manifest采用如下方式使用permission即可。
`<uses-permission android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" />`
服務端的onTransact方法中進行權限驗證,驗證失敗就會返回false,服務端的方法就不會執行,驗證方式可以采用permission驗證,也可以使用Uid和Pid來驗證。通過getCallingUid和getCallingPid可以拿到客戶端所屬的應用的Uid和Pid,通過這兩個參數可以做一些驗證工作,比如驗證包名。
下面即驗證了權限又需要包名以com.rgy開始。
public boolean onTransact( int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
int check = checkCallingOrSelfPermission( "com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" );
Log. d(TAG, "check=" + check);
if ( check == PackageManager. PERMISSION_DENIED) {
return false;
}
String packageName = null;
String[] packages = getPackageManager().getPackagesForUid(
getCallingUid());
if ( packages != null && packages. length > 0) {
packageName = packages[0];
}
Log. d(TAG, "onTransact: " + packageName);
if (! packageName.startsWith( "com.ryg")) {
return false;
}
return super.onTransact( code, data, reply, flags);
}
### 5.使用ContentProvider
底層實現是AIDL,使用比AIDL簡單許多。自定義ContentProvider需要繼承ContentProvider并實現里面的方法即可,onCreate,query,update,insert,delete,getType,getType用來返回Uri請求所對應的MimeType。如果應用不關系這個,只需要返回null或者”/“,根據Binder,我們知道這6個方法均運行在ContentProvider的進程里面,除了onCreate方法由系統回調并運行在主線程里面,其余的5個均由外界調用并運行在Binder線程池中。
雖然ContentProvider的底層數據看起來很像一個SQLite數據庫,但是它對底層的數據的存儲方式沒有任何要求,我們即可以使用SQLite,也可以使用普通文件,甚至可以采用內存中的一個對象來進行數據的存儲。
注冊Android:authorities是它的唯一標識,建議命名的時候加上包名前綴,如果聲明了權限,那么外界應用也需要相應的權限。
ContentProvider(有的手機上會出現不加uses-permission依然可以訪問BookProvider的問題)
### 6.使用Socket
Socket 一般用于網絡通信,AIDL用這種方式會過于繁瑣,不建議。
### 7.Binder連接池
比如100個地方需要用到AIDL那么不可能創建100個Service,需要減少Service的數量,將AIDL放在同一個Service里面去管理。
Binder連接池的作用就是將每個業務模塊的Binder請求統一轉發到遠程的Service中去。
Binder連接池
Binder連接池,通過BinderPool的方式將Binder的控制與Service本身解耦,同時只需要維護一份Service即可。這里用到了CountDownLatch,大概解釋下用意:線程在await后等待,直到CountDownLatch的計數為0,BinderPool里使用它的目的是為了保證Activity獲取BinderPool的時候Service已確定bind完成~
例子:
兩個AIDL:
ISecurityCenter.aidl
~~~
package com.ryg.chapter_2.binderpool;
interface ISecurityCenter {
String encrypt(String content);
String decrypt(String password);
}
ICompute.aidl
package com.ryg.chapter_2.binderpool;
interface ICompute {
int add( int a, int b);
}
~~~
實現:
~~~
public class SecurityCenterImpl extends ISecurityCenter.Stub {
private static final char SECRET_CODE = '^';
@Override
public String encrypt(String content) throws RemoteException {
char[] chars = content.toCharArray();
for (int i = 0; i < chars.length; i++) {
chars[ i] ^= SECRET_CODE;
}
return new String(chars);
}
@Override
public String decrypt (String password ) throws RemoteException {
return encrypt( password);
}
}
~~~
以及:
~~~
public class ComputeImpl extends ICompute.Stub {
@Override
public int add(int a, int b) throws RemoteException {
return a + b;
}
}
~~~
為Binder連接池創建AIDL接口IBinderPool.aidl
~~~
interface IBinderPool {
/**
* @param binderCode, the unique token of specific Binder<br/>
* @return specific Binder who's token is binderCode.
*/
IBinder queryBinder( int binderCode);
}
~~~
為Binder連接池創建遠程Service并實現IBinderPool,
下面是queryBinder的實現:
@Override
public IBinder queryBinder( int binderCode) throws RemoteException {
IBinder binder = null;
switch ( binderCode) {
case BINDER_SECURITY_CENTER: {
binder = new SecurityCenterImpl();
break;
}
case BINDER_COMPUTE: {
binder = new ComputeImpl();
break;
}
default:
break;
}
return binder;
}
遠程Service的實現比較簡單了:
~~~
public class BinderPoolService extends Service {
private static final String TAG = "BinderPoolService";
private Binder mBinderPool = new BinderPool.BinderPoolImpl();
@Override
public void onCreate() {
super.onCreate();
}
@Override
public IBinder onBind(Intent intent ) {
Log.d(TAG, "onBind");
return mBinderPool;
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
~~~
下面還有Binder連接池的具體實現,首先綁定遠程服務,成功之后,客戶端就可以通過它的queryBinder方法獲取各自對應的Binder,拿到所需要的Binder之后,不同的業務模塊之間就可以進行各自的操作了。
~~~
public class BinderPool {
private static final String TAG = "BinderPool";
public static final int BINDER_NONE = -1;
public static final int BINDER_COMPUTE = 0;
public static final int BINDER_SECURITY_CENTER = 1;
private Context mContext;
private IBinderPool mBinderPool;
private static volatile BinderPool sInstance;
private CountDownLatch mConnectBinderPoolCountDownLatch;
private BinderPool(Context context) {
mContext = context.getApplicationContext();
connectBinderPoolService();
}
public static BinderPool getInsance(Context context) {
if (sInstance == null) {
synchronized (BinderPool.class) {
if (sInstance == null) {
sInstance = new BinderPool(context);
}
}
}
return sInstance;
}
private synchronized void connectBinderPoolService() {
mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
Intent service = new Intent(mContext, BinderPoolService.class);
mContext.bindService(service, mBinderPoolConnection,
Context.BIND_AUTO_CREATE);
try {
mConnectBinderPoolCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* query binder by binderCode from binder pool
*
* @param binderCode
* the unique token of binder
* @return binder who's token is binderCode<br>
* return null when not found or BinderPoolService died.
*/
public IBinder queryBinder(int binderCode) {
IBinder binder = null;
try {
if (mBinderPool != null) {
binder = mBinderPool.queryBinder(binderCode);
}
} catch (RemoteException e) {
e.printStackTrace();
}
return binder;
}
private ServiceConnection mBinderPoolConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
// ignored.
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinderPool = IBinderPool.Stub.asInterface(service);
try {
mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
mConnectBinderPoolCountDownLatch.countDown();
}
};
private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.w(TAG, "binder died.");
mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);
mBinderPool = null;
connectBinderPoolService();
}
};
public static class BinderPoolImpl extends IBinderPool.Stub {
public BinderPoolImpl() {
super();
}
@Override
public IBinder queryBinder(int binderCode) throws RemoteException {
IBinder binder = null;
switch (binderCode) {
case BINDER_SECURITY_CENTER: {
binder = new SecurityCenterImpl();
break;
}
case BINDER_COMPUTE: {
binder = new ComputeImpl();
break;
}
default:
break;
}
return binder;
}
}
}
~~~
使用:
~~~
public class BinderPoolActivity extends Activity {
private static final String TAG = "BinderPoolActivity";
private ISecurityCenter mSecurityCenter;
private ICompute mCompute;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate( savedInstanceState);
setContentView(R.layout. activity_binder_pool);
new Thread(new Runnable() {
@Override
public void run() {
doWork();
}
}).start();
}
private void doWork() {
BinderPool binderPool = BinderPool.getInsance(BinderPoolActivity. this);
IBinder securityBinder = binderPool
.queryBinder(BinderPool. BINDER_SECURITY_CENTER);
mSecurityCenter = (ISecurityCenter) SecurityCenterImpl
. asInterface(securityBinder);
Log.d(TAG, "visit ISecurityCenter");
String msg = "helloworld-安卓";
System. out.println( "content:" + msg);
try {
String password = mSecurityCenter.encrypt( msg);
System. out.println( "encrypt:" + password);
System. out.println( "decrypt:" + mSecurityCenter.decrypt(password ));
} catch (RemoteException e) {
e.printStackTrace();
}
Log.d(TAG, "visit ICompute");
IBinder computeBinder = binderPool
.queryBinder(BinderPool. BINDER_COMPUTE);
mCompute = ComputeImpl.asInterface(computeBinder );
try {
System. out.println( "3+5=" + mCompute.add(3, 5));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
~~~
### 8.選擇適合的IPC方式
選擇方式

- 前言
- 第一章Activity的生命周期和啟動模式
- 1.1 Activity生命周期全面分析
- 1.2 Activity的啟動模式
- 1.3 IntentFilter的匹配規則
- 第二章IPC
- 轉 chapter IPC
- 轉IPC1
- 轉IPC2
- Binder講解
- binder
- Messenger
- 一、Android IPC簡介
- 二、Android中的多進程模式
- 三、IPC基礎概念介紹
- 四、Android中的IPC方式
- 五、Binder連接池
- 第三章
- 第九章四大組件的工作過程
- 第十章
- 第13章 綜合技術
- 使用CrashHandler 來獲取應用的crash 信息
- 使用Multidex來解決方法數越界
- Android的動態加載技術