Binder 驅動詳解(上)

SharryChoo發表於2019-01-31

前言

我們知道 Binder 驅動主要用於跨程式的通訊, 它的身影出現在 Androrid 系統方方面面, 是 Android 系統框架中非常重要, 又非常難懂以搞懂的一部分
關於 Binder 驅動, 以前看別人分享文章的時候, 很多都會在前面寫上 "關於 Binder 驅動相關的知識, 我有些難以下筆", 今天產生整理這篇文章的時候, 筆者忽然之間感覺, 前輩們說的真是太有道理了, 忽然之間, 我也有些難以下筆, 因為它覆蓋面太過於龐大了, 牽扯到了 應用開發層, Java 應用框架層, 執行時庫層, HAL 層, 以及 Linux 核心層, 想要將之闡述清楚, 可真是不是一篇文章能夠搞定的, 不過這裡還是嘗試寫一寫

本文主要從以下幾個方面闡述

  • Java 層通訊例項
  • Binder 代理物件的建立
  • Binder 實體物件的建立

一. Java 層的通訊例項

熟悉 Android 開發的我們都知道, 在 Android 專案中進行跨程式通訊可以使用 AIDL 介面定義語言, 快捷生成相應的程式碼進行跨程式通訊, 這裡為了弄清楚程式碼生成背後故事, 我們手動實現一下

服務提供介面

/**
 * 定義一個服務的提供的功能介面
 *
 * @author Sharry <a href="SharryChooCHN@Gmail.com">Contact me.</a>
 * @version 1.0
 * @since 2018/9/29 16:29
 */
public interface IRemoteService extends IInterface {

    /*
      跨程式提供服務的介面描述
     */
    String DESCRIPTOR = IRemoteService.class.getName();
    /*
      跨程式提供服務的介面中 getServiceName 這個方法 Transaction id.
     */
    int TRANSACTION_getServiceName = (IBinder.FIRST_CALL_TRANSACTION + 0);

    String getServiceName() throws RemoteException;

}
複製程式碼

服務實現類 和 Binder 實體物件

/**
 * 介面 IRemoteService 在服務端的實現類
 * {@link #mBinder } 這個物件是讓 IRemoteService 擁有跨程式提供資料能力的 Binder 本地實現類
 *
 * @author Sharry <a href="SharryChooCHN@Gmail.com">Contact me.</a>
 * @version 1.0
 * @since 2018/9/29 17:48
 */
public class RemoteServiceImpl implements IRemoteService {

    private IBinder mBinder;

    public RemoteServiceImpl(IBinder binder) {
        this.mBinder = binder;
    }

    @Override
    public String getServiceName() {
        return "This this RemoteService support function.";
    }

    @Override
    public IBinder asBinder() {
        return mBinder;
    }

}

/**
 * Service 端 Binder 實體物件實現類
 * 其向外提供的功能介面為 {@link IRemoteService}
 *
 * @author Sharry <a href="SharryChooCHN@Gmail.com">Contact me.</a>
 * @version 1.0
 * @since 2018/9/28 20:50
 */
public class RemoteServiceBinder extends Binder {

    /*
      持有其對應介面實現類的引用
     */
    private IRemoteService mImpl;

    public RemoteServiceBinder() {
        mImpl = new RemoteServiceImpl(this);
        // 呼叫了 attachInterface 之後, 父類 Binder 將會持有當前 IInterface 介面的描述
        // 在 onTransact 中 會自動處理 INTERFACE_TRANSACTION 型別的事務
        // 在 queryLocalInterface 中可以找到本地介面
        this.attachInterface(mImpl, mImpl.DESCRIPTOR);
    }

    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (code) {
            // 呼叫了 attachInterface 之後, 父類會處理該型別的 code.
//            case INTERFACE_TRANSACTION: {
//                reply.writeString(DESCRIPTOR);
//                return true;
//            }
            case IRemoteService.TRANSACTION_getServiceName: {
                data.enforceInterface(mImpl.DESCRIPTOR);
                reply.writeNoException();
                reply.writeString(mImpl.getServiceName());
                return true;
            }
            default:
                return super.onTransact(code, data, reply, flags);
        }
    }

}
複製程式碼

好的, 可以看到這裡定義了兩個類, 它們分別是

  • RemoteServiceImpl: 遠端服務的實現類
  • RemoteServiceBinder: IRemoteService 服務的 Binder 驅動

