Binder Java層的實現原理分析

Jk_maker發表於2019-01-26

Binder Java層實現原理

近日在做元件化方案時,複習了一遍Bidner機制,在熟悉了一遍Bidner機制後,對程式間通訊以及Android設計模式原來有了較深的感悟。 Android Binder 是一個及其深入的話題,從Linux間程式通訊的方式,到Android間通訊方式都需要了解,下圖是binder大致實現(看完我就暈了)。本文不通過複雜的程式碼細節,以及底層程式碼分析Binder實現方式。而是通過相對好理解的AIDL(也是大眾認為的最好理解binder的方式)來熟悉binder。對AIDL不瞭解的朋友,可先行看下三步掌握Android中的AIDL來大致瞭解一下期使用方式。

複雜的binder

Binder介紹

這裡我們不講述上面複雜的實現。而是通過AIDL介紹binder的上層實現原理。Binder是Android中的一個類,它繼承了IBinder介面。

  • 從IPC角度來說,Binder是Android中的一種跨程式通訊方式。
  • Binder還可以理解為一種虛擬的物理裝置,它的裝置驅動是/dev/binder,該通訊方式在Linux中沒有
  • 從Android Framework角度來說,Binder是ServiceManager連線各種 Manager(ActivityManager、WindowManager,等等)和相應ManagerService的橋樑;
  • 從Android應用層來說,Binder是客戶端和服務端進行通訊的媒介,當bindService的時候,服務端會返回一個包含了服務端業務呼叫的Binder物件,通過這個Binder物件,客戶端就可以獲取服務端提供的服務或者資料,這裡的服務包括普通服務和基於AIDL的服務。

下面是通過Aidl實現程式間通訊的簡化圖,您可以不記住,這裡有個印象就好。

Binder Java層的實現原理分析

建立AIDL

我們先建立三個檔案分別為Market.java、 Market.aidl、ImarketManger.aidl檔案 程式碼如下:

Market.java

public class Market implements Parcelable {
    private int goodsId;
    private String goodsName;

    public Market(int goodsId, String goodsName) {
        this.goodsId = goodsId;
        this.goodsName = goodsName;
    }


    protected Market(Parcel in) {
        goodsId = in.readInt();
        goodsName = in.readString();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(goodsId);
        dest.writeString(goodsName);
    }

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

    public static final Creator<Market> CREATOR = new Creator<Market>() {
        @Override
        public Market createFromParcel(Parcel in) {
            return new Market(in);
        }

        @Override
        public Market[] newArray(int size) {
            return new Market[size];
        }
    };
}
複製程式碼

Market.aidl

// Market.aidl
package com.ccdt.itvision.demo;

parcelable Market;
複製程式碼

ImarketManger.aidl

// IMarketManger.aidl
package com.ccdt.itvision.demo;
import com.ccdt.itvision.demo.Market;
// Declare any non-default types here with import statements

interface IMarketManger {

   List<Market> getGoodList();

   void addGoods(in Market market);
}
複製程式碼

上面三個檔案中,Market表示一個超市食物類,它實現了Parcelable介面。Market.aidl是Market類在AIDL中的宣告。IMarketManger.aidl是我們自己定義的一個介面。裡面有兩個方法。然後我們rebuild project會在 app/build/generated/source/com.xxx.xxx/ 看見ImarketManger.java檔案這個檔案就是我們研究的重中之重!它的全部程式碼如下:

public interface IMarketManger extends android.os.IInterface {
/**
 * Local-side IPC implementation stub class.
 */
public static abstract class Stub extends android.os.Binder implements com.ccdt.itvision.viewlearning.IMarketManger {
    private static final java.lang.String DESCRIPTOR = "com.ccdt.itvision.demo.IMarketManger";

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

