1. 程式人生 > >Android : 跟我學Binder --- (2) AIDL分析及手動實現 Android : 跟我學Binder --- (1) 什麼是Binder IPC?為何要使用Binder機制? Android : 跟我學Binder --- (3) 深入程式碼實戰(假裝有連結)

Android : 跟我學Binder --- (2) AIDL分析及手動實現 Android : 跟我學Binder --- (1) 什麼是Binder IPC?為何要使用Binder機制? Android : 跟我學Binder --- (3) 深入程式碼實戰(假裝有連結)

一、關於Android日常開發中程序間通訊-AIDL

  通常Android應用開發中實現程序間通訊用的最多的就是 AIDL,藉助 AIDL 編譯以後的程式碼能幫助我們進一步理解 Binder IPC 的通訊原理。但是無論是從可讀性還是可理解性上來看,編譯器生成的程式碼對開發者並不友好。比如一個 INanoMethod.aidl 檔案對應會生成一個 INanoMethod.java 檔案,這個 java 檔案包含了一個 INanoMethod介面、一個 Stub 靜態的抽象類和一個 Proxy 靜態類。Proxy 是 Stub 的靜態內部類,Stub 又是 INanoMethod的靜態內部類,這就造成了可讀性和可理解性的問題。

  INanoMethod.aidl 內容:

package com.android.NanoServer;
import com.android.NanoServer.INanoMethodCallback;
// Declare any non-default types here with import statements

interface INanoMethod {
    /**
     * 註冊INanoVoiceCallback相關回調介面.
     *
     * @param INanoVoiceCallback 回撥介面
     */
    void registerCallBack( INanoMethodCallback callback);
    
/** * 解除INanoVoiceCallback相關回調介面. * * @param INanoVoiceCallback 回撥介面 */ void unregisterCallBack(INanoMethodCallback callback); /** * 儲存Activity物件. */ void registerActivity(); }

  通過編譯器生產的 INanoMethod.java:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: D:\\work\\Android\\project\\androidStudio\\BLE-GATT-\\aidl\\src\\main\\aidl\\com\\android\\NanoServer\\INanoMethod.aidl
 
