Android IPC機制(三):淺談Binder的使用

very_on發表於2018-07-08

一、前言

在上一篇部落格Android IPC機制(二):AIDL的基本使用方法中,筆者講述了安卓程式間通訊的一個主要方式,利用AIDL進行通訊,並介紹了AIDL的基本使用方法。其實AIDL方式利用了Binder來進行跨程式通訊,Binder是Android中的一種跨程式通訊方式,其底層實現原理比較複雜,限於筆者水平,不能展開詳談,所以這篇文章主要談談以AIDL為例,談談Binder的使用方法。


二、原理

上一篇文章中建立了一個IMyAidl.aidl檔案,即介面檔案,隨即編譯了該檔案,生成了一個.java檔案,該檔案在gen目錄下:


開啟該檔案,得到如下程式碼:

  1. <span style="font-size:18px;">/*
  2. * This file is auto-generated. DO NOT MODIFY.
  3. * Original file: G:\\Android\\Project\\MyAidl\\app\\src\\main\\aidl\\com\\chenyu\\service\\IMyAidl.aidl
  4. */
  5. package com.chenyu.service;
  6. public interface IMyAidl extends android.os.IInterface {
  7. /**
  8. * Local-side IPC implementation stub class.
  9. */
  10. public static abstract class Stub extends android.os.Binder implements com.chenyu.service.IMyAidl {
  11. ......
  12. public void addPerson(com.chenyu.service.Person person) throws android.os.RemoteException;
  13. public java.util.List<com.chenyu.service.Person> getPersonList() throws android.os.RemoteException;</span>
  14. }
其中省略了一部分,我們先從大體上認識,然後在深入。

(1)從大體上看,該java檔案是一個介面,繼承了IInterface介面,接著,宣告瞭一個靜態內部抽象類:Stub,然後是兩個方法,可以看到,這兩個方法分別是原IMyAidl.aidl檔案內宣告的兩個方法。

(2)我們看回Stub類,它繼承了Binder,同時實現了IMyAidl。這個類實現了自己的介面!那麼可想而知,該介面所宣告的addPerson,getPersonList方法,將會在Stub類得到實現,具體如何實現,我們展開Stub類:

  1. public static abstract class Stub extends android.os.Binder implements com.chenyu.service.IMyAidl {
  2. private static final java.lang.String DESCRIPTOR = "com.chenyu.service.IMyAidl";
  3. /**
  4. * Construct the stub at attach it to the interface.
  5. */
  6. public Stub() {<span style="white-space:pre"> </span>//①
  7. this.attachInterface(this, DESCRIPTOR);
  8. }
  9. /**
  10. * Cast an IBinder object into an com.chenyu.service.IMyAidl interface,
  11. * generating a proxy if needed.
  12. */
  13. public static com.chenyu.service.IMyAidl asInterface(android.os.IBinder obj) { //②
  14. if ((obj == null)) {
  15. return null;
  16. }
  17. android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
  18. if (((iin != null) && (iin instanceof com.chenyu.service.IMyAidl))) {
  19. return ((com.chenyu.service.IMyAidl) iin);
  20. }
  21. return new com.chenyu.service.IMyAidl.Stub.Proxy(obj);
  22. }
  23. @Override
  24. public android.os.IBinder asBinder() {<span style="white-space:pre"> </span>//③
  25. return this;
  26. }
  27. @Override
  28. public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {<span style="white-space:pre"> </span>//④
  29. switch (code) {
  30. case INTERFACE_TRANSACTION: {
  31. reply.writeString(DESCRIPTOR);
  32. return true;
  33. }
  34. case TRANSACTION_addPerson: {
  35. data.enforceInterface(DESCRIPTOR);
  36. com.chenyu.service.Person _arg0;
  37. if ((0 != data.readInt())) {
  38. _arg0 = com.chenyu.service.Person.CREATOR.createFromParcel(data);
  39. } else {
  40. _arg0 = null;
  41. }
  42. this.addPerson(_arg0);
  43. reply.writeNoException();
  44. return true;
  45. }
  46. case TRANSACTION_getPersonList: {
  47. data.enforceInterface(DESCRIPTOR);
  48. java.util.List<com.chenyu.service.Person> _result = this.getPersonList();
  49. reply.writeNoException();
  50. reply.writeTypedList(_result);
  51. return true;
  52. }
  53. }
  54. return super.onTransact(code, data, reply, flags);
  55. }
  56. private static class Proxy implements com.chenyu.service.IMyAidl {<span style="white-space:pre"> </span>//⑤
  57. ...
  58. }
  59. static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); //⑥
  60. static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
  61. }
(3)從上往下地,我們逐個分析一下各個方法或者變數的作用:

