天天看點

IPC(上)-多程序入門1 IPC介紹2 Android中多程序模式

1 IPC介紹

既然是IPC的開篇那麼先介紹下IPC的定義

IPC:程序間通信或者跨程序通信,即程序間交換資料的過程.

說到程序,那麼需要了解下什麼是程序.什麼是線程,按作業系統描述,線程是CPU排程的最小單元,同時線程是一種有限的系統資源,而程序指一個執行單元,在PC和移動裝置上指一個程式或者應用,一個程序可以包含多個線程,是以程序和線程是包含和被包含的關系,

在Android中程序間通信方式就是Binder了,除了Binder還有Socket不僅可以實作程序通信,也可以實作任意終端之間的通信,

IPC在多程序使用情況分為兩種:

  • 應用因為某些原因自身需要采用多程序模式來運作,比如某些子產品由于特殊原因需要運作在單獨的程序中,又或者為了加大一個應用可使用的記憶體是以需要通過多程序來擷取更多記憶體空間.
  • 應用需要通路其他應用的資料.甚至我們通過系統提的ContentProvider去查詢資料的時候,也是一種程序間通信.

總之采用了多程序的設計方法,那麼就必須解決程序間通信的問題了.

2 Android中多程序模式

在Android中通過給四大元件指定android:process屬性,我們可以輕易的開啟多程序模式,但是同時也會帶來一個麻煩,下面将會一一介紹

2.1 開啟多程序模式

Android中多程序一般指一個應用中存在多個程序的情況,是以這裡不讨論兩個應用之間的多程序情況,

一般情況下Android中使用多程序隻有一個方法,就是給四大元件在AndroidMenifest中指定android:process屬性,還有一個非常特殊的方法,就是通過JNI在native層去fork一個新的程序,由于不常用,是以這裡隻介紹一般情況下的建立方式

下面是一個例子描述如何建立多線程

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.zly.www.ipc">

      <application
          android:allowBackup="true"
          android:icon="@mipmap/ic_launcher"
          android:label="@string/app_name"
          android:supportsRtl="true"
          android:theme="@style/AppTheme">
          <activity android:name="com.zly.www.ipc.MainActivity">
              <intent-filter>
                  <action android:name="android.intent.action.MAIN" />

                  <category android:name="android.intent.category.LAUNCHER" />
              </intent-filter>
          </activity>

          <activity android:name="com.zly.www.ipc.TwoActivity"
              android:process=":zly"/>

          <activity android:name="com.zly.www.ipc.ThreeActivity"
              android:process="com.zly.www.ipc.zly"/>
      </application>

  </manifest>
           

這裡是三個activity.

MainActivity未指定process是以運作在預設的程序中,而預設的程序名是包名.TwoActivity和ThreeActivity指定了不同的process,是以将會建立兩個程序TwoActivity process值為:zly,而”:”的含義指目前程序名為 包名 + “:zly”,是一種簡寫,是以程序為com.zly.www.ipc:zly. ThreeActivity process值為com.zly.www.ipc.zly是以程序名就是com.zly.www.ipc.zly

其次,程序名以:開頭的程序為目前應用的私有程序,其他應用的元件不可以和它跑在同一程序中,而程序名不以:開頭的屬于全局程序,其他應用可以通過shareUID方式和他跑在同一程序.

這裡可以通過ddms工具看出

IPC(上)-多程式入門1 IPC介紹2 Android中多程式模式

确實開起了三個程序,但這隻是開始,實際使用中多程序還有很多問題需要處理後面将細說

2.2 多程序模式的運作機制

如果用一句話來形容多程序,那麼可以這樣描述,當應用開啟了多程序以後,各種奇怪的現象就都來了,舉個栗子,建立一個類User,裡面有一個靜态成員變量uId如下

public class UserBean {

  public static int uId = 1;

}
           

其中MainActivity運作在預設程序,TwoActivity通過指定process運作在一個獨立的程序中,然後在MainActivity的onCreate()方法中将uId指派為2,列印出這個變量,再在TwoActivity中列印該值

