五分鐘讀懂Android中的Binder跨進程機制和AIDL工具

前言

做Android開發,邁入高級必須會的技術很多,而且肯定包括Binder技術,關於Binder的瞭解,筆者最開始只是使用AIDL工具開發接口,生成Service的服務方法,對於其內部的實現細節不太瞭解。從書本上或者搜索看了好幾遍,每次都是似懂非懂,然後日子久了,連那點印象也不見了。所以最近的這次綜合瞭解了這個技術後,決定將自己的理解記錄下來,一方面鞏固理解,一方面分享給需要的朋友。

兩個對象能直接互相訪問的前提是這兩個對象都存在於相同的內存地址空間中,如果兩個對象位於兩個不同進程,則不能直接互相調用。Binder 是Android中的一種跨進程通信方式,即IPC(Inter-process Communication)一種。首先,我們學習開發最開始知道的知識點,線程是CPU的最小調度單位;進程是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,進程可以包含多個線程。理論是這麼說,從客觀認識上,一個普通的app就是一個進程,很明顯兩個app之間不能夠直接通信,如果需要,那就要用到IPC的各種方式,這裏只講binder,或者說Android中的Service,即Android四大組件之一,Binder主要用在service中。

Binder主要模塊

Binder框架圖1, Binder框架
Binder主要分爲Binder服務端、Binder客戶端和Binder驅動。
首先Binder服務端,可以看做是一個Binder類的對象,該對象創建後,會接收Binder驅動發送的消息,即在收到消息的回調中處理,執行服務代碼。這個回調即是onTransact()方法。
從翻譯來看,transact是辦理、處理的意思,這裏就可以認爲,binder驅動這個中介把客戶端的需求發到了服務端,服務端即可以根據回調信息進行處理。那既然有收,肯定客戶端要發數據,服務端才能收到。
在這裏插入圖片描述
最後看Binder客戶端,客戶端要訪問遠程服務,調用服務接口,那麼就必須獲取遠程服務在Binder對象中對應的mRemote引用,或者可以直接認爲獲取Binder。然後調用其transact()方法,將客戶端要調用的函數和參數傳遞到服務端,當然是經由Binder驅動中介傳到服務端。
由於Binder驅動位於內核空間,且較底層,所以我們不用太深入瞭解其原理,只需把它看做一個黑盒,我們只看接口輸入輸出,作爲和服務端傳遞信息的信使就可以,等學有餘力我們繼續深挖。這裏可以有個大概的認識,其爲字符型設備,用戶可以從/dev/binder設備文件節點上通過open和ioctl文件操作函數與Binder driver通信,其主要負責Binder通信機制的建立以及在進程間傳遞和Binder引用計數管理、數據包傳輸等。

Demo示例

這裏以一個aidl的demo爲示例,其鏈接爲binderdemo,其中此project中的app module爲binder的server端,有MediaService作爲服務器,添加的AIDL接口。然後binderclient module爲binder客戶端。

// IMediaService.aidl
package com.example.binderserver;
import com.example.binderserver.MediaInfo;

// Declare any non-default types here with import statements
interface IMediaService{
void startplay(in MediaInfo info);
void stop();
void search(String name);
}

其中的MediaInfo的aidl源碼爲:

// MediaInfo.aidl
package com.example.binderserver;

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

parcelable MediaInfo;

