IPC(上)-多進程入門

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工具看出

確實開起了三個進程,但這只是開始,實際使用中多進程還有很多問題需要處理後面將細說

2.2 多進程模式的運行機制

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

public class UserBean {

  public static int uId = 1;

}

其中MainActivity運行在默認進程,TwoActivity通過指定process運行在一個獨立的進程中,然後在MainActivity的onCreate()方法中將uId賦值爲2,打印出這個變量,再在TwoActivity中打印該值

發現結果和我們平常的所見的完全不同,按正常情況下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;
    }
}

運行以後日誌如下

可以看出,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,接下來看代碼

    可以看到有兩種不同的返回值,一個是獲取到的android.os.IInterface,一個是Stub.Proxy對象,這裏先分析android.os.IInterface爲返回值的時候

    它先判斷唯一標識DESCRIPTOR是否相同如果相同返回mOwner,再來看看mOwner是什麼


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

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

    都調用的mRemote.transact()方法,再來看看mRemote是什麼

    可以看出mRemote就是Binder,然後transact方法實際調用的

    OnTransact方法我們在Stub重寫了

    經過這個流程就回到服務端的實現的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的工作機制圖,方便理解

通過上面的分析來看,我們完全可以不寫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這部分確實寫的好所以有不少文字直接摘自剛哥的開發藝術了,再此表示感謝!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章