IPC(上)-多程式入門1 IPC介紹2 Android中多程式模式

發現結果和我們平常的所見的完全不同,按正常情況下TwoActivity中uid應該也是2才對,看到這裡也就應了前面所說的,當應用開啟了多程序以後,各種奇怪的現象就都來了,是以多程序絕非僅僅隻是添加一個process屬性那麼簡單.

上述問題出現的原因是TwoActivity運作在一個單獨的程序中,而Android為每一個應用配置設定了一個獨立的虛拟機,或者說為每一個程序都配置設定一個獨立的虛拟機,不同的虛拟機在記憶體配置設定上有不同的位址空間,這就導緻在不同的虛拟機中通路同一個類的對象會産生多份副本.拿我們這個例子來說,在TwoActivity所在程序和MainActivity所在程序都存在一個UserBean類,并且這兩個類互相不幹擾,在一個程序中修改uId值隻會影響目前程序,對其他程序都不會造成任何影響,這樣就解釋了為什麼在MainActivity修改uId的值,而在TwoActivity中uId值沒有發生改變.

所有運作在不同程序中的四大元件,隻要他們之間通過記憶體來共享資料都會失敗,這也是多程序帶來的主要影響.一般來說,使用多程序會造成如下幾個方面的問題

  1. 靜态成員變量和單例模式失效
  2. 線程同步機制完全失效
  3. SharedPreferences的可靠性下降
  4. Application會多次建立

第1個問題上面已經分析了.第2個問題本質和第1個是一個問題,既然都不是一個記憶體了,那麼不管是鎖對象還是鎖全局類都無法保證線程同步,因為不同程序鎖的不是同一個對象.第3個問題是因為SharedPreferences不支援兩個程序同時執行操作否則會有資料丢失的可能,第4個問題,當一個元件跑在一個新的程序中的時候,由于系統要在建立新的程序同時配置設定獨立虛拟機,是以這個過程其實就是啟動一個應用的過程.是以,相當于系統又把這個應用重新啟動了一遍,既然重新開機了當然會建立新的application,其實可以這麼了解,運作在同一個程序的元件是屬于同一個虛拟機和同一個application.為了更加清晰的展示着一點,下面我們做個測試,首先在Application的onCreate方法中列印出目前程序的名字,然後連續啟動三個同一個應用内但屬于不同程序的Activity,按照期望,Application的onCreate應該執行三次,并且列印出來的三次的程序名不同.

public class AppApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        Logger.init("zhuliyuan");

        Logger.d(getCurProcessName(this));
    }

    String getCurProcessName(Context context) {
        int pid = android.os.Process.myPid();
        ActivityManager mActivityManager = (ActivityManager) context
                .getSystemService(Context.ACTIVITY_SERVICE);
        for (ActivityManager.RunningAppProcessInfo appProcess : mActivityManager
                .getRunningAppProcesses()) {
            if (appProcess.pid == pid) {

                return appProcess.processName;
            }
        }
        return null;
    }
}
           

運作以後日志如下

IPC(上)-多程式入門1 IPC介紹2 Android中多程式模式

可以看出,Application執行了三次onCreate,并且每次程序名都不一樣,它的程序名為我們在Manifest檔案中指定的process屬性值一緻.這也就證明了再多程序模式中,不同程序的元件的确會擁有獨立的虛拟機,Application和記憶體空間,這會給我們開發帶來困擾,尤其需要注意.

雖然多程序會給我們帶來很多問題,但是系統給我們提供了很多跨程序通訊的方法,雖然說不能直接共享記憶體,但是通過跨程序通訊我們還是可以實作資料互動.實作跨程序通訊的方式有很多,比如Intent傳遞資料,共享檔案和SharedPreferences,基于Binder的Messager和AiDL以及Socket,但是為了更好的了解IPC各種方式,下面先介紹些基礎概念.

2.3 IPC基礎概念

下面介紹IPC中一些基本概念,Serializable接口,Parcelable接口以及Binder,熟悉這三種方式後,後面才能更好的了解跨程序通信的各種方式.

2.3.1 Serializable接口

