Android-多進程通信(一)

一、簡介

Android的多進程通信即IPC是指兩個進程之間進行數(shù)據(jù)交換。進程一般指一個執(zhí)行單元,在PC和移動設備中指一個程序或應用。最簡單的情況下,Android應用中只有一個進程,包含一個線程,即主線程,也叫作UI線程,只能在此線程更新操作UI。普通情況下是不需要多進程的,但是當應用需要更多的內存或者某些特殊的Module或特殊的需求需要運行在多進程條件下。

在Android中多進程的方式有許多,Bundle、文件共享、Socket、Messenger、ContentProvider、AIDL。因為Android是Linux內核,所以文件的并發(fā)讀寫是被允許的,可寫數(shù)據(jù)或將對象序列化寫入文件,但是并沒有提供什么便捷性,還容易出問題。Socket因為是端到端的通信,天然支持多進程,一個進程為服務端,一個進程作客戶端。Messenger信使,可以在不同的進程傳遞Message對象,是一種輕量級的多進程方法。ContentProvider是Android提供的專門用于不同應用之間數(shù)據(jù)共享的方式,當然可用來進程間通信。系統(tǒng)也提供了很多內置的ContentProvider,通訊錄、短信等。AIDL是Messenger的底層,實現(xiàn)相對復雜,但是能處理大量的并發(fā)請求及跨進程調用服務端的方法,Android將AIDL做了封裝,便有了更方便上層調用的Messenger。

二、多進程模式

2.1 開啟多進程

Android開啟多進程的方式不多,正常情況只能在Manifest中給四大組件(Activity、Service、ContentProvider、Receiver)指定process屬性。也有一個非常規(guī)的手段,通過JNI在native層去fork一個進程。以下是Activity的多進程示例,其他組件類似。:remotecom.example.package.remote表示此Activity運行在名為com.exmaple.package:remotecom.exmaple.package.remote的進程中。

<activity
    android:name=".activity.RemoteActivity2"
    android:label="Android多進程/.remote"
    android:process="com.example.package.remote" />
<activity
    android:name=".activity.RemoteActivity1"
    android:label="Android多進程/:remote"
    android:process=":remote" />

通過ADB命令查看

adb shell ps | findstr com.libo
image

可以看到有三個進程,其中以包名命名的進程是默認進程,.remote:remote是自定義的多進程,兩個自定義進程之間的區(qū)別就是:是一種簡寫形式,實際是在:前附加上packagename;.是定義了完整的進程名,不會附加包名信息,顯示com.libok.androidnote.remote是因為定義的名稱如此。:開頭的進程是屬于當前應用的私有進程,其他應用的組件不能和它跑在同一個進程中;.進程名的是屬于全局進程,其他應用可以通過ShareUID方式和它跑在同一個進程中。