①Stub()構造方法:此方法呼叫了父類Binder的attachInterface()方法,將當前的Interface與Binder聯絡起來,由於傳遞了DESCRIPTOR這個引數,唯一標識了當前Interface。


②asInterface(IBinder obj) :靜態方法,傳遞了一個介面物件,該物件從哪裡傳遞進來的呢?我們來看看上一章部落格的客戶端程式碼:

  1. public void onServiceConnected(ComponentName name, IBinder service) {
  2. Log.d("cylog", "onServiceConnected success");
  3. iMyAidl=IMyAidl.Stub.asInterface(service);
  4. }
在這裡,可以看到,呼叫了IMyAidl.Stub.asInterface(service)方法,即上面的②號方法,並且把service傳遞了進去,我們接著往下看:

  1. public static com.chenyu.service.IMyAidl asInterface(android.os.IBinder obj) {
  2. if ((obj == null)) {
  3. return null;
  4. }
  5. android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
  6. if (((iin != null) && (iin instanceof com.chenyu.service.IMyAidl))) {
  7. return ((com.chenyu.service.IMyAidl) iin);
  8. }
  9. return new com.chenyu.service.IMyAidl.Stub.Proxy(obj);
  10. }
首先判斷obj是否有效,如果無效直接返回Null,說明客戶端與服務端的連線失敗了。接著呼叫了obj.queryLocalInterface(DESCRIPTOR)方法,為IInterface的物件賦值,注意到這裡再次傳遞了DESCRIPTOR引數,可以猜測,這個方法應該是查詢與當前Interface相關的一個方法,我們看看IBinder介面的queryLocalInterface()方法:

  1. /**
  2. * Attempt to retrieve a local implementation of an interface
  3. * for this Binder object. If null is returned, you will need
  4. * to instantiate a proxy class to marshall calls through
  5. * the transact() method.
  6. */
  7. public IInterface queryLocalInterface(String descriptor);
大概意思是說,根據descriptor的值,試圖為Binder取回一個本地的interface,其中local意思應為當前程式,如果返回值是null,那麼應該例項化一個proxy類。在瞭解了obj.queryLocalInterface(DESCRIPTOR)方法後,我們再次回到asInterface(obj)方法,繼續往下看:接著是一個if判斷,主要判斷客戶端與服務端是否處於同一程式,如果處於同一程式,那麼直接返回了Stub物件本身,如果不是同一個程式,那麼就會新建一個Proxy代理類(下面會提到)。


③asBinder():此方法用於返回當前物件本身。

④onTransact(int code,Parcel data,Parcel reply,int flags):該方法一般執行在服務端中的Binder執行緒池中,即遠端請求會在該方法得到處理。傳遞的code值用於判斷客戶端的請求目標,是addPerson或者是getPersonList。我們以請求目標為addPerson()為例分析一下,提取其主要函式體如下:

  1. case TRANSACTION_addPerson: {
  2. data.enforceInterface(DESCRIPTOR);
  3. com.chenyu.service.Person _arg0;
  4. if ((0 != data.readInt())) {
  5. _arg0 = com.chenyu.service.Person.CREATOR.createFromParcel(data);
  6. } else {
  7. _arg0 = null;
  8. }
  9. this.addPerson(_arg0);
  10. reply.writeNoException();
  11. return true;
  12. }
首先宣告瞭_arg0是Person類的物件,接著,以data為引數呼叫了Person類的CREATOR.createFromParcel方法,反序列化生成了Person類,這也是為什麼實現了Parcelable介面的類應該同時實現CREATOR,原來在這裡呼叫了反序列化的方法。接著,呼叫this.addPerson(_arg0)方法,注意:這裡的this代表當前的Binder物件,那麼由Binder呼叫的addPerson(_arg0)方法,實際上是由繫結到Binder的service呼叫的,即服務端呼叫了自身的addPerson方法。為了方便明白,讓我們來回顧一下上一篇文章服務端的程式碼:

  1. private IBinder iBinder= new IMyAidl.Stub() {
  2. @Override
  3. public void addPerson(Person person) throws RemoteException {
  4. persons.add(person);
  5. }
  6. @Override
  7. public List<Person> getPersonList() throws RemoteException {
  8. return persons;
  9. }
  10. };
是不是一下子就明白了?IMyAidl.Stub()實現的介面,其中的方法在服務端得到了實現:addPerson和getPersonList()。當在Binder執行緒池中,呼叫了this.addPerson()方法,實際上回撥了服務端的addPerson方法,而底層到底是怎麼實現的,限於筆者的水平,暫時不瞭解,等以後筆者再深入瞭解Binder的工作機制再回答這個問題。

好了,回到當前的類,我們繼續往下看:
⑤private static class Proxy:這裡又出現了一個私有的靜態內部類,關於這個類將在接下來詳細講述。


