Android多程式之Binder的使用

xxq2dream發表於2018-08-17

Android多程式系列

Binder是什麼

  • Binder是Android的一個類,實現了IBinder介面
  • 從IPC角度來說,Binder是Android中的一種跨程式通訊方式
  • Binder還可以理解為一種虛擬的物理裝置,裝置驅動是/dev/binder,Linux中沒有
  • 從Android Framework角度來說,Binder是ServiceManger連線各種Manger(ActivityManager、WindowManager等)和相應的ManagerService的橋樑
  • 從Android應用層來說,Binder是客戶端和服務端進行通訊的媒介,當bindService的時候會返回服務端的Binder物件,通過這個Binder物件可以呼叫服務端的服務

使用Binder

生成Binder類
  • 可以通過2中方式生成Binder:通過AIDL檔案讓系統自動生成;我們自己手動編寫Binder
通過AIDL檔案生成Binder
  • 首先需要準備一個Parcelable物件
public class Book implements Parcelable {

    public int bookId;
    public String bookName;

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

    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];
        }
    };

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

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }
}
複製程式碼
  • 然後編寫AIDL檔案

新建AIDL檔案,AS中會自動建立aidl的目錄

// Book.aidl
package com.xxq2dream.aidl;

parcelable Book;

複製程式碼
// IBookManager.aidl
package com.xxq2dream.aidl;

// Declare any non-default types here with import statements
//必須顯示匯入需要的Parcelable物件
import com.xxq2dream.aidl.Book;

//除了基本型別,其他引數都要標上方向,in表示輸入型引數,out表示輸出型引數,inout表示輸入輸出引數
interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}
複製程式碼

最好把AIDL相關的檔案都放在一個目錄下

  • 檔案編寫完成以後通過Make Project命令就可以生成對應的Binder類

通過make讓系統自動生成Binder類

生成的Binder類

模擬客戶端和服務端的程式間通訊
  • 首先編寫一個Service類,並設定android:process屬性,開啟在不同的程式
public class BookManagerService extends Service {
    private static final String TAG = "BookManagerService";

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

    private Binder mBinder = new BookManagerImpl(){
        @Override
        public List<Book> getBookList() throws RemoteException {
            Log.e(TAG, "getBookList-->"+ System.currentTimeMillis());
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            Log.e(TAG, "addBook-->");
            mBookList.add(book);
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "onBind-->"+ System.currentTimeMillis());
        return mBinder;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "onCreate-->"+ System.currentTimeMillis());
        mBookList.add(new Book(1, "Android"));
        mBookList.add(new Book(2, "IOS"));
    }
}
複製程式碼
  • 註冊Service
//AndroidManifest.xml
<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

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

    <service
        android:name="com.xxq2dream.service.BookManagerService"
        android:process=":remote" />
</application>
複製程式碼

可以看到2個程式

  • 客戶端的話就簡單在activity中通過bindService方法繫結服務端,然後通過返回的Binder呼叫服務端的方法
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.e(TAG, "ServiceConnection-->"+ System.currentTimeMillis());
            //通過服務端回傳的Binder得到客戶端所需要的AIDL介面型別的物件,即我們上面的IBookManager
            IBookManager bookManager = BookManagerImpl.asInterface(iBinder);
            try {
                // 通過AIDL介面型別的物件bookManager呼叫服務端方法
                List<Book> list = bookManager.getBookList();
                Log.e(TAG, "query book list, list type:" + list.getClass().getCanonicalName());
                Log.e(TAG, "query book list:" + list.toString());
                Book newBook = new Book(3, "Android 進階");
                bookManager.addBook(newBook);
                Log.e(TAG, "add book:" + newBook);
                List<Book> newList = bookManager.getBookList();
                Log.e(TAG, "query book list:" + newList.toString());

            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.e(TAG, "binder died");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(this, BookManagerService.class);
        //繫結服務,後面會回撥onServiceConnected方法
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        //解綁服務
        unbindService(mConnection);
        super.onDestroy();
    }
}
複製程式碼
  • 通過以上的幾個步驟我們就實現了一個簡單的程式間通訊的例子

客戶端程式日誌
服務端程式日誌

呼叫過程
  • 通過列印的日誌我們可以大概分析上面的例子中各個方法的呼叫過程,這裡我們分析下呼叫服務端獲取書本列表的過程
  • 客戶端呼叫bindService方法後,BookManagerService建立,呼叫Binder類的構造方法建立Binder
  • 然後BookManagerService的onBind方法將建立的Binder返回給客戶端
  • 客戶端的onServiceConnected方法被呼叫,然後呼叫Binder的asInterface方法得到AIDL介面型別的物件bookManager
  • 呼叫bookManager的getBookList方法實際上呼叫的是Binder類中的Proxy類對應的getBookList方法
  • 在Proxy類對應的getBookList方法中呼叫Binder的transact方法發起遠端過程呼叫請求,同時當前執行緒掛起,服務端的onTransact方法會被呼叫
  • 服務端的onTransact方法被呼叫,通過code找到具體要呼叫的方法,這裡是TRANSACTION_getBookList
  • 最後會呼叫BookManagerService中的mBinder物件對應的getBookList方法,將書籍列表mBookList返回,返回的結果在Parcel變數reply中
  • Proxy類中的getBookList方法通過result = reply.createTypedArrayList(Book.CREATOR);獲取到服務端返回的資料
  • 客戶端onServiceConnected方法中接收到資料,呼叫過程結束
需要注意的地方
  • 客戶端呼叫遠端請求時客戶端當前的執行緒會被掛起,直到服務端程式返回資料,所以不能在UI執行緒中發起遠端請求
  • 客戶端的onServiceConnected和onServiceDisconnected方法都執行在UI執行緒中,不可以在裡面直接呼叫服務端的耗時方法
  • 服務端的Binder方法執行在Binder執行緒池中,所以Binder方法不管是否耗時都應該採用同步的方式去實現
  • 同上面一點,服務端的Binder方法需要處理執行緒同步的問題,上面的例子中CopyOnWriteArrayList支援併發讀寫,自動處理了執行緒同步
  • AIDL中能夠使用的List只有ArrayList,但AIDL支援的是抽象的List。因此雖然服務端返回的是CopyOnWriteArrayList,但是在Binder中會按照List的規範去訪問資料並最終形成一個新的ArrayList傳遞給客戶端

結語

  • 以上只是Binder的簡單應用,Binder的使用過程中還是有很多問題需要注意的
  • 比如Binder意外死亡以後怎麼辦?
  • 如何註冊監聽回撥,當服務端有新訊息後馬上通知註冊回撥的客戶端?如何解除註冊?
  • 如何進行許可權驗證等

                歡迎關注我的微信公眾號,和我一起學習一起成長!
複製程式碼

AntDream

相關文章