    /**
     * Cast an IBinder object into an com.ccdt.itvision.viewlearning.IMarketManger interface,
     * generating a proxy if needed.
     */
    public static com.ccdt.itvision.viewlearning.IMarketManger asInterface(android.os.IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof com.ccdt.itvision.viewlearning.IMarketManger))) {
            return ((com.ccdt.itvision.viewlearning.IMarketManger) iin);
        }
        return new com.ccdt.itvision.viewlearning.IMarketManger.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 {
        switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(DESCRIPTOR);
                return true;
            }
            case TRANSACTION_getGoodList: {
                data.enforceInterface(DESCRIPTOR);
                java.util.List<com.ccdt.itvision.viewlearning.Market> _result = this.getGoodList();
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
            }
            case TRANSACTION_addGoods: {
                data.enforceInterface(DESCRIPTOR);
                com.ccdt.itvision.viewlearning.Market _arg0;
                if ((0 != data.readInt())) {
                    _arg0 = com.ccdt.itvision.demo.Market.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                this.addGoods(_arg0);
                reply.writeNoException();
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }

    private static class Proxy implements com.ccdt.itvision.viewlearning.IMarketManger {
        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.ccdt.itvision.viewlearning.Market> getGoodList() throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            java.util.List<com.ccdt.itvision.viewlearning.Market> _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                mRemote.transact(Stub.TRANSACTION_getGoodList, _data, _reply, 0);
                _reply.readException();
                _result = _reply.createTypedArrayList(com.ccdt.itvision.viewlearning.Market.CREATOR);
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }

        @Override
        public void addGoods(com.ccdt.itvision.viewlearning.Market market) 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 ((market != null)) {
                    _data.writeInt(1);
                    market.writeToParcel(_data, 0);
                } else {
                    _data.writeInt(0);
                }
                mRemote.transact(Stub.TRANSACTION_addGoods, _data, _reply, 0);
                _reply.readException();
            } finally {
                _reply.recycle();
                _data.recycle();
            }
        }
    }

    static final int TRANSACTION_getGoodList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_addGoods = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}

public java.util.List<com.ccdt.itvision.viewlearning.Market> getGoodList() throws android.os.RemoteException;

public void addGoods(com.ccdt.itvision.viewlearning.Market market) throws android.os.RemoteException;
}
複製程式碼

看起來很複雜,但其實這個類邏輯還是蠻清晰。我們縮排下程式碼在看。

Binder Java層的實現原理分析
首先這裡宣告瞭兩個方法,getGoodList()addGoods() 。這兩個方法顯然是我們在ImarketManger中宣告的。然後是一個靜態的內部類Stub。stub繼承自Binder,顯然他自己就是一個Binder類。當客戶端和服務端都位於同一個程式時,方法呼叫不會走跨程式的transact過程,而當兩者位於不同程式時,方法呼叫需要走transact過程,這個邏輯由Stub的內部代理類Proxy來完成。所以這裡的核心就是Stub的內部代理Proxy。Proxy內部方法如下:

  • asInterface(android.os.IBinder obj)

用於將服務端的Binder物件轉換成客戶端所需的AIDL介面型別的物件,這種轉換過程是區分程式的,如果客戶端和服務端位於同一程式,那麼此方法返回的就是服務端的Stub物件本身,否則返回的是系統封裝後的Stub.proxy物件。

  • asBinder

此方法用於返回當前Binder物件

  • onTransact

此方法執行在服務端中的Binder執行緒池中,當客戶端通過aidl發起跨程式請求時,遠端請求會通過系統底層封裝後交由此方法來處理。服務端通過code可以確定客戶端所請求的方法是什麼,接著從data中取出該方法所需的引數(沒引數則不取),然後執行該方法。當該方法執行完畢後,就向reply中寫入返回值,這就是onTransact的執行過程。值得注意的是,如果此方法返回false,那麼客戶端的請求會失敗

  • Proxy#getGoodsList