可以發現 RemoteServiceImpl 與 RemoteServiceBinder 內部是相互持有對方引用的

  • RemoteServiceImpl 若未持有 IBinder 物件, 它僅僅是一個介面的實現類而已, 正是這個 IBinder 物件讓它提供了跨程式通訊的可能
  • 這裡的 RemoteServiceBinder 物件它作用於服務端的實現介面, 我們稱之為 Binder 實體物件

實現客戶端介面的代理實現類

/**
 * 介面 IRemoteService 在客戶端的代理實現類
 * {@link #mBinderProxy } 這個物件即 BinderProxy {@link android.os.Binder#BinderProxy} 例項的引用
 *
 * @author Sharry <a href="SharryChooCHN@Gmail.com">Contact me.</a>
 * @version 1.0
 * @since 2018/9/29 16:36
 */
public class RemoteServiceProxyImpl implements IRemoteService {

    private IBinder mBinderProxy;

    public RemoteServiceProxyImpl(IBinder binder) {
        mBinderProxy = binder;
    }

    @Override
    public String getServiceName() throws RemoteException {
        String result = null;
        Parcel data = Parcel.obtain();
        Parcel replay = Parcel.obtain();
        try {
            // 1. 寫入呼叫方法所對應介面的描述
            data.writeInterfaceToken(DESCRIPTOR);
            // 2. 向 Service 端發起 invocation 請求
            mBinderProxy.transact(IRemoteService.TRANSACTION_getServiceName, data, replay, 0);
            // 3. 讀取 Service 端扔出來的異常資訊
            replay.readException();
            // 4. 讀取異常結果
            result = replay.readString();
        } finally {
            replay.recycle();
            data.recycle();
        }
        return result;
    }

    @Override
    public IBinder asBinder() {
        return mBinderProxy;
    }

}
複製程式碼

好的, 可以看到這裡我們也建立了一個 IRemoteService 的實現類 RemoteServiceProxyImpl 從名字我們就可以知道它是 IRemoteService 介面的代理實現類, 相同的它內部也持有一個 IBinder 物件, 不過這裡我們沒有自己建立這個 IBinder 的實現類, **那這個 IBinder 實現類是誰呢? **, 其實我們無需為代理介面物件建立其特定的 BinderProxy 實現類, 因為系統給我們提供了, 這個 IBinder 的實現類其實是 android.os.BinderProxy: IBinder

用於將 IBinder 轉為介面的工具類

/**
 * 用於將一個 Binder 物件轉為對應的介面
 *
 * @author Sharry <a href="SharryChooCHN@Gmail.com">Contact me.</a>
 * @version 1.0
 * @since 2018/9/29 16:35
 */
public class RemoteServiceUtils {

    /**
     * Cast an IBinder object into an com.frank.aidldemo.IServiceInteraction interface,
     * generating a proxy if needed.
     */
    public static IRemoteService asInterface(IBinder obj) {
        if (null == obj) {
            return null;
        }
        // 若想獲取 Service 端 RemoteServiceImpl 物件, 需要在 Binder 本地物件構建時, 呼叫 attachInterface
        IInterface iin = obj.queryLocalInterface(IRemoteService.DESCRIPTOR);
        if (null != iin && iin instanceof IRemoteService) {
            return ((IRemoteService) iin);
        }
        return new RemoteServiceProxyImpl(obj);
    }

}
複製程式碼

UML 圖解析

Binder 依賴類圖.png

二. Binder 實體物件的建立

Binder(android.os.Binder: IBinder) 是 Binder 本地物件 Java 層的實現, 在 Server 端例項化, 對應我們上面的 RemoteServiceBinder, 它的作用是在 onTransact 中接收 Client 端通過 Binder 驅動傳來的 IPC 請求, 並將請求結果通過 Binder 驅動返回給 Client 端

接下來我們就看看如何例項化一個 Binder 實體物件

建立一個 Binder 物件

使用過 Service 的都知道, 在 onBind 中會 new 一個我們實現好的 Binder 物件

public class RemoteService extends Service {

    public IBinder onBind(Intent intent) {
        return new RemoteServiceBinder();
    }
    
}
複製程式碼

這個樣子我們就建立了一個 Binder 本地物件, 我們看看他的構造方法

