1. 程式人生 > >春風十里不如你、與IPC的邂逅

春風十里不如你、與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這個資料包的協助。**