首先建立該方法所需要的輸入型Parcel物件_data、輸出型Parcel物件_reply。 宣告返回值物件List。 _data.writeInterfaceToken(DESCRIPTOR); 將該方法所需引數資訊寫入data 中。 接著呼叫transact方法來發起RPC(遠端過程呼叫)請求,當前執行緒hang on(掛起),當服務端 onTransact(剛介紹完的方法,回頭去看一看)執行完畢後。從_reply中取出RPC過程的返回結果處理完畢。

通過以上分析,下面我們在回頭看剛剛的簡圖是不是就明朗了許多?

手寫一個AIDL的實現類

從上述分析過程來看,我們完全可以不提供AIDL檔案即可實現Binder,之所以提供AIDL檔案,是為了方便系統為我們生成程式碼。系統根據AIDL檔案生成Java檔案的格式是固定的,我們可以不用AIDL檔案直接寫一個Binder出來。

首先宣告一個介面繼承 IInterface 程式碼如下:

public interface IMarketManger extends IInterface {
    static final String DESCRIPTOR = "com.ccdt.itvision.demo.custombinder.IMarketManger";
    static final int TRANSACTION_getGoodList = IBinder.FIRST_CALL_TRANSACTION + 0;
    static final int TRANSACTION_addGood = IBinder.FIRST_CALL_TRANSACTION+1;
    public List<Market> getGoodList() throws RemoteException;
    public void addGoods(Market market) throws RemoteException;
}
複製程式碼

接著,建立ImarketManger的實現類,實現思路與AIDL生成的程式碼類似。如果服務端使用手寫Binder,只需要在onBinder的時候返回ImarketManger的實現類就好。程式碼如下:

public class MarketImpl extends Binder implements IMarketManger {

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

public static IMarketManger asInterface(IBinder obj) {
    if (obj == null) {
        return null;
    }
    IInterface iInterface = obj.queryLocalInterface(DESCRIPTOR);
    if (iInterface != null && iInterface instanceof IMarketManger) {

        return (IMarketManger) iInterface;
    }
    return new MarketImpl.Proxy(obj);
}

@Override
public List<Market> getGoodList() throws RemoteException {
    return null;
}

@Override
public void addGoods(Market good) throws RemoteException {

}

@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:
            reply.writeString(DESCRIPTOR);
            return true;
            
        case TRANSACTION_getGoodList:
            data.enforceInterface(DESCRIPTOR);
            List<Market> goodList = this.getGoodList();
            reply.writeNoException();
            reply.writeTypedList(goodList);
            return true;

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

}


private static class Proxy implements IMarketManger {
    private IBinder mRemote;

    public Proxy(IBinder obj) {
        mRemote = obj;
    }

    @Override
    public List<Market> getGoodList() throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        List<Market> result;
        try {
            data.writeInterfaceToken(DESCRIPTOR);
            mRemote.transact(TRANSACTION_getGoodList, data, reply, 0);
            reply.readException();
            result = reply.createTypedArrayList(Market.CREATOR);
        } finally {
            reply.recycle();
            data.recycle();
        }
        return result;
    }

    @Override
    public void addGoods(Market market) throws RemoteException {
        // TODO: 與aidl中一樣
    }


    @Override
    public IBinder asBinder() {
        return mRemote;
    }
}
複製程式碼

Aidl只是Google為我們實現Binder 實現的一個簡便方式生成模板化程式碼,事實上我們完全可以這樣手寫一個aidl,這有利於我們理解Bidner的實現方式。另外Binder還有個重要的方法,Linktodeath() 因服務端的情況我們未知,可能出現異常情況,當binder 死亡時,我們可以通過binder死亡代理 知道連結已經斷開,重新繫結連結。

最後Binder的實現機制看似複雜,但是其中很有條理,如果你想使用元件化方案,瞭解android底層原理,做framwork層程式碼,binder 使我們繞不開的話題,所以先在Aidl開始瞭解並深入,對您的成長是非常有意義的!

相關文章