public class Binder implements IBinder {

    private final long mObject;//
    
    /**
     * Binder.constructor
     */
    public Binder() {
        // 呼叫了 getNativeBBinderHolder 方法, 獲取一個 Native 層的 BBinderHolder 物件控制程式碼值
        mObject = getNativeBBinderHolder();
        ......
    }
    
    private static native long getNativeBBinderHolder();
    
}
複製程式碼

可以看見 Binder 中主要呼叫了 native 方法, 獲取了一個控制程式碼值, 當然這個 mObject 控制程式碼值也非常重要, 因為 Binder 類內部很多方法都是由它代理實現的

getNativeBBinderHolder

// frameworks/base/core/jni/android_util_Binder.cpp
static jlong android_os_Binder_getNativeBBinderHolder(JNIEnv* env, jobject clazz)
{
    // new 了一個 JavaBBinderHolder 這個物件
    JavaBBinderHolder* jbh = new JavaBBinderHolder();
    return (jlong) jbh;
}
複製程式碼

非常簡單的實現 new 了一個 JavaBBinderHolder 這個物件, 然後將它的控制程式碼返回給 Java 中 Binder 物件的 mObject 屬性, 接下來我們看看這個 JavaBBinderHolder 的建構函式中做了哪些操作

// frameworks/base/core/jni/android_util_Binder.cpp
class JavaBBinderHolder
{
public:
    // 獲取 JavaBBinder 物件
    sp<JavaBBinder> get(JNIEnv* env, jobject obj)
    {
        AutoMutex _l(mLock);
        // 嘗試將 mBinder 這個若指標升級為強指標
        sp<JavaBBinder> b = mBinder.promote();
        // 升級失敗則重新建立 JavaBBinder 物件
        if (b == NULL) {
            b = new JavaBBinder(env, obj);
            mBinder = b;
        }
        // 返回一個 JavaBinder
        return b;
    }

private:
    Mutex           mLock;   // 互斥鎖
    wp<JavaBBinder> mBinder; // 一個 JavaBBinder 的弱指標
};
複製程式碼

可以看到 JavaBBinderHolder 顧名思義果真持有了一個 JavaBBinder 的物件, 並且可以通過 get 方法獲取到, 結下來看看這個 JavaBinder

// frameworks/base/core/jni/android_util_Binder.cpp
class JavaBBinder : public BBinder
{
public:
    // 建構函式
    JavaBBinder(JNIEnv* env, jobject /* Java Binder */ object)
        : mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object))
    {
        ......
    }

    // 獲取我們 Java 中例項化的 Binder 物件
    jobject object() const
    {
        return mObject;
    }

protected:
    // Native 層 JavaBBinder 的 onTransact 函式
    virtual status_t onTransact(
        uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0)
    {   
        // 將 JavaVM convert2 JNIEnv
        JNIEnv* env = javavm_to_jnienv(mVM);
        // 回撥 java 中 Binder 物件的 execTransact 方法
        jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact,
            code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags);
        
        // ......
        if (code == SYSPROPS_TRANSACTION) {
            // 回撥父類的 onTransact 函式
            BBinder::onTransact(code, data, reply, flags);
        }
        
        return res != JNI_FALSE ? NO_ERROR : UNKNOWN_TRANSACTION;
    }

private:
    JavaVM* const   mVM;      // JavaVM 指標物件, 用於轉為 JNIEnv*
    jobject const   mObject;  // Java 中的 Binder 物件, 即我們在 Java 中建立的例項
};
複製程式碼

看到 JavaBBinder 的實現, 一切就變得很明瞭了

  • JavaBBinder 繼承了 BBinder , 它即是應用框架中的 Binder 本地物件
  • 可以看到建構函式中將我們 Java 中例項化的 Binder 物件傳了近來, 儲存在 JavaBBinder 的成員變數中

如此一來, Binder 驅動傳送的 IPC 請求, 就可以通過 JavaBBinder 的 onTransact 函式回溯到 Java 的 Binder 方法中了

三. Binder 代理物件的建立

BinderProxy(android.os.BinderProxy: IBinder) 是 Binder 代理物件 Java 層代理物件, 實現了 IBinder 介面, 它在 Client 程式例項化, 它的作用是讓 Client 端呼叫 transact 方法通過 Binder 驅動向 Server 端發起 IPC 請求, 並等待 Server 端返回的結果, 接下來我們就看看它例項化的過程