⑥最後兩行程式碼分別是兩個常量,標誌了兩個方法,即上面提到的code值。


(4)Proxy類,也實現了IMyAidl介面,同時實現了addPerson和getPersonList的方法。而Proxy類在哪裡被例項化的呢?是上面(3)②中,當客戶端與服務端不在同一個程式的時候,就會例項化這個代理類,並返回給客戶端。什麼叫做代理類呢?所謂代理,即一箇中介,客戶端拿到的例項,能操作服務端的部分功能,讓客戶端以為自己已經拿到了服務端的例項,其實不是,只是拿到服務端的一個代理而已。接下來我們展開該類,看看內部:

  1. private static class Proxy implements com.chenyu.service.IMyAidl {
  2. private android.os.IBinder mRemote;
  3. Proxy(android.os.IBinder remote) {
  4. mRemote = remote;
  5. }
  6. @Override
  7. public android.os.IBinder asBinder() {
  8. return mRemote;
  9. }
  10. public java.lang.String getInterfaceDescriptor() {
  11. return DESCRIPTOR;
  12. }
  13. @Override
  14. public void addPerson(com.chenyu.service.Person person) throws android.os.RemoteException {
  15. android.os.Parcel _data = android.os.Parcel.obtain();
  16. android.os.Parcel _reply = android.os.Parcel.obtain();
  17. try {
  18. _data.writeInterfaceToken(DESCRIPTOR);
  19. if ((person != null)) {
  20. _data.writeInt(1);
  21. person.writeToParcel(_data, 0);
  22. } else {
  23. _data.writeInt(0);
  24. }
  25. mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
  26. _reply.readException();
  27. } finally {
  28. _reply.recycle();
  29. _data.recycle();
  30. }
  31. }
  32. @Override
  33. public java.util.List<com.chenyu.service.Person> getPersonList() throws android.os.RemoteException {
  34. android.os.Parcel _data = android.os.Parcel.obtain();
  35. android.os.Parcel _reply = android.os.Parcel.obtain();
  36. java.util.List<com.chenyu.service.Person> _result;
  37. try {
  38. _data.writeInterfaceToken(DESCRIPTOR);
  39. mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);<span style="white-space:pre"> </span> //①
  40. _reply.readException();
  41. _result = _reply.createTypedArrayList(com.chenyu.service.Person.CREATOR);
  42. } finally {
  43. _reply.recycle();
  44. _data.recycle();
  45. }
  46. return _result;
  47. }
  48. }
這裡關注兩個介面方法的實現:addPerson和getPersonList,這兩個方法已經多次出現了,而在代理類實現的這兩個方法,是執行在客戶端的!!!其主要實現過程是這樣的:當客戶端拿到代理類,呼叫addPerson或者getPersonList方法,首先會建立輸入型Parcel物件_data和輸出型Parcel物件_reply,接著呼叫①號程式碼呼叫transact來發起RPC遠端請求,同時當前執行緒會被掛起,此時,服務端的onTransact會被呼叫,即上面所說的(3)④號程式碼,當服務端處理完請求後,會返回資料,當前執行緒繼續執行知道返回 _result結果。


至此,對於IPC的方式之一——AIDL的原理已經剖析完畢,接下來總結一下:

1、客戶端發出繫結請求,服務端和客戶端繫結在同一個Binder上。客戶端執行asInterface()方法,如果客戶端和服務端處於同一程式,則直接返回服務端的Stub物件本身,如果處於不同程式,則返回的是Stub.proxy代理類物件。

2、客戶端傳送遠端請求(addPerson或者getPersonList),此時客戶端執行緒掛起,Binder拿到資料後,對資料進行處理如在不同程式,會把資料寫入Parcel,呼叫Transact方法。

3、觸發onTransact方法,該方法執行在Binder執行緒池,方法中會呼叫到服務端實現的介面方法,當資料處理完畢後,返回reply值,經過Binder返回客戶端,此時客戶端執行緒被喚醒。


三、優化

最後說一說如何優化AIDL,上面提到,客戶端傳送請求後,會被掛起,這意味著,如果處理資料的時間過長,那麼該執行緒就一直等不到喚醒,這是很嚴重的,如果在UI執行緒傳送請求,會直接導致ANR,所以我們需要在子執行緒傳送非同步請求,這樣才能避免ANR。還有一點,Binder可能是意外死亡的,如果Binder意外死亡,那麼子執行緒可能會一直掛起,所以我們要啟用重新連線服務。有兩個方法,一個是給Binder設定DeathRecipient監聽,另一個是在onServiceDisconnected中重連服務




參考書籍:《Android 開發藝術探索》 任玉剛著,2015年9月第一版

相關文章