2.2 多進程帶來的問題

  1. 靜態(tài)變量失效

    在一個Activity中新建一個靜態(tài)變量TEST_STATIC,并在RemoteActivity1中的onStartOtherRemoteActivity方法中自增,之后啟動RemoteActivity2,并在2中打印TEST_STATIC的值。

    public static int TEST_STATIC = 21;
    
    public void onStartOtherRemoteActivity(View view) {
        TEST_STATIC++;
        Log.e(TAG, "onStartOtherRemoteActivity: " + TEST_STATIC);
        startActivity(new Intent(this, RemoteActivity2.class));
    }
    

    結果:

    // RemoteActivity1 log
    E/RemoteActivity1: onStartOtherRemoteActivity: 22
        
    // RemoteActivity2 log
    E/RemoteActivity2: onCreate: 21
    

    并不相同的數(shù)值說明在多進程中靜態(tài)變量是失效的,同樣的因為靜態(tài)變量帶來的問題是單例模式的失效。原因就是多進程時Android為其他進程分配了一個新的虛擬機,導致不同的虛擬機在內存上有不同的內存地址, 當在新的進程訪問變量時,訪問的其實是這個類在新的虛擬機中的副本,也就是相當于在:remote和.remote中各有一個RemoteActivity1類,而.remote訪問的那個副本中的TEST_STATIC是沒有進行自增操作的,所以還是會打印出21的初始數(shù)值,而在:remote中是自增過的22。單例模式也是同樣的解釋,當在另一個進程中訪問單例類時,在此進程中其實并沒有進行初始化,所以才會失效。

  1. 線程同步機制失效

    本質上跟靜態(tài)變量類似,在一個進程鎖住的是副本的對象,而在另一個副本中,內存都不同,所以肯定是無效的。

  2. SharedPreferences可靠性下降

    因為SharedPreferences不支持兩個進程同時去執(zhí)行寫操作,否則會導致一定幾率的數(shù)據(jù)丟失。SharedPreferences的底層是通過讀寫XML文件實現(xiàn)的,并發(fā)寫很可能導致問題,并發(fā)讀寫都不能保證不會出問題。

  3. Application會被創(chuàng)建多次

    當一個組件跑在一個新的進程中時,系統(tǒng)給新的進程分配一個新的虛擬機,就相當于應用又一次的重新啟動,Application作為應用基礎肯定也會被重新創(chuàng)建。

    新建Application類,繼承自Application,并在onCreate方法中輸出當前進程的PID:

    public class LApplication extends Application {
    
        private static final String TAG = "LApplication";
        
        @Override
        public void onCreate() {
            super.onCreate();
            Log.e(TAG, "onCreate: " + android.os.Process.myPid());
        }
    }
    

    當依次開啟進程后輸出如下:

    // Main
    E/LApplication: onCreate: 16031
    // RemoteActivity1
    E/LApplication: onCreate: 16127
    // RemoteActivity2
    E/LApplication: onCreate: 16202
    

    Application被創(chuàng)建多次帶來的問題是,有些時候會需要在Application中初始化些依賴,但是多進程就會隨著Application的創(chuàng)建而重復初始化,可以在Application中設置一些條件跳過重復初始化部分。

    // 根據(jù)pid獲取進程名
    private String getAppName(int pid) {
        String processName = null;
        ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> list = am.getRunningAppProcesses();
        for (ActivityManager.RunningAppProcessInfo info : list) {
            try {
                if (info.pid == pid) {
                    processName = info.processName;
                    return processName;
                }
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
        return null;
    }
    

    通過PID獲取進程名,與包名做對比,只有跟包名一致時才做一些初始化工作。

三、Binder

Binder是一個實現(xiàn)了IBinder接口的類。從IPC的角度來說,是一種跨進程通信方式,還可以理解為一種在Linux中沒有的虛擬物理設備,設備驅動是/dev/binder;從Android Framework角度說,Binder是ServiceManager連接各種Manager和相應的ManagerService的橋梁;從Android應用層來說,Binder是服務端和客戶端進行通信的媒介,當bindService時,服務端會返回一個包含服務端調用的Binder對象,通過這個對象,客戶端就可以獲取服務端提供的服務或信息數(shù)據(jù),這里的服務包括普通服務和基于AIDL的服務。

3.1 初識Binder

Binder主要用在Service中,包括AIDL和Messenger,普通的Service中的Binder不涉及進程間的通信,新建一個AIDL的示例如下。

  1. 在項目目錄下,新建aidl文件夾,并新建實現(xiàn)Parcelable接口的Book類。

    public class Book implements Parcelable {
    
        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];
            }
        };
    
        private int mBookId;
        private String mBookName;
    
        public Book(int bookId, String bookName) {
            mBookId = bookId;
            mBookName = bookName;
        }
    
        private Book(Parcel in) {
            mBookId = in.readInt();
            mBookName = in.readString();
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(mBookId);
            dest.writeString(mBookName);
        }
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        public int getBookId() {
            return mBookId;
        }
    
        public void setBookId(int bookId) {
            mBookId = bookId;
        }
    
        public String getBookName() {
            return mBookName;
        }
    
        public void setBookName(String bookName) {
            mBookName = bookName;
        }
    
        @Override
        public String toString() {
            return "Book{" +
                    "mBookId=" + mBookId +
                    ", mBookName='" + mBookName + '\'' +
                    '}';
        }
    }
    
    image-20200305102234931
  1. 在項目的main路徑下,新建aidl文件夾及子目錄com.example.name.aidl,并在其中創(chuàng)建IBookManager.aidl,如果是在AndroidStudio中,就省去了創(chuàng)建目錄的操作,直接在main->java->aidl下右鍵new->new AIDL File創(chuàng)建IBookManager.aidl就會自動創(chuàng)建到main->aidl->com.example.name.aidl目錄下。
    image-20200305102541442