Serializable接口是Java提供的一個序列化接口,它是一個空接口,為對象提供标準的序列化和反序列化操作,實作Serializable來實作序列化相當簡單,隻需要在類的聲明中指定一個類似下面的标示即可自動實作預設的序列化過程.

public class UserBean implements Serializable{

    private static final long serialVersionUID = 213213213123L;

    public static int uId = 1;
}
           

當然serialVersionUID并不是必須的,我們不聲明這個同樣也可以實作序列化,但是這個會對反序列化産生影響,具體影響後面介紹.

通過Serializable實作對象的序列化,非常簡單,幾乎所有的工作都被系統完成了,具體對象序列化和反序列化如何進行的,隻需要采用ObjectOutputStream和ObjectInputStream即可實作,下面看一個栗子

//序列化
User user = new User("大帥比");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(bean);
oos.close();

//反序列化過程
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
User user = (User)ois.readObject();
ois.close;
           

當然還可以通過重寫writeObject()與readObject()自定義序列化過程.

private void writeObject(java.io.ObjectOutputStream out)
    throws IOException {
  // write 'this' to 'out'...
}

private void readObject(java.io.ObjectInputStream in)
    throws IOException, ClassNotFoundException {
  // populate the fields of 'this' from the data in 'in'...
}
           

上述代碼示範了采用Serializable方式序列化對象的過程,有一點需要注意的是恢複後user和之前的user并不是同一個對象,這裡我轉載了一篇關于serializable序列化的blog有興趣的小夥伴可以看看Java序列化與反序列化.

剛開始提到,即使不指定serialVersionUID也可以實作序列化,那麼這個到底是幹嘛的呢,其實這個serialVersionUID是用來輔助序列化和反序列化過程的,原則上序列化後的資料中的serialVersionUID隻有和目前類的serialVersionUID相同才能夠正常的被反序列化.serialVerisonUID的工作機制是這樣:序列化的時候把目前類的serialVersionUID寫入序列化檔案中(也可能是其他中介),當反序列化的時候系統會去檢測檔案中的SerialVerisonUID,看是否和目前類一緻,如果一緻則說明序列化的類和反序列化的類的版本相同序列化成功,否則說明目前類和序列化時的類發生了某些變化,這個時候無法序列化,會報如下異常

java.io.InvalidClassException: com.zly.www.ipc.UserBean; 
    Incompatible class (SUID): com.zly.www.ipc.UserBean: static final long serialVersionUID =213213213123L;
     but expected com.zly.www.ipc.UserBean: static final long serialVersionUID =21333123L;
           

一般來說,我們應該手動指定serialVersionUID的值,如果不手動指定serialVersionUID,反序列化時目前類有所改變,比如增加或者删除了某些成員變量,那麼系統會重新計算目前hash值并把他賦給serialVersionUID,這個時候目前類serialVerisonUID就和目前類不一緻,序列化失敗,是以當我們手動指定了它以後,就可以很大程度避免反序列化失敗.比如當版本更新以後,我們可能隻删除或者新增了某些成員變量,這個時候我們反序列化仍然能成功,程式仍然能最大限度的恢複資料,相反,如果不指定serialVerisonUID的話,程式則會crash,當然如果類的結構發生了非正常性改變,比如修改了類名,修改了成員變量的類型,這個時候盡管serialVersionUID通過了,但是反序列化的過程還是會失敗,因為類的結構發生了毀滅性改變.

另外需要注意下,首先靜态成員變量屬于類不屬于對象,是以不會參與序列化過程;其次用transient關鍵字标記的成員變量不參與序列化過程.

2.3.2 Parcelable接口

Parcelable也是一個接口,隻要實作這個接口,一個類和對象就可以實作序列化并可以通過Intent和Binder傳遞.下面是個典型用法.

public class UserBean implements Parcelable{


    public static int uId = 1;

    public String userName;

    public int age;