*/ package com.android.NanoServer; // Declare any non-default types here with import statements public interface INanoMethod extends android.os.IInterface { /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.android.NanoServer.INanoMethod { private static final java.lang.String DESCRIPTOR = "com.android.NanoServer.INanoMethod"; /** Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.android.NanoServer.INanoMethod interface, * generating a proxy if needed. */ public static com.android.NanoServer.INanoMethod asInterface(android.os.IBinder obj) { if ((obj==null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin!=null)&&(iin instanceof com.android.NanoServer.INanoMethod))) { return ((com.android.NanoServer.INanoMethod)iin); } return new com.android.NanoServer.INanoMethod.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_registerCallBack: { data.enforceInterface(descriptor); com.android.NanoServer.INanoMethodCallback _arg0; _arg0 = com.android.NanoServer.INanoMethodCallback.Stub.asInterface(data.readStrongBinder()); this.registerCallBack(_arg0); reply.writeNoException(); return true; } case TRANSACTION_unregisterCallBack: { data.enforceInterface(descriptor); com.android.NanoServer.INanoMethodCallback _arg0; _arg0 = com.android.NanoServer.INanoMethodCallback.Stub.asInterface(data.readStrongBinder()); this.unregisterCallBack(_arg0); reply.writeNoException(); return true; } case TRANSACTION_registerActivity: { data.enforceInterface(descriptor); this.registerActivity(); reply.writeNoException(); return true; } default: { return super.onTransact(code, data, reply, flags); } } } private static class Proxy implements com.android.NanoServer.INanoMethod { 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; } /** * 註冊INanoVoiceCallback相關回調介面. * * @param INanoVoiceCallback 回撥介面 */ @Override public void registerCallBack(com.android.NanoServer.INanoMethodCallback callback) 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.writeStrongBinder((((callback!=null))?(callback.asBinder()):(null))); mRemote.transact(Stub.TRANSACTION_registerCallBack, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } /** * 解除INanoVoiceCallback相關回調介面. * * @param INanoVoiceCallback 回撥介面 */ @Override public void unregisterCallBack(com.android.NanoServer.INanoMethodCallback callback) 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.writeStrongBinder((((callback!=null))?(callback.asBinder()):(null))); mRemote.transact(Stub.TRANSACTION_unregisterCallBack, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } /** * 儲存Activity物件. */ @Override public void registerActivity() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_registerActivity, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } } static final int TRANSACTION_registerCallBack = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_unregisterCallBack = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); static final int TRANSACTION_registerActivity = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2); } /** * 註冊INanoVoiceCallback相關回調介面. * * @param INanoVoiceCallback 回撥介面 */ public void registerCallBack(com.android.NanoServer.INanoMethodCallback callback) throws android.os.RemoteException; /** * 解除INanoVoiceCallback相關回調介面. * * @param INanoVoiceCallback 回撥介面 */ public void unregisterCallBack(com.android.NanoServer.INanoMethodCallback callback) throws android.os.RemoteException; /** * 儲存Activity物件. */ public void registerActivity() throws android.os.RemoteException; }
  • IBinder : IBinder 是一個介面,代表了一種跨程序通訊的能力。只要實現了這個藉口,這個物件就能跨程序傳輸。

  • IInterface : IInterface 代表的就是 Server 程序物件具備什麼樣的能力(能提供哪些方法,其實對應的就是 AIDL 檔案中定義的介面)

  • Binder : Java 層的 Binder 類,代表的其實就是 Binder 本地物件。BinderProxy 類是 Binder 類的一個內部類,它代表遠端程序的 Binder 物件的本地代理;這兩個類都繼承自 IBinder, 因而都具有跨程序傳輸的能力;實際上,在跨越程序的時候,Binder 驅動會自動完成這兩個物件的轉換。

  • Stub : 編寫AIDL 的時候,編譯工具會生成一個名為 Stub 的靜態內部類,這個類繼承了 Binder, 說明它是一個 Binder 本地物件,它實現了 IInterface 介面,表明它具有 Server 承諾給 Client 的能力,Stub 是一個抽象類,具體的 IInterface 的相關實現需要開發者自己實現。

雖然難於閱讀,但Android 之所以這樣設計其實是有道理的,因為當有多個 AIDL 檔案的時候把 INanoMethod、Stub、Proxy 放在同一個檔案裡能有效避免 Stub 和 Proxy 重名的問題。

 

二、手動編寫程式碼來實現跨程序呼叫

   一次跨程序通訊必然會涉及到兩個程序,在這個例子中 BookManagerService 作為服務端程序,提供服務;ClientActivity 作為客戶端程序,使用 BookManagerService提供的服務。那麼服務端程序具備什麼樣的能力?能為客戶端提供什麼樣的服務呢?前面介紹過的 IInterface 代表的就是服務端程序具體什麼樣的能力,即提供了什麼功能的介面。因此需要定義一個 BookManager 介面,BookManager 繼承自 IInterface ,表明服務端具備什麼樣的能力:

/**
 * 這個類用來定義服務端 BookManagerService 具備什麼樣的能力
 */
public interface BookManager extends IInterface {

    void addBook(Book book) throws RemoteException; //可以新增書籍
}

  只定義服務端具備什麼要的能力是不夠的,既然是跨程序呼叫,那需要實現一個跨程序呼叫物件 Stub。Stub 繼承 Binder, 說明它是一個 Binder 本地物件;實現 IInterface 介面,表明具有 Server 承諾給 Client 的能力;Stub 是一個抽象類,具體的 IInterface 的相關實現需要呼叫方自己實現。

public abstract class Stub extends Binder implements BookManager {

    ...
    
    public static BookManager asInterface(IBinder binder) {
        if (binder == null)
            return null;
        IInterface iin = binder.queryLocalInterface(DESCRIPTOR);
        if (iin != null && iin instanceof BookManager)
            return (BookManager) iin;
        return new Proxy(binder);
    }