![image-20200305102917196](https://upload-images.jianshu.io/upload_images/8533197-5e7adadf18ab8921.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

```java
// IBookManager.aidl
package com.libok.androidnote.aidl;

// Declare any non-default types here with import statements

import com.libok.androidnote.aidl.Book;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}
```

其中getBookList方法是獲取所有的Book,addBook方法是添加一個Book。addBook的參數(shù)中有個關鍵字in,表示數(shù)據(jù)是從客戶端傳遞到服務端,相應的還會有out以及inout,這個等后面的IPC方式時再做探討。
  1. 有了aidl在main路徑下的目錄后,再創(chuàng)建一個Book.aidl,IBookManager.aidl中也有import Book類,但是引入的不是Book.java而是Book.aidl,之所以后建Book.aidl,是因為需要先創(chuàng)建IBookManager.aidl所自動創(chuàng)建的路徑。

    package com.libok.androidnote.aidl;
    
    parcelable Book;
    
  1. 等Java類、類對應AIDL、業(yè)務數(shù)據(jù)處理AIDL三個文件(根據(jù)自身需要做調整)建好后執(zhí)行AndroidStudio->Build->Make Project即可自動生成IBookManager.java。AndroidStudio3.6.1+gradle3.6.1目錄如下:

    image-20200305110258535
```java
/*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package com.libok.androidnote.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.libok.androidnote.aidl.IBookManager {
        private static final java.lang.String DESCRIPTOR = "com.libok.androidnote.aidl.IBookManager";

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

        /**
         * Cast an IBinder object into an com.libok.androidnote.aidl.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.libok.androidnote.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.libok.androidnote.aidl.IBookManager))) {
                return ((com.libok.androidnote.aidl.IBookManager) iin);
            }
            return new com.libok.androidnote.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 {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(descriptor);
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(descriptor);
                    java.util.List<com.libok.androidnote.aidl.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(descriptor);
                    com.libok.androidnote.aidl.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.libok.androidnote.aidl.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

        private static class Proxy implements com.libok.androidnote.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.libok.androidnote.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.libok.androidnote.aidl.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.libok.androidnote.aidl.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

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

    public void addBook(com.libok.androidnote.aidl.Book book) throws android.os.RemoteException;
}
```

至此Binder就已經創(chuàng)建完畢,接下來各方面解析一下自動生成的IBookManager.java。

3.2 再識Binder

IBookManager類乍看有點亂,其實還是比較清晰的。

image-20200305111355036

IBookManager接口繼承自android.os.IInterface,并在內部實現(xiàn)一個靜態(tài)抽象內部類Stub,除此之外還聲明了兩個IBookManager.aidl中定義的getBookList()方法和addBook(com.libok.androidnote.aidl.Book book)方法。重點關注的應該是其內部類Stub。

Stub內部解析

image-20200305121201924
  • Stub類

    1.Stub繼承了android.os.Binder并實現(xiàn)了IBookManager接口,繼承Binder重寫了onTransact方法,使其能處理自定的業(yè)務和數(shù)據(jù)。

    2.實現(xiàn)了IBookManager接口的繼承方法asBinder。

  • DESCRIPTOR

    Binder唯一標識,通常用當前Binder的類名表示,防止沖突。

  • TRANSACTION_getBookList和TRANSACTION_addBook

    標識聲明的方法,在方法onTransact中區(qū)分遠程調用的方法是什么。范圍在IBinder.FIRST_CALL_TRANSACTION到IBinder.LAST_CALL_TRANSACTION之間。

  • asInterface方法

    用于將服務端的Binder對象轉換成客戶端所需要的AIDL接口類型的對象,如果客戶端和服務端在同一進程,則返回的是服務端的Stub對象本身,不在同一進程,返回的就是系統(tǒng)封裝后的Stub.Proxy對象。

  • asBinder方法

    返回當前Binder對象。

  • onTransact方法

    此方法運行在服務端中的Binder線程池中,當客戶端發(fā)起跨進程請求時,遠程會通過系統(tǒng)底層封裝后交由此方法處理。

    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
    

    服務端通過code區(qū)分被調用的方法,從data中取出方法所需的參數(shù),如果有參數(shù)的話,然后執(zhí)行方法。當方法執(zhí)行完畢,通過reply寫入返回值,如果有返回值的話。返回值表示客戶端是否請求成功,如果返回false,那么客戶端的請求會失敗,可用來作權限驗證,是否是準許的客戶端調用而不是其他的任意客戶端。

  • Proxy類

    代理類,當客戶端和服務端不在同一進程時會被從asInterface方法中創(chuàng)建,客戶端通過調用Proxy的方法實現(xiàn)調用服務端的方法。

Proxy內部解析

image-20200305122125185
  • getBookList方法

    方法運行在客戶端,當客戶端遠程調用此方法時,內部過程是這樣的:首先創(chuàng)建輸入型Parcel對象_data、輸出型Parcel對象_reply以及返回值對象List;接著把方法的參數(shù)寫進_data中(如果有參數(shù)的話),調用transact方法發(fā)起RPC(遠程過程調用)請求,同時將當前線程掛起;然后服務端的onTransact方法會被調用,將遠程方法結果寫入_reply中(如果有結果的話),并返回請求結果true或false,直到RPC過程返回后,當前線程才會繼續(xù)執(zhí)行,并從_reply中獲取RPC過程的方法結果;最后返回_reply中的數(shù)據(jù)。

  • addBook方法

    調用過程與上一個方法一樣,只是多了參數(shù),少了返回值。

總結

  1. 因為在客戶端發(fā)起請求后,所在的線程會被掛起直到服務端進程返回結果,所以如果一個遠程方法是耗時的,那么不能在UI線程中發(fā)起請求,會導致不必要的ANR。
  2. 由于服務端的Binder方法運行在Binder的線程池中,所以Binder方法不管是否耗時都應該采用同步的方式實現(xiàn),因為它已經運行在一個線程中了。
graph LR
A[Client]-->|遠程請求 掛起Client|B[Binder]
B-->|寫入?yún)?shù)|C(data)
C-->|Transact|D[Service]
D-->|onTransact|E(線程池)
E-->|寫入結果|F(reply)
F-->B
B-->|返回數(shù)據(jù) 喚醒Client|A