    protected UserBean(Parcel in) {
        userName = in.readString();
        age = in.readInt();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(userName);
        dest.writeInt(age);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public static final Creator<UserBean> CREATOR = new Creator<UserBean>() {
        @Override
        public UserBean createFromParcel(Parcel in) {
            return new UserBean(in);
        }

        @Override
        public UserBean[] newArray(int size) {
            return new UserBean[size];
        }
    };
}
           

在序列化的過程中需要實作功能有序列化,反序列化和内容描述.序列化功能由writeToParcel方法完成,是通過Parcel中一系列write方法來完成,反序列化功能由CREATOR來完成,其内部表明了如何建立序列化對象和數組,并通過Parcel一系列read方法在完成反序列化,内容描述由describeContents方法完成.

既然Parcelable與Serializable都能實作序列化,那我們該如何選擇呢,Parcelable主要用在記憶體序列化上,其他情況用Serializable

2.3.3 Binder

由于Binder是一個非常深入的話題不是一兩句能說清的,是以本節側重Binder的使用和上層原理.

簡單的說,Binder是Android中的一個類,實作了IBinder接口.Android開發中,Binder主要用于Service中,包括AIDL和Messenger,其中普通service中的Binder不涉及程序間通信,而Messenger底層就是AIDL,是以這裡從AIDL入手分析Binder工作機制.(AIDL不太熟悉的同學可以參考我另一篇blog回顧下 AIDL使用)

這裡建立一個AIDL示例,然後SDK會自動為我們生成AIDL所對應的Binder類,然後我們可以分析Binder工作過程.建立自定義資料類型Book.java和自定義類型說明檔案Book.aidl和IBookManager.aidl代碼如下

//Book.java
public class Book implements Parcelable {
    public int bookId;
    public String bookName;

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

    protected Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }
}

//Book.aidl
package com.zly.www.ipcdemo;
parcelable Book;

//IBookManager.aidl
package com.zly.www.ipcdemo;

// Declare any non-default types here with import statements
import com.zly.www.ipcdemo.Book;

interface IBookManager {

    List<Book> getBookList();

    void addBook(in Book book);

}
           

上面三個檔案中,Book.java是圖書資訊類,實作了Parcelable接口,Book.aidl是book在AIDL中的聲明,IBookManager是我們定義的接口.裡面有兩個方法getBookList和addBook,這裡這兩個方法主要用于示例.下面看下系統根據IBookManager.aidl生成的Binder類,在app\build\generated\source\aidl\debug\包名 目錄下有一個IBookManager.java類,接下來我們要根據這個生成的類分析Binder工作原理.

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: C:\\Users\\Administrator\\Desktop\\IPCDemo\\app\\src\\main\\aidl\\com\\zly\\www\\ipcdemo\\IBookManager.aidl
 */
package com.zly.www.ipcdemo;

public interface IBookManager extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.zly.www.ipcdemo.IBookManager {
        private static final java.lang.String DESCRIPTOR = "com.zly.www.ipcdemo.IBookManager";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.zly.www.ipcdemo.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.zly.www.ipcdemo.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.zly.www.ipcdemo.IBookManager))) {
                return ((com.zly.www.ipcdemo.IBookManager) iin);
            }
            return new com.zly.www.ipcdemo.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.zly.www.ipcdemo.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.zly.www.ipcdemo.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.zly.www.ipcdemo.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.zly.www.ipcdemo.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.zly.www.ipcdemo.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.zly.www.ipcdemo.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.zly.www.ipcdemo.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addBook(com.zly.www.ipcdemo.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.zly.www.ipcdemo.Book> getBookList() throws android.os.RemoteException;

    public void addBook(com.zly.www.ipcdemo.Book book) throws android.os.RemoteException;
}
           