建構函式

    /**
     * BinderProxy.getInstance 是私有化的
     */
    private static BinderProxy getInstance(long nativeData, long iBinder) {
        BinderProxy result;
        ......
        result = new BinderProxy(nativeData);
        ......
        return result;
    }

    /**
     * BinderProxy 的構造方法也是私有化的
     */
    private BinderProxy(long nativeData) {
        mNativeData = nativeData;
    }
複製程式碼

可以看到 BinderProxy 這個 java 的 Class 兩個獲取例項的方式都是私有化的, 而且其構造方法的形參是一個 Native 指標的控制程式碼值, 那麼只有一種可能性, BinderProxy 這個物件是在 Native 層使用 JNI 例項化再返回 java 層的, 帶著這個疑問我們去探究一下

BinderProxy 物件的獲取方式

瞭解 Binder 驅動相關知識的小夥伴都知道, BinderProxy 一般可以通過兩種方式來獲取

方式一

我們在 Client 程式, 通過呼叫 Context.bindService(), 啟動一個遠端的 Service, 這時候需要需要傳入一個 ServiceConnection 引數物件, 在其 onServiceConnected 方法中會收到一個 IBinder 物件的回撥, 這個物件即為 BinderProxy, 如下圖

   private val connect: ServiceConnection = object : ServiceConnection {

        override fun onServiceDisconnected(name: ComponentName?) {

        }

        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            // 這個 service 即一個 BinderProxy 的例項物件
        }
        
    }
複製程式碼

因為這裡還會牽扯到 Service 的啟動流程, 所以我們使用方式二的思路往下解析

方式二

通過 ServiceManager 獲取一個系統註冊好的服務, Activity 啟動流程中的 AMS 相信大家再熟悉不過了, 看看它是如何獲取的

    private static final Singleton<IActivityManager> IActivityManagerSingleton =
            new Singleton<IActivityManager>() {
                @Override
                protected IActivityManager create() {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                    final IActivityManager am = IActivityManager.Stub.asInterface(b);
                    return am;
                }
            };
複製程式碼

可以看到第二種方式是通過 ServiceManager.getService(Context.ACTIVITY_SERVICE) 獲取的, 我們繼續往下剖析

    /**
     * ServiceManager.getService 獲取服務的 IBinder 物件
     */
    public static IBinder getService(String name) {
        try {
            // 嘗試從快取中獲取
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                // 關注一下這個地方
                // 1. getIServiceManager() 獲取 IServiceManager 服務提供物件
                // 2. 呼叫 IServiceManager.getService(name) 獲取服務
                return Binder.allowBlocking(getIServiceManager().getService(name));
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }
複製程式碼

可以看到 ServiceManager 其實是一個殼, 其方法的主要實現是通過 IServiceManager 實現的, 接下來我們看看 getIServiceManager 是如何獲取 IServiceManager 物件的

    /**
     * ServiceManager.getIServiceManager
     */
    private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }
        // 關注這裡
        // 1. 這裡通過 BinderInternal.getContextObject() 這個方法獲取到了 BinderProxy 物件
        // 2. 通過 asInterface 將這個 BinderProxy 物件封裝成了 ServiceManagerProxy 
        sServiceManager = ServiceManagerNative
                .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
        return sServiceManager;
    }
    
    /**
     * BinderInternal.getContextObject 這就是獲取 BinderProxy 物件的關鍵
     */
    public static final native IBinder getContextObject();
    
    /**
     * ServiceManagerNative.asInterface
     */
    static public IServiceManager asInterface(IBinder obj)
    {
        if (obj == null) {
            return null;
        }
        IServiceManager in =
            (IServiceManager)obj.queryLocalInterface(descriptor);
        if (in != null) {
            return in;
        }
        // 將 IBinder 物件封裝成 ServiceManagerProxy 物件, 這個物件是 IServiceManager 提供給 Client 端使用的實現類
        return new ServiceManagerProxy(obj);
    }
    
}
複製程式碼

我們看到了一個非常重要的線索 BinderInternal.getContextObject() 這是一個非常關鍵的方法, 系統就是通過它獲取了 IServiceManager 的 Binder 代理物件

至此 Java 層的旅程就告一段落了, 接下來附上一張 ServiceManager 在 java 層的關係依賴圖