3.3 手寫B(tài)inder

public interface IMyBookManager extends IInterface {

    public static final String TAG = "IMyBookManager";

    public static abstract class Stub extends Binder implements IMyBookManager {
        private static final String DESCRIPTOR = "com.libok.androidnote.core.IMyBookManager";
        private static final int FUNCTION_GET_BOOK_LIST = FIRST_CALL_TRANSACTION + 1;
        private static final int FUNCTION_ADD_BOOK = FIRST_CALL_TRANSACTION + 2;

        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        public static IMyBookManager asInterface(IBinder binder) {
            if (binder == null) {
                return null;
            }
            IInterface iInterface = binder.queryLocalInterface(DESCRIPTOR);
            if (iInterface instanceof IMyBookManager) {
                Log.e(TAG, "asInterface: local interface");
                return (IMyBookManager) iInterface;
            }
            Log.e(TAG, "asInterface: proxy interface");
            return new Proxy(binder);
        }

        @Override
        public IBinder asBinder() {
            return this;
        }

        @Override
        protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION:
                    if (reply != null) {
                        reply.writeString(DESCRIPTOR);
                    }
                    return true;
                case FUNCTION_GET_BOOK_LIST:
                    data.enforceInterface(DESCRIPTOR);
                    List<Book> bookList = this.getBookList();
                    if (reply != null) {
                        reply.writeNoException();
                       // reply.writeException(new NullPointerException("Test Proxy reply.readException"));
                        reply.writeTypedList(bookList);
                    }
                    return true;
                case FUNCTION_ADD_BOOK:
                    data.enforceInterface(DESCRIPTOR);
                    Book book = null;
                    if (data.readInt() != 0) {
                        book = Book.CREATOR.createFromParcel(data);
                    }
                    this.addBook(book);
                    if (reply != null) {
                        reply.writeNoException();
                    }
                    return true;
                default:
                    return super.onTransact(code, data, reply, flags);
            }
        }

        public static class Proxy implements IMyBookManager {

            private IBinder mBinder;

            public Proxy(IBinder binder) {
                mBinder = binder;
            }

            @Override
            public IBinder asBinder() {
                return mBinder;
            }

            public String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public List<Book> getBookList() throws RemoteException {
                Parcel data = Parcel.obtain();
                Parcel reply = Parcel.obtain();
                List<Book> result = null;

                try {
                    data.writeInterfaceToken(DESCRIPTOR);
                    // data.writeInterfaceToken(DESCRIPTOR + "1");
                    mBinder.transact(FUNCTION_GET_BOOK_LIST, data, reply, 0);
                    reply.readException();
                    result = reply.createTypedArrayList(Book.CREATOR);
                } finally {
                    data.recycle();
                    reply.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);
                    }
                    mBinder.transact(FUNCTION_ADD_BOOK, data, reply, 0);
                } finally {
                    data.recycle();
                    reply.recycle();
                }
            }
        }

    }

    public List<Book> getBookList() throws RemoteException;

    public void addBook(Book book) throws RemoteException;
}

