春風十里不如你、與IPC的邂逅
今天要梳理的知識是Android中的IPC機制,由於這一點難度太高又相對重要,所以筆者也是主要參考了一些書才完成了這篇文章。
- IPC是Inter-Process Communication的縮寫,含義是程序間通訊或者是程序間通訊,是指兩個程序之間進行資料交換的過程。
先說一下程序與執行緒的定義吧
程序(Process)是計算機中的程式關於某資料集合上的一次執行活動,是系統進行資源分配和排程的基本單位,是作業系統結構的基礎。執行緒,有時被稱為輕量級程序(Lightweight Process,LWP),是程式執行流的最小單元。
所以多程序有什麼意義呢?
好處:
- 分擔主程序的記憶體壓力。
當應用越做越大,記憶體越來越多,將一些獨立的元件放到不同的程序,它就不佔用主程序的記憶體空間了。當然還有其他好處,有心人會發現 - 使應用常駐後臺,防止主程序被殺守護程序,守護程序和主程序之間相互監視,有一方被殺就重新啟動它。
Android後臺程序裡有很多應用是多個程序的,因為它們要常駐後臺,特別是即時通訊或者社交應用,不過現在多程序已經被用爛了。
典型用法是在啟動一個不可見的輕量級私有程序,在後臺收發訊息,或者做一些耗時的事情,或者開機啟動這個程序,然後做監聽等。
壞處:
- 消耗使用者的電量。
- 多佔用了系統的空間,若所有應用都這樣佔用,系統記憶體很容易佔滿而導致卡頓。
- 應用程式架構會變得複雜,因為要處理多程序之間的通訊。這裡又是另外一個問題了。
其它缺陷:
- Application的多次重建。
- 靜態成員和單例模式的完全失效。
- 執行緒同步機制失效。
- SharedPreference可靠性下降。
既然它有很多缺點,所以我們就放棄吧
(怎麼可能?你在逗我?當然要學習啊!)
下面通過一些小程式碼來看一下吧
在Android中使用多程序只有一種方法:
就是給四大元件(Activity、Service、Receiver、ContentProvider)在AndroidManifest中指定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" />
</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" />
上面的程式碼中,
(1)MainActivity沒有指定process屬性,所以它執行在預設的程序中,預設程序的程序名是包名。
(2)SecondActivity會執行在一個單獨的程序中,程序名為“com.ryg.chapter_2:remote”,其中com.ryg.chapter_2是包名。在程式中的冒號“:”的含義是指要在當前的程序名前面附加上當前的包名,是一種簡寫的方法。而且以“:”開頭的程序屬於當前應用的私有程序,其他應用的元件不可以和它跑在同一個程序中。
(3)ThirdActivity會執行在另一個單獨的程序中,程序名為“com.ryg.chapter_2.remote”。這是一種完整的命名方式。屬於全域性程序,其他應用通過ShareUID方式可以和它跑在同一個程序中。
程序也建立好了,下面就是通訊的內容了
在Android中最有特色的程序間通訊方式就是Binder了,通過Binder可以輕鬆地實現程序間通訊。
當我們需要通過Intent和Binder傳輸資料時就需要使用Parcelable或者Serializeble。Serializable和Parcelable介面可以完成物件的序列化過程。還有時候我們需要把物件持久化到儲存 裝置上或者通過網路傳輸給其他客戶端,這個時候也需要Serializable來完成物件的持久化。
http://blog.csdn.net/callmesp/article/details/54632139 在我的這篇部落格裡面有使用過Serializable,我們就不再講解了。我們就說一下Parcelable吧。
- Parcelable也是一個介面,只要實現這個介面,一個類的物件就可以實現序列化並可以通過Intent和Binder傳遞。
看一個小demo
public class User implements Parcelable {
public int userId;
public String userName;
public boolean isMale;
public Book book;
public User(int userId, String userName, boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
/*
* 內容描述功能幾乎都是直接返回0的。
* */
public int describeContents() {
return 0;
}
/*
* 序列化由writeToParcel方法來完成,最終是通過Parcel中一系列write方法來完成的。
* 其中flags標識有兩種值:0和1(PARCELABLE_WRITE_RETURN_VALUE)。
* 為1時標識當前物件需要作為返回值返回,不能立即釋放資源,
* 幾乎所有情況都為0。
* */
public void writeToParcel(Parcel out, int flags) {
out.writeInt(userId);
out.writeString(userName);
out.writeInt(isMale? 1:0);
out.writeParcelable(book, 0);
}
/*
* 反序列化功能是由CREATOR來完成,其內部標明瞭如何建立序列化物件和陣列,
* 並通過Parcel的一些了read方法來完成反序列化過程。
* */
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];
}
};
/*
* Parcel內部包裝了可序列化的資料,可以在Binder中自由傳輸。
* 從序列化後的物件中建立原始物件。
* */
private User(Parcel in) {
userId = in.readInt();
userName = in.readString();
isMale = in.readInt() == 1;
/*
* 由於book是另一個可序列化物件,所以它的反序列化過程需要傳遞當前執行緒的上下文類載入器,
* 否則會報無法找到類的錯誤。
* */
book = in.readParcelable(Thread.currentThread().getContextClassLoader());
}
}
既然Parcelable和Serializable都能實現系列化並且都可用於Intent間的資料傳遞,該如何選取呢?
- Serializable用起來簡單,但開銷很大,序列化和反序列化過程都需要大量的I/O操作。
- Parcelable是Android中的序列化方式,更適合在Android平臺上使用,用起來比較麻煩,效率很高,首選。主要用在記憶體序列化上。
接下來就到了我們的重頭戲Binder:
- Binder實現了IBinder介面
從IPC角度來說,Binder是Android中的一種跨程序通訊方式。Binder還可以理解為一種虛擬的物理裝置,它的裝置驅動是/dev/binder,這種通訊方式在Linux中沒有。
從Android
Framework角度來說,Binder是ServiceManager連線各種Manager(ActivityManager、WindowManager,等等)和相應ManagerService的橋樑。從Android應用層來說,Binder是客戶端和服務端進行通訊的媒介,當bindService的時候,服務端會返回一個包含了服務端業務呼叫的Binder物件,通過這個物件,客戶端就可以獲取服務端提供的服務或者資料,這裡的服務包括普通服務和基於AIDL的服務。
AIDL即Android interface definition Language,即Android介面定義語言。
在分析Binder的工作原理之前,我們先補充一下Android設計模式之Proxy模式
Proxy代理模式簡介
- 代理模式是物件的結構模式。代理模式給某一個物件提供一個代理物件,並由代理物件控制對原物件的引用。
- 模式的使用場景:就是一個人或者機構代表另一個人或者機構採取行動。在一些情況下,一個客戶不想或者不能夠直接引用一個物件,而代理物件可以在客戶端和目標物件之間起到中介的作用。
抽象物件角色AbstarctObject:聲明瞭目標物件和代理物件的共同介面,這樣一來在任何可以使用目標物件的地方都可以使用代理物件。
目標物件角色RealObject:定義了代理物件所代表的目標物件。
代理物件角色ProxyObject:代理物件內部含有目標物件的引用,從而可以在任何時候操作目標物件;代理物件提供一個與目標物件相同的介面,以便可以在任何時候替代目標物件。代理物件通常在客戶端呼叫傳遞給目標物件之前或之後,執行某個操作,而不是單純地將呼叫傳遞給目標物件。
Proxy代理模式的簡單實現
抽象物件角色:
public abstract class AbstractObject {
//操作
public abstract void operation();
}
目標物件角色:
public class RealObject extends AbstractObject {
@Override
public void operation() {
//一些操作
System.out.println("一些操作");
}
}
代理物件角色:
public class ProxyObject extends AbstractObject{
RealObject realObject = new RealObject();//目標物件角色
@Override
public void operation() {
//呼叫目標物件之前可以做相關操作
System.out.println("before");
realObject.operation(); //目標物件角色的操作函式
//呼叫目標物件之後可以做相關操作
System.out.println("after");
}
}
客戶端:
public class Client {
public static void main(String[] args) {
AbstractObject obj = new ProxyObject();
obj.operation();
}
}
我們通過一個案例來分析Binder工作原理
我們需要新建一個AIDL示例,SDK會自動為我們生產AIDL所對應的Binder類。
(1)Book.java:這裡面沒有什麼特殊之處,為了實現Parcelable,添加了幾個方法,上面在Parcelable部分已經介紹過了。
package com.ryg.chapter_2.aidl;
import android.os.Parcel;
import android.os.Parcelable;
/*
* (1)它是一個表示圖示資訊的類,
* 它實現了Parcelable介面,因為實現了Parcelable介面便可以進行序列化
* (2)Book.aidl是Book類在ADIL中的宣告。
* (3)IBookManager.aidl是我們定義的一個介面,裡面有兩個方法:getBookList和addBook,
* 其中getBookList用於從遠端服務端獲取圖書列表,而addBook用於往圖書列表中新增一本書,
* 當然這兩個方法主要是示例用,不一定要有實際意義。
* (4)儘管Book類和IBookManager位於相同的包中,但是在IBookManager中仍然要匯入Book類,
* 這就是AIDL的特殊之處。
* */
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);
}
/*
* 反序列化,
* 這個creator就是通過一個Parcle來建立一個book物件或者陣列。
* */
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);
}
}
(2)Book.aidl:它是Book在AIDL中的宣告。
package com.ryg.chapter_2.aidl;
parcelable Book;
(3)IBookManager.aidl:雖然Book類已經和IBookManager位於相同的包中,但是這裡依然需要匯入Book類。這是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);
void registerListener(IOnNewBookArrivedListener listener);
void unregisterListener(IOnNewBookArrivedListener listener);
}
(4)下面我們要看一下系統為IBookManager.aidl生產的Binder類,在gen目錄下有一個IBookManager.java的類,這就是我們要找的類。
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package com.ryg.chapter_2.aidl;
/*
* IBookManager它繼承了IInterface這個介面,同時它自己也還是個介面,
* 所有可以在Binder中傳輸的介面都要繼承IInterface介面。
* 首先,它聲明瞭兩個方法getBookList和addBook,顯然這就是我們在IBookManager.aidl中所宣告的方法,
* 同時它還聲明瞭兩個整型的id分別用於標識這兩個方法。
* 接著,它聲明瞭一個內部類Stub,這個Stub就是一個Binder類,
* 當客戶端和服務端都位於同一個程序時,方法呼叫不會走跨程序的transact過程,
* 而當兩者位於不同程序時,方法呼叫需要走transact過程,
* 這個邏輯由Stub的內部代理類Proxy來完成。
* */
public interface IBookManager extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
/*
* 首先這個Stub,它是一個內部類,它繼承了Binder,所以它是一個Binder,
* 同時Stub還實現了IBookManager中的方法。
* */
public static abstract class Stub extends android.os.Binder implements com.ryg.chapter_2.aidl.IBookManager
{
/*
* Binder的唯一識別符號。
* */
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.
*/
/*
* 用於將服務端的Binder物件轉換成客戶端所需的AIDL介面型別的物件,
* 這種轉換過程是區分程序的,
* 如果客戶端和服務端位於同一程序,那麼此方法返回的就是服務端的Stub物件本身,
* 否則返回的是系統封裝後的Stub.proxy代理物件。
* */
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);
}
/*
* 此方法用於返回當前Binder物件,也就是內部類Stub。
* */
@Override public android.os.IBinder asBinder()
{
return this;
}
/*
* 這個方法執行在服務端中的Binder執行緒池中,
* 當客戶端發起跨程序請求時,遠端請求會通過系統底層封裝後交由此方法來處理。
* 服務端通過code可以確定客戶端所請求的目標方法是什麼,
* 接著從data中取出目標方法所需的引數,
* 然後執行目標方法。
* 當目標方法執行完畢後,就向reply中寫入返回值。
* 如果此方法返回false,那麼客戶端的請求會失敗,因此我們可以利用這個特性來做許可權驗證。
* */
@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;
}
case TRANSACTION_registerListener:
{
data.enforceInterface(DESCRIPTOR);
com.ryg.chapter_2.aidl.IOnNewBookArrivedListener _arg0;
_arg0 = com.ryg.chapter_2.aidl.IOnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());
this.registerListener(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_unregisterListener:
{
data.enforceInterface(DESCRIPTOR);
com.ryg.chapter_2.aidl.IOnNewBookArrivedListener _arg0;
_arg0 = com.ryg.chapter_2.aidl.IOnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());
this.unregisterListener(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
/*
* 代理類Proxy。
* */
private static class Proxy implements com.ryg.chapter_2.aidl.IBookManager
{
/*
* 這個mRemote代表的就是目標物件角色,
* */
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;
}
/*
* 這個方法執行在客戶端,
* 因為當客戶端和服務端不在同一程序時,服務端返回代理類Proxy,所以客戶端會通過Proxy呼叫到代理類的getBookList方法,
* 當客戶端遠端呼叫此方法時,它的內部實現是這樣的:
* 首先建立該方法所需要的輸入型Parcel物件_data、輸出型Parcel物件_reply和返回值物件List,
* 然後把該方法的引數資訊寫入_data中,
* 接著呼叫transact方法來發起RPC(遠端過程呼叫)請求,同時當前執行緒掛起,
* 然後服務端的onTransact方法會被呼叫,直到RPC過程返回後,當前執行緒繼續執行,
* 並從_reply中取出RPC過程的返回結果。
* 最後返回_reply中的資料。
* */
@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();
}
}
@Override public void registerListener(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStrongBinder((((listener!=null))?(listener.asBinder()):(null)));
mRemote.transact(Stub.TRANSACTION_registerListener, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public void unregisterListener(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStrongBinder((((listener!=null))?(listener.asBinder()):(null)));
mRemote.transact(Stub.TRANSACTION_unregisterListener, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
/*
* 用於標識方法的整型id。
* 它們用於在transact過程總客戶端所請求的到底是哪個方法。
* */
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_registerListener = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
static final int TRANSACTION_unregisterListener = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
}
/*
* 聲明瞭在IBookManager.aidl中所宣告的方法。
* 這裡才是真正的方法宣告。具體實現我們仍然沒有看到呢。
* */
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;
public void registerListener(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException;
public void unregisterListener(com.ryg.chapter_2.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException;
}
**注意點一:上面的Book類,就是一個可以Parcelable序列化的簡單的Book類,它裡面沒有任何的方法,就是定義了一個簡單的Book類結構。
注意點二:Book.aidl的存在是因為在IBookManager.aidl中出現的物件也必須有aidl宣告。
注意點三:在IBookManager.aidl中,對於自動生成的IBookManager.java檔案,它是伺服器端的程式碼。當客戶端向服務端傳送連線請求時,如果客戶端和服務端在同一程序中,那麼服務端就向客戶端返回Stub這個Binder物件,如果客戶端和服務端在不同程序中,那麼服務端就向客戶端返回內部類Stub的內部代理類Proxy,然後客戶端根據這個Proxy來呼叫Proxy內部的方法,這個Proxy內部含有服務端真正的Binder物件也就是那個內部類Stub,在客戶端呼叫Proxy內部的方法也就會導致呼叫Stub的transact方法,而Stub的transact方法又會回撥它自己的onTransact方法,onTransact方法是在服務端執行的,而transact方法是在客戶端呼叫的,這樣就實現了客戶端呼叫服務端的方法了。當然這所有的傳遞過程也少不了Parcel這個資料包的協助。**