ServiceManager .png

接下來我們就要深入到 Native 層去看看 BinderInternal.getContextObject() 的實現了

BinderInternal.getContextObject()

// frameworks/base/core/jni/android_util_Binder.cpp
static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz)
{
    // 呼叫了 ProcessState 的 getContextObject 獲取一個控制程式碼值為 NULL 的 IBinder 物件
    sp<IBinder> b = ProcessState::self()->getContextObject(NULL);
    // 呼叫 javaObjectForIBinder 返回一個 Java 中的 Binder 物件
    return javaObjectForIBinder(env, b);
}
複製程式碼

可以看到 Native 層中的 getContextObject 中主要做了兩步操作

  • 通過 ProcessState->getContextObject(NULL) 獲取了一個 IBinder 物件
    • 若為 ServiceManger 的建立程式, 則為 BBinder 本地物件
    • 若非建立程式, 則為 BpBinder 代理物件
  • 通過 javaObjectForIBinder 這個方法獲取 Java 的代理物件
// frameworks/base/core/jni/android_util_Binder.cpp
jobject javaObjectForIBinder(JNIEnv* env, const sp<IBinder>& val)
{
    if (val == NULL) return NULL;

    // 若為本地物件, 則返回 Java 中的 Binder 物件
    // 很顯然這個 val 為 BpBinder, 我們關注下面的程式碼
    if (val->checkSubclass(&gBinderOffsets)) {
        jobject object = static_cast<JavaBBinder*>(val.get())->object();
        return object;
    }

    // 互斥鎖
    AutoMutex _l(mProxyLock);

    // 1. 判斷是否已經為當前驅動建立了 BinderProxy 物件了
    jobject object = (jobject)val->findObject(&gBinderProxyOffsets);
    if (object != NULL) {
        // 通過引用升級的方式, 判斷這個 BinderProxy 物件是否有效
        jobject res = jniGetReferent(env, object);
        if (res != NULL) {
            // 若有效則返回到 java 層
            return res;
        }
        ......
        // 若無效則解除這個物件與 BpBinder 的關聯
        val->detachObject(&gBinderProxyOffsets);
        ......
    }
    // 2. 這裡通過 JNI 的方式呼叫了 BinderProxy 的建構函式
    object = env->NewObject(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mConstructor);
    if (object != NULL) {
        // 3. 給 BinderProxy 中的 mObject 物件賦值, 值為 BpBinder 的控制程式碼
        // 如此一來 Java 中的 BinderProxy 就可以通過它找到 BpBinder 了
        env->SetLongField(object, gBinderProxyOffsets.mObject, (jlong)val.get());
        val->incStrong((void*)javaObjectForIBinder);
        // 4. 將這個 BinderProxy 快取到 BpBinder 中
        jobject refObject = env->NewGlobalRef(
                env->GetObjectField(object, gBinderProxyOffsets.mSelf));
        val->attachObject(&gBinderProxyOffsets, refObject,
                jnienv_to_javavm(env), proxy_cleanup);
        ......
    }

    return object;
}
複製程式碼

可以看到這個方法中做了如下幾件事情

  • 若 val 為 BBinder, 則返回其關聯的 Java 層的 Binder 物件
  • 若 val 為 BpBinder
    • 從快取中查詢對應的 BinderProxy 物件, 若有效則返回給 Java 層
    • 若無效則通過 JNI 例項化一個 BinderProxy 物件
      • 給 BinderProxy.mObject 賦值, 指向 Native 層對應的 BpBinder 物件
      • 將這個 BinderProxy 物件加入 BpBinder 的快取

至此 Java 中的 BinderProxy 就在 Native 層建立出來了

總結

好的分析到這裡我們知道了 Binder 實體物件與 代理物件的例項化過程, 並且也悄悄的解開了 Binder 驅動在應用框架層的面紗

  • 在 Client 端
    • Binder 提供了 BinderProxy 物件
    • BinderProxy 物件對應 Binder執行時庫中的 BpBinder
  • 在 Service 端
    • Binder 提供了 Binder 物件, 需要根據介面我們自行實現
    • Binder 物件對應了 Binder 執行時庫中的 BBinder

好的, 至此 Binder 驅動的上篇就結束了, 下篇中會著重分析 Binder 執行時庫和一次 Binder 通訊的流程

相關文章