紙上得來終覺淺,絕知此事要躬行。

不手動寫一遍還體會不到這個流程到底是怎樣的,不知道Stub中onTransact方法執(zhí)行時首先需要data參數(shù)執(zhí)行一個enforceInterface方法,需要檢驗一下client和service執(zhí)行的是不是同一個方法,沒寫到Proxy類時,還在納悶這個方法到底是什么作用,源碼中也沒有作什么解釋,在Stack Overflow上搜加寫完Proxy類后才知道是一個檢驗方法,在Proxy類中執(zhí)行接口的方法第一步就是先在data執(zhí)行writeInterfaceToken,這個方法有注釋是這樣寫的:

/**
 * Store or read an IBinder interface token in the parcel at the current
 * {@link #dataPosition}.  This is used to validate that the marshalled
 * transaction is intended for the target interface.
 */

enforceInterface是一對函數(shù),這才恍然大悟,這也就是自己手動寫一遍的好處。

在寫的時候慢慢的有了更深的認識,當然再深都只是應用層的東西,想要更了解可以去看底層源碼。

Stub中的DESCRIPTOR、FUNCTION_GET_BOOK_LIST以及FUNCTION_ADD_BOOK沒什么好說的了。

  • Stub要繼承Binder,這是必然的,如果不繼承那么就沒多進程通信什么事了。
  • 還要實現(xiàn)IMyBookManager接口,當然這也是必然的,不然到了服務端都沒有業(yè)務方法可執(zhí)行。
  • Stub的構造函數(shù),只有一句this.attachInterface(this, DESCRIPTOR);但是卻關系著服務端運行在與客戶端相同或不同的進程時asInterface方法返回的Binder,也就是綁定Service時返回的Binder。只有在Binder中綁定Interface才會在Binder的queryLocalInterface方法查詢到,才能在不同的環(huán)境下返回正確的Binder。
  • asInterface靜態(tài)函數(shù),返回相應的繼承了相應接口的對象,在客戶端和服務端相同進程時返回Stub構造函數(shù)中注冊的本身,在不同進程時返回代理。其中首先參數(shù)binder執(zhí)行queryLocalInterface方法,查詢是否存在跟調用端也就是客戶端同一進程的接口對象。參數(shù)binder從何而來,在bindService時需要的ServiceConnection中的onServiceConnected方法參數(shù),在此方法的Binder肯定不會查詢到接口對象,所以才會創(chuàng)建一個Proxy代理對象。Proxy同樣也是運行在客戶端進程的。
  • asBinder方法沒啥可講的,不管是同進程還是不同進程,都是返回服務端的Binder。
  • 先說Proxy類再說Stub的onTransact方法,Proxy類是要實現(xiàn)IMyBookManager接口的,這也是必然,既然叫Proxy就得有代理的地方,也就是實現(xiàn)接口供客戶端調用方法,繼而在方法中完成跨進程的方法調用。
  • Proxy類的構造函數(shù)接收一個Binder對象,此Binder對象是Stub的asInterface方法中接收的Binder對象,同樣也是onServiceConnected的方法參數(shù)。
  • Proxy實現(xiàn)的接口方法中創(chuàng)建Parcel對象data和reply,需要注意的是Parcel對象的獲得及回收,因為Parcel類是有一個靜態(tài)的Parcel數(shù)組作Parcel池,要正確的維護此ParcelPool。其中data存放的是需要執(zhí)行的方法參數(shù),reply存放的是方法的執(zhí)行結果。在執(zhí)行遠程方法執(zhí)行請求之前要先在data中寫入一個token,以便在onTransact方法中作檢驗,之后執(zhí)行遠程方法的執(zhí)行請求——Binder的transact方法,此方法需要的參數(shù)是執(zhí)行的遠程方法的標識、data、reply,及一個flags,方法注釋中描述常規(guī)的遠程調用默認是0,如果是單向調用那么flags需要填寫FLAG_ONEWAY,此標記的意思是Proxy發(fā)送遠程方法的調用請求后立即返回,不再等遠程方法的執(zhí)行結果,看情況選擇哪一個flags。

    /**
     * Flag to {@link #transact}: this is a one-way call, meaning that the
     * caller returns immediately, without waiting for a result from the
     * callee. Applies only if the caller and callee are in different
     * processes.
     */
    

    之后一步是執(zhí)行reply的readException方法讀取一下方法執(zhí)行是否有異常,異常的寫入同樣是在遠程的onTransact方法中,如果有異常會在此步拋出異常。如果沒有異常那么就可以從reply中讀取正確的返回值,之后將Parcel對象data和reply回收,最后將返回值返回到調用方。

  • asBinder方法沒啥可講的,返回服務端的Binder。
  • Stub的onTransact方法,服務端具體執(zhí)行接口方法的地方

    protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException
    

    方法參數(shù)code標明Proxy想要調用的是哪個方法,data保存方法參數(shù),reply保存方法執(zhí)行結果。執(zhí)行自定義方法時先執(zhí)行data的enforceInterface,正常來說是沒問題的,但可以通過人為的操作讓其不正常,見手寫示例的Proxy類中的注釋語句,如果檢驗不成功的話是會報以下異常。

    Caused by: java.lang.SecurityException: Binder invocation to an incorrect interface
    at android.os.Parcel.createException(Parcel.java:2071)
    at android.os.Parcel.readException(Parcel.java:2039)
    at android.os.Parcel.readException(Parcel.java:1987)
    at com.libok.androidnote.core.IMyBookManagerStubProxy.getBookList(IMyBookManager.java:111)
    at com.libok.androidnote.activity.IPCActivity.onGetBookList(IPCActivity.java:65)

    之后就是調用遠程方法,遠程方法的實現(xiàn)在Service中創(chuàng)建Binder時實現(xiàn)的方法,此時就已經是真正自己的業(yè)務邏輯位置。如果方法執(zhí)行沒問題就可以在reply中寫入NoException,要是有異常可以執(zhí)行reply的writeException方法,將異常寫入reply,需要記得的是writeException方法和readException方法得成對出現(xiàn)。故意在其中寫入一個NullPointerException,見手寫示例的Stub類中的注釋語句,會在Proxy中執(zhí)行readException時拋出以下異常。

    Caused by: java.lang.NullPointerException: Test Proxy reply.readException
    at android.os.Parcel.createException(Parcel.java:2077)
    at android.os.Parcel.readException(Parcel.java:2039)
    at android.os.Parcel.readException(Parcel.java:1987)
    at com.libok.androidnote.core.IMyBookManagerStubProxy.getBookList(IMyBookManager.java:111)
    at com.libok.androidnote.activity.IPCActivity.onGetBookList(IPCActivity.java:65)

    之后再將方法執(zhí)行結果寫入reply中。onTransact方法是具有返回值的,返回true表示遠程方法調用成功,false則失敗,可以用此返回值做一些其他的檢驗,比如數(shù)據(jù)校驗等,不正??芍苯臃祷豧alse終止調用。