上述代碼就是系統生成的IBookManager.java,他繼承了IInterface接口,同時他自己也是一個接口,所有在Binder中傳輸的接口都需要繼承IInterface這個接口.雖然看起來有點混亂,但實際還是清晰的,首先,他聲明了兩個方法getBookList和addBook,顯然這就是我們在IBookManager.aidl中聲明的方法,同時它還聲明了兩個整形的id TRANSACTION_getBookList與TRANSACTION_addBook用于辨別這兩個方法,這兩個辨別用于在transact過程中區分用戶端到底請求的哪個方法,然後他有個内部類Stub,Stub繼承了Binder,當用戶端和服務端都位于一個程序時,方法調用不會走跨程序的transact過程.當兩者位于不同程序時,方法調用需要走transact過程,這個邏輯由Stub内部代理Proxy來完成,可以發現IBookManager這個接口很簡單,這個接口實作的核心就是它的内部類Stub和Stub内部代理類Proxy,下面具體介紹下每個方法含義:

  • DESCRIPTOR

    Binder唯一辨別,一般為目前Binder的類名.

  • asInterface(android.os.IBinder obj)

    用于将服務端的Binder對象轉換成用戶端所需的AIDL接口對象,這種轉換過程區分程序,如果用戶端和服務端在同一程序,那麼此方法傳回的就是服務端的Stub對象本身,否則就傳回的系統封裝後的Stub.proxy對象.

    這裡我們來分析下,一般AIDL我們是在service中寫一個内部類這裡暫且叫MyBinder實作Stub,然後在用戶端我們根據Stub.asInterface()擷取service中的實作MyBinder,在調用他的方法….

    在service中我們僅僅是實作了Stub并未做其他操作,是以這個流程中能夠決定我們究竟是走跨程序的transact方法還是直接走MyBinder實作的方法關鍵就看asInterface,接下來看代碼

    IPC(上)-多程式入門1 IPC介紹2 Android中多程式模式
    可以看到有兩種不同的傳回值,一個是擷取到的android.os.IInterface,一個是Stub.Proxy對象,這裡先分析android.os.IInterface為傳回值的時候
    IPC(上)-多程式入門1 IPC介紹2 Android中多程式模式
    它先判斷唯一辨別DESCRIPTOR是否相同如果相同傳回mOwner,再來看看mOwner是什麼
    IPC(上)-多程式入門1 IPC介紹2 Android中多程式模式
    IPC(上)-多程式入門1 IPC介紹2 Android中多程式模式

    可以看到mOwner是通過attachInterface()方法傳入的,而Stub在構造方法的時候将this傳入,是以mOwner就是Stub,也就是我們service中實作的MyBinder對象,是以當傳回值為android.os.IInterface的時候我們實際就走的本地的

    再來看Stub.Proxy為傳回值的時候,當我們拿到proxy對象在調用他的getBookList或者addBook方法的時候都走了transact方法,通過下面代碼可以看出

    IPC(上)-多程式入門1 IPC介紹2 Android中多程式模式
    都調用的mRemote.transact()方法,再來看看mRemote是什麼
    IPC(上)-多程式入門1 IPC介紹2 Android中多程式模式
    IPC(上)-多程式入門1 IPC介紹2 Android中多程式模式
    可以看出mRemote就是Binder,然後transact方法實際調用的
    IPC(上)-多程式入門1 IPC介紹2 Android中多程式模式
    OnTransact方法我們在Stub重寫了
    IPC(上)-多程式入門1 IPC介紹2 Android中多程式模式

    經過這個流程就回到服務端的實作的Stub了,是以當傳回值為Stub.Proxy我們實際走的跨程序的

    總結一下,其實同一個程序的話 aidl就類似接口回調,如果不同程序的話 就是client拿到 stub.proxy 然後調用方法 在回調到 service中 stub的onTransact根據code判斷在調用具體的方法

    當然這裡還有些疑問比如Binder是如何從service傳遞到client的,然後他為何調用了transact就為跨程序調用..目前我也不知道,但是開發藝術在後面應該會講,是以我們暫且把這個問題留下後面再解決

  • asBinder

    用于傳回目前Binder對象

  • onTransact

    這個方法運作在服務端中的Binder線程池中,當用戶端發起跨程序請求時,遠端請求會通過系統底層封裝後調用此方法,該方法的原型為onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags),服務端通過code可以确定用戶端所請求的是哪個方法,接着從data中取出目标方法需要的參數(如果目标方法有參數的話),然後執行目标方法,當方法執行完以後,就會向reply中寫入傳回值(如果目标方法有傳回值的話),需要注意的是,如果此方法傳回false,那麼用戶端會請求失敗,是以我們可以利用這特性來做權限驗證.

  • Proxy#getBookList

    這個方法運作在用戶端,當用戶端遠端調用此方法時,它的内部實作是這樣的,先建立該方法需要的輸入型Parcel對象_data,輸出型Parcel對象_reply和傳回值對象List;然後把該方法的參數資訊寫入_data中(如果有參數的話);接着調用transact方法來發起RPC(遠端過程調用),同時目前線程挂起,然後服務端的onTransact方法會被調用,直到RPC過程傳回後,目前線程繼續執行,并從_reply中取出RPC過程的傳回結果,最後傳回_reply中的資料

  • Proxy#addBook

    這個方法運作在用戶端,它執行過程和getBookList一樣,不過addBook沒有傳回值,是以他不需要從_reply中取出傳回值.