    ...

    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (code) {

            case INTERFACE_TRANSACTION:
                reply.writeString(DESCRIPTOR);
                return true;

            case TRANSAVTION_addBook:
                data.enforceInterface(DESCRIPTOR);
                Book arg0 = null;
                if (data.readInt() != 0) {
                    arg0 = Book.CREATOR.createFromParcel(data);
                }
                this.addBook(arg0);
                reply.writeNoException();
                return true;

        }
        return super.onTransact(code, data, reply, flags);
    }
... }

  接下來就要實現這個代理類 Proxy 了,既然是代理類自然需要實現 BookManager 介面:

public class Proxy implements BookManager {
    
    ...

    public Proxy(IBinder remote) {
        this.remote = remote;
    }

    @Override
    public void addBook(Book book) throws RemoteException {

        Parcel data = Parcel.obtain();
        Parcel replay = Parcel.obtain();
        try {
            data.writeInterfaceToken(DESCRIPTOR);
            if (book != null) {
                data.writeInt(1);
                book.writeToParcel(data, 0);
            } else {
                data.writeInt(0);
            }
            remote.transact(Stub.TRANSAVTION_addBook, data, replay, 0);
            replay.readException();
        } finally {
            replay.recycle();
            data.recycle();
        }
    }

    ...
}

 

  關於 Stub 類中重點介紹下 asInterface 和 onTransact

  • asInterface : 當 Client 端在建立和服務端的連線,呼叫 bindService 時需要建立一個 ServiceConnection 物件作為入參。在 ServiceConnection 的回撥方法 onServiceConnected 中 會通過這個 asInterface(IBinder binder) 拿到 BookManager 物件,這個 IBinder 型別的入參 binder 是驅動傳給我們的,正如在程式碼中看到的一樣,方法中會去呼叫 binder.queryLocalInterface() 去查詢 Binder 本地物件,如果找到了就說明 Client 和 Server 在同一程序,那麼這個 binder 本身就是 Binder 本地物件,可以直接使用。否則說明是 binder 是個遠端物件,也就是 BinderProxy。因此需要建立一個代理物件 Proxy,通過這個代理物件來是實現遠端訪問。
  • onTransact : 在 Proxy 中的 addBook() 方法中首先通過 Parcel 將資料序列化,然後呼叫 remote.transact()。正如前文所述 Proxy 是在 Stub 的 asInterface 中建立,能走到建立 Proxy 這一步就說明 Proxy 建構函式的入參是 BinderProxy,即這裡的 remote 是個 BinderProxy 物件。最終通過一系列的函式呼叫,Client 程序通過系統呼叫陷入核心態,Client 程序中執行 addBook() 的執行緒掛起等待返回;驅動完成一系列的操作之後喚醒 Server 程序,呼叫 Server 程序本地物件的 onTransact()。最終又走到了 Stub 中的 onTransact() 中,onTransact() 根據函式編號呼叫相關函式(在 Stub 類中為 BookManager 介面中的每個函式中定義了一個編號,只不過上面的原始碼中簡化掉了,這裡要提及一下,在跨程序呼叫的時候不會傳遞函式而是傳遞編號來指明要呼叫哪個函式,如果提供給client端的aidl檔案介面數量或者順序和server端的宣告不一樣,則會導致函式呼叫錯位的問題。

  在client端繫結服務及使用介面:

  /**
     * 嘗試與服務端建立連線
     */
    private void attemptToBindService() {
        Intent intent = new Intent();
        intent.setAction("com.android.aidl.server");
        intent.setPackage("com.android.NanoServer");
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            BookManagerService = BookManager.Stub.asInterface(service);
            if (BookManagerService != null) {
                mBound = true;
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mBound = false;
        }
    };

......
//成功繫結服務後便可呼叫服務端提供的介面
try {
    if (BookManagerService != null) {
    BookManagerService.addBook(book);
    }
} catch (RemoteException e) {
    e.printStackTrace();
}
  • 如果 Client 和 Server 在同一個程序,那麼直接就是呼叫這個方法。
  • 如果是遠端呼叫,Client 想要呼叫 Server 的方法就需要通過 Binder 代理來完成,也就是上面的 Proxy。

 

上一篇:Android : 跟我學Binder --- (1) 什麼是Binder IPC?為何要使用Binder機制?

下一篇:Android : 跟我學Binder --- (3) 深入程式碼實戰(假裝有連結)