下面附上測試代碼及一點說明。

IPCActivity作為本地客戶端,布局就不列出了,只有兩個Button,其中注釋的代碼是用的通過AIDL來自動創(chuàng)建的IBookManager.java。

public class IPCActivity extends AppCompatActivity {

    private static final String TAG = "IPCActivity";

    private static final int BOOK_START_ID = 7873;

    private int mBookId = BOOK_START_ID;
//    private IBookManager mIBookManager = null;
    private IMyBookManager mOwnBookManager = null;

    private ServiceConnection mBookServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
//            mIBookManager = IBookManager.Stub.asInterface(service);
            mOwnBookManager = IMyBookManager.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ipc);
        Intent remoteBookServiceIntent = new Intent(this, BookManagerService.class);
        bindService(remoteBookServiceIntent, mBookServiceConnection, Context.BIND_AUTO_CREATE);
    }

    public void onGetBookList(View view) {
//        if (mIBookManager != null) {
//            try {
//                List<Book> bookList = mIBookManager.getBookList();
//                Log.e(TAG, "onGetBookList: " + bookList.toString());
//            } catch (RemoteException e) {
//                e.printStackTrace();
//            }
//        }
        if (mOwnBookManager != null) {
            try {
                List<Book> bookList = mOwnBookManager.getBookList();
                Log.e(TAG, "onGetBookList: " + bookList.toString());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    public void onAddBookToList(View view) {
//        if (mIBookManager != null) {
//            try {
//                mBookId++;
//                mIBookManager.addBook(new Book(mBookId, "Added Book" + mBookId));
//            } catch (RemoteException e) {
//                e.printStackTrace();
//            }
//        }
        if (mOwnBookManager != null) {
            try {
                mBookId++;
                mOwnBookManager.addBook(new Book(mBookId, "Own Added Book" + mBookId));
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mBookServiceConnection);
    }
}

BookManagerService作服務端,并在Manifest中設置Service的Process屬性讓其運行在另一個進程。

public class BookManagerService extends Service {

    private static final String TAG = "BookManagerService";

    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();

    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() {
            Log.e(TAG, "getBookList: " + mBookList.size());
            return mBookList;
        }

        @Override
        public void addBook(Book book) {
            Log.e(TAG, "addBook: " + book.toString());
            mBookList.add(book);
        }
    };

    private Binder mOwnBinder = new IMyBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }
    };

    public BookManagerService() {
        mBookList.add(new Book(123, "Love & Peace"));
        mBookList.add(new Book(321, "Play Boy"));
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
//        throw new UnsupportedOperationException("Not yet implemented");
        Log.e(TAG, "onBind: " + (mOwnBinder.queryLocalInterface("com.libok.androidnote.core.IMyBookManager") == null) + mOwnBinder.toString());
        return mOwnBinder;
    }
}