通過上面的分析,我們應該對Binder工作流程有了個簡單的了解,但是還有兩點需要說明下.

  1. 當用戶端發起遠端請求的時候,由于目前線程會被挂起直至服務端程序傳回,是以如果遠端操作是一個很耗時,那麼不能再ui線程中發起遠端請求
  2. 由于服務端的Binder方法運作在Binder的線程池中,是以Binder方法不管是否耗時都應該采用同步的方式去實作,因為它已經運作在一個線程中了.

下面給出一個binder的工作機制圖,友善了解

IPC(上)-多程式入門1 IPC介紹2 Android中多程式模式

通過上面的分析來看,我們完全可以不寫AIDL檔案實作Binder,之是以寫AIDL檔案,是為了系統幫我們生成代碼,系統根據AIDL檔案生成Java檔案的格式是固定,我們可以抛開AIDL檔案直接寫一個Binder出來,然而我覺得還是根據AIDL檔案自動生成吧,但是這裡我們需要知道AIDL檔案并不是實作Binder的必需品,AIDL本質是系統為我們提供了一種快速實作Binder的工具,僅此而已.

接下來我們介紹Binder兩個重要的方法linkToDeath和unlinkToDeath.我們知道,Binder運作在服務端程序,如果服務端程序由于某種原因終止,這個時候我們到服務端的Binder連接配接斷裂(稱之為Binder死亡),會導緻我們遠端調用失敗,更加關鍵的是如果我們用戶端不知道Binder連接配接已經斷裂,那麼用戶端功能将會受影響,是以為了解決這個問題,Binder提供了兩個配對的方法linkToDeath和unlinkToDeath,通過linkToDeath可以給Binder設定一個死亡代理,當Binder死亡時,我們就會收到通知,這個時候我們可以重新發起連接配接請求進而恢複連接配接,下面通過代碼介紹.

首先聲明一個DeathRecipient對象,實作其内部方法binderDied,當Binder死亡的時候,系統就會回調BinderDied方法,然後我們就可以移除之前綁定的binder代理并重新綁定遠端服務

private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {

        @Override
        public void binderDied() {
            Log.i("ppjun", "binderDied");

            if (mIBookManager == null)
                return;
            mIBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
            mIBookManager = null;

            Intent intent = new Intent("111");
            intent.setPackage("com.zly.www.ipcdemo");
            bindService(intent, new MyServiceConnection(), BIND_AUTO_CREATE);
        }
    };
           

其次在用戶端綁定遠端服務成功後,給binder設定死亡代理

class MyServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            mIBookManager = AbsBookManager.asInterface(binder);
            try {
                binder.linkToDeath(mDeathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    }
           

其中linkToDeath第二個參數是标記為,我們給0就可以,當Binder死亡的時候我們就可以收到通知了,(然後我測試了一把是可以收到Binder死亡的通知,但是并不能拉起遠端服務,是以其實沒卵用),另外通過Binder的isBinderAlive也可以判斷Binder是否死亡.

到此IPC基礎介紹完全…累死寶寶,剛哥ipc這部分确實寫的好是以有不少文字直接摘自剛哥的開發藝術了,再此表示感謝!!!