AIDL中支持的數據類型有:
1.基本數據類型:int、long、char、boolean、double等;
2.String和CharSequence;
3.ArrayList,且其每個元素都必須是支持的數據類型;
4.HashMap,且其每個元素都是支持的數據類型;
5.Parcelable,即所有實現了parcelable接口的對象;
6.AIDL,即所有AIDL接口本身。
所以根據第5條,MediaInfo非其他AIDL支持的類型,自定義類必須繼承parcelable。
而parcelable是Android序列化中最重要的方式,在跨進程通信中很重要,總之,就是重要。 想對其有深入瞭解的讀者可以參考我的另一篇文章:一文看懂Android中的序列化
接下來我們繼續說服務端和客戶端,爲了讀者更快了解框架全貌,這裏先把代碼附錄上。
服務端的代碼如下,

   public class MediaService extends Service {
    private static final String TAG = "MediaService";

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return (IBinder) mBinder;
    }

    private IMediaService mBinder = new IMediaService.Stub() {
        @Override
        public void startplay(MediaInfo info) throws RemoteException {
            Log.d(TAG, "startplay mediaInfo =" + info.toString());
        }

        @Override
        public void stop() throws RemoteException {
            Log.d(TAG, "service stop  ");
        }

        @Override
        public void search(String name) throws RemoteException {
            Log.d(TAG, "service search name=" + name);
        }
    };
}

客戶端的代碼如下:

public class MainActivity extends Activity {
    private static final String TAG = "binderclient";
    ServiceConnection conn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        conn = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
                Log.d(TAG, "onServiceConnected");
                IMediaService binder = IMediaService.Stub.asInterface(iBinder);
                try {
                    Log.d(TAG, "onServiceConnected search invoke");
                    binder.search("忘情水");
                    Log.d(TAG, "onServiceConnected startplay invoke");
                    binder.startplay(new MediaInfo("夜空中最閃亮的星", "xxx", "逃跑計劃"));
                    Log.d(TAG, "onServiceConnected stop invoke");
                    binder.stop();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }

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

        Intent intent = new Intent();
        intent.setAction("example.bindertest");
        intent.setPackage("com.example.binderserver");
        bindService(intent, conn, BIND_AUTO_CREATE);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unbindService(conn);
    }

}

源碼都已附上,然後由於AIDL是Android提供的Binder工具,並不是真正的源碼,如您下載了github源碼並編譯成功,則會在app/build/generated/aidl_source_output_dir/debug/compileDebugAidl/out/com/example/binderserver目錄發現IMediaService.java源碼,public interface IMediaService extends android.os.IInterface,可以看出AIDL的方法是interface的類,編譯生成的自然也是interface類, 由於其繼承IInterface,由於這是個接口類
public interface IInterface {
IBinder asBinder();
}
所以也需要實現其asBinder方法。