在服務端創(chuàng)建IMyBookManager的內部類Stub的對象mBinder,mBinder不僅是個Binder對象還是個IMyBookManager對象,并需要實現(xiàn)IMyBookManager接口的所有方法,即服務端業(yè)務邏輯處理。在Service被綁定時返回mBinder對象供客戶端中的服務會話ServiceConnection調用。ServiceConnection中onServiceConnected方法的參數(shù)IBinder service與mBinder是有不同點的,在Stub中能否通過DESCRIPTOR找到對應的接口對象,只有通過創(chuàng)建的Binder才會被注冊。

當服務端不是另一個進程時,流程就簡單了,只是繼承的Binder類跑到了IMyBookManager中,在綁定Service時onBind方法返回的和ServiceConnection中onServiceConnected方法參數(shù)是同一個Binder,而獲取接口對象的asInterface方法會直接返回找到的IInterface對象,即在構造函數(shù)中綁定的Binder this,然后跟Proxy類就沒什么關系了。

graph TB
A[客戶端綁定Service]-->|多|B[Service啟動]
A-->|單|B
B-->C[創(chuàng)建Service Binder即Stub并實現(xiàn)IInterface方法]
C-->|注冊|D[attachInterface]
D-->|binder queryLocalInterface|E{是否是本進程的Binder對象}
E-->|否|F[返回新的內部實現(xiàn)接口的Proxy代理類對象]
E-->|是|G[返回查詢到的接口對象, 還是服務端的Stub對象]
F-->|客戶端進程|H[客戶端執(zhí)行接口方法]
H-->|客戶端進程|I[Proxy執(zhí)行接口方法]
I-->|服務端進程, 輸入code data reply|J[Stub執(zhí)行標識對應方法有結果則返回]
J-->|客戶端進程|K[Proxy返回結果]
K-->|客戶端進程|L[客戶端接收結果]