/*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package com.example.binderserver;
// Declare any non-default types here with import statements

public interface IMediaService extends android.os.IInterface
{
  /** Default implementation for IMediaService. */
  public static class Default implements com.example.binderserver.IMediaService
  {
    @Override public void startplay(com.example.binderserver.MediaInfo info) throws android.os.RemoteException
    {
    }
    @Override public void stop() throws android.os.RemoteException
    {
    }
    @Override public void search(java.lang.String name) throws android.os.RemoteException
    {
    }
    @Override
    public android.os.IBinder asBinder() {
      return null;
    }
  }
  /** Local-side IPC implementation stub class. */
  public static abstract class Stub extends android.os.Binder implements com.example.binderserver.IMediaService
  {
    private static final java.lang.String DESCRIPTOR = "com.example.binderserver.IMediaService";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.example.binderserver.IMediaService interface,
     * generating a proxy if needed.
     */
    public static com.example.binderserver.IMediaService asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.example.binderserver.IMediaService))) {
        return ((com.example.binderserver.IMediaService)iin);
      }
      return new com.example.binderserver.IMediaService.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_startplay:
        {
          data.enforceInterface(descriptor);
          com.example.binderserver.MediaInfo _arg0;
          if ((0!=data.readInt())) {
            _arg0 = com.example.binderserver.MediaInfo.CREATOR.createFromParcel(data);
          }
          else {
            _arg0 = null;
          }
          this.startplay(_arg0);
          reply.writeNoException();
          return true;
        }
        case TRANSACTION_stop:
        {
          data.enforceInterface(descriptor);
          this.stop();
          reply.writeNoException();
          return true;
        }
        case TRANSACTION_search:
        {
          data.enforceInterface(descriptor);
          java.lang.String _arg0;
          _arg0 = data.readString();
          this.search(_arg0);
          reply.writeNoException();
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements com.example.binderserver.IMediaService
    {
      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 void startplay(com.example.binderserver.MediaInfo info) 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 ((info!=null)) {
            _data.writeInt(1);
            info.writeToParcel(_data, 0);
          }
          else {
            _data.writeInt(0);
          }
          boolean _status = mRemote.transact(Stub.TRANSACTION_startplay, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().startplay(info);
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      @Override public void stop() throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          boolean _status = mRemote.transact(Stub.TRANSACTION_stop, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().stop();
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      @Override public void search(java.lang.String name) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeString(name);
          boolean _status = mRemote.transact(Stub.TRANSACTION_search, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().search(name);
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      public static com.example.binderserver.IMediaService sDefaultImpl;
    }
    static final int TRANSACTION_startplay = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_stop = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    static final int TRANSACTION_search = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
    public static boolean setDefaultImpl(com.example.binderserver.IMediaService impl) {
      if (Stub.Proxy.sDefaultImpl == null && impl != null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.example.binderserver.IMediaService getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  public void startplay(com.example.binderserver.MediaInfo info) throws android.os.RemoteException;
  public void stop() throws android.os.RemoteException;
  public void search(java.lang.String name) throws android.os.RemoteException;
}

方法含義

劃重點啦,要理解Binder及AIDL使用,以下的函數定義必須牢記,就像知識字典,如果看源碼有不懂的就來查下字典,如果牢記了以下幾點,相信你肯定對Binder有了基本的認識。

  1. DESCRIPTOR: Binder的唯一標識,一般用當前Binder的類名錶示。這裏即“com.example.binderserver.IMediaService”
  2. Stub:是一個abstract類,即抽象類,繼承了Binder類,並實現IMediaService接口,主要在服務端使用。既然繼承了Binder,我們就可以把Stub看做Binder。定義爲抽象類是因爲具體的服務函數需要由程序員實現。看demo編譯生成的IMediaService源碼,public static abstract class Stub extends android.os.Binder implements com.example.binderserver.IMediaService由於IMediaService是接口類,所以Stub類implement了IMediaService類就需要實現其接口, 服務端需要程序員手動去實現這些接口。
 public void startplay(com.example.binderserver.MediaInfo info) throws android.os.RemoteException;
  public void stop() throws android.os.RemoteException;
  public void search(java.lang.String name) throws android.os.RemoteException;

然後Stub類內部重載onTransact方法。由於繼承的Binder類,而Binder類public class Binder implements IBinder又繼承接口public interface IBinder,所以服務端函數Stub即需要實現Binder接口onTransact和其中帶過來的請求,即如下第5條。這裏onTransact處理Binder傳過來的客戶端請求後最後又調用的即是IMediaService的接口函數,即需要程序員實現的部分,最後返回。
4. asInterface(android.os.IBinder obj):將服務端的Binder對象轉換爲客戶端需要的AIDL接口類型的對象,此轉換區分進程,如果客戶端和服務端位於同一進程,則此方法返回服務端對象本身即不需要經過IPC調用,否則返回系統封裝的Stub.proxy對象。可以看到Stub類的asInterface的源碼中,有 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);這個用來判斷是否是本地接口,即本進程的方法,接下來判斷iin如果不爲空且是IMediaService的實例時,直接返回。否則調用Stub的Proxy方法。如下圖所示。

/**
     * Cast an IBinder object into an com.example.binderserver.IMediaService interface,
     * generating a proxy if needed.
     */
    public static com.example.binderserver.IMediaService asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.example.binderserver.IMediaService))) {
        return ((com.example.binderserver.IMediaService)iin);
      }
      return new com.example.binderserver.IMediaService.Stub.Proxy(obj);
    }
  1. asBinder:返回當前的Binder對象。
  2. onTransact:運行在服務端Binder線程池,客戶端的數據請求通過系統底層封裝後回調到此方法來處理,其中public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)參數code表示客戶端調用方法值,data中是客戶端調用方法的參數,reply中是客戶端調用方法的返回值。 由於data中包裹的數據是在客戶端的transact方法寫入,其順序由AIDL工具定義,自然在onTransact中,AIDL工具也根據定義的數據包裹順序解出來進行處理。
  3. enforceInterface:爲了某種校驗,與客戶端Proxy中的writeInterfaceToken對應。
  4. Proxy:運行在客戶端,作爲客戶端訪問服務端的代理,客戶端在對應的接口函數內創建Parcel對象_data和_reply,想要深入瞭解下parcel也可以查看我的另一篇關於序列化的解讀,都是類似的。一文看懂Android中的序列化,序列化中封裝的參數信息也是按順序約定的。
		android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();

並將要調用的方法所需的參數信息寫入_data,然後通過Binder的遠程調用mRemote封裝調用方法的code和_data,_reply,(_data是要傳遞給遠程Binder服務的包裹(Parcel),其中放入code對應方法所需參數,然後)發起跨進程調用;同時客戶端的當前線程掛起,等待服務端的onTransact方法調用結束後,把執行結果放入_reply中,然後服務端向Binder驅動發送notify消息,從而客戶端線程能從Binder驅動中返回到繼續執行客戶端代碼。
然後這裏的方法code,用於標識客戶端想調用服務端的哪個函數,即雙方約定好一組int值,不同的int值代表不同的服務端函數。客戶端transact的code和服務端的onTransact的code是對應的。

static final int TRANSACTION_startplay = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_stop = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_search = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
Binder框架描述

測試結果

讀者可以根據需要下載demo源碼自己測試,地址爲binderdemo,根據上述章節的講解,讀者也可以測試在同一進程中看是否
客戶端的執行結果打印

2020-02-06 16:59:39.827 7447-7447/com.example.binderclient D/binderclient: onServiceConnected
2020-02-06 16:59:39.828 7447-7447/com.example.binderclient D/binderclient: onServiceConnected search invoke
2020-02-06 16:59:39.829 7447-7447/com.example.binderclient D/binderclient: onServiceConnected startplay invoke
2020-02-06 16:59:39.829 7447-7447/com.example.binderclient D/binderclient: onServiceConnected stop invoke

同時服務端的執行結果:

2020-02-06 16:59:39.828 6519-6532/com.example.binderserver D/MediaService: service search name=忘情水
2020-02-06 16:59:39.829 6519-6531/com.example.binderserver D/MediaService: startplay mediaInfo =com.example.binderserver.MediaInfo@54956ae
2020-02-06 16:59:39.829 6519-7005/com.example.binderserver D/MediaService: service stop  

結語

本文結束了,希望大家能對Binder和AIDL有個不錯的瞭解,如果使用過AIDL的讀者應該更加加深了印像,但是建議收藏下,過一段時間複習下,否則不常用時又會遺忘。對於初學者,如果不能很快的理解,可以先記憶住上述章節的方法含義部分,然後加以demo練習,如不能記住,容易撿了西瓜丟了芝麻,做到胸中有丘壑。這樣在練習或者實操時按圖索驥就理解了。
當然本文只簡單的通過例子解析了其中的部分方法和原理,Android中還有其他例子可講,比如Android系統的ServiceManager,其是系統所有原生Service的總管家,它也是個BinderServer,想繼續深入的同學可以繼續研究下其源碼,想查看系統源碼的可以參考:如何查看Android系統源碼
喜歡或者對你有幫助的同學幫忙點個贊吧,文中參考了書中的知識和自己的理解,如有錯誤,歡迎留言指正交流。

參考文獻:
《Android開發藝術探索》
《Android內核剖析》
《Android源碼設計模式解析與實戰》

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