C-->|注冊|D
G-->|客戶端進程|H
H-->|客戶端進程|M[Stub對象執(zhí)行接口方法]
M-->|客戶端進程|L

3.4 Binder死亡代理

Service不管是運行在另一個進程還是跟客戶端同進程,都可能會碰到被殺死的情況,此時Binder不能正常工作,導致調用失敗,當然Binder在設計是也是充分考慮到了這種情況,所以Binder中有一對很重要方法linkToDeath和unlinkToDeath可以做一些針對性的工作。

首先得聲明一個DeathRecipient對象,實現(xiàn)其binderDied方法,當代理的Binder死亡是會調用此方法,我們可以在此方法中作重新綁定Service等操作。

private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        Log.e(TAG, "binderDied: ");
        if (mOwnBookManager == null) {
            return;
        }
        mOwnBookManager.asBinder().unlinkToDeath(this, 0);
        mOwnBookManager = null;
        bindService(mRemoteBookServiceIntent, mBookServiceConnection, Context.BIND_AUTO_CREATE);
        // 其他操作
    }
};

在ServiceConnection中的onServiceConnected方法綁定死亡代理,即客戶端綁定遠程服務成功后,等Binder死亡時就可以收到通知并會執(zhí)行既定方法。

private ServiceConnection mBookServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
          mIBookManager = IBookManager.Stub.asInterface(service);
        Log.e(TAG, "onServiceConnected: " + service.toString());
        mOwnBookManager = IMyBookManager.Stub.asInterface(service);
        try {
            service.linkToDeath(mDeathRecipient, 0);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.e(TAG, "onServiceDisconnected: ");
    }
};

除此之外還可以通過Binder的isBinderAlive方法判斷Binder是否死亡。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380