Android Binder 學習筆記

寫在前面

Binder是Android給我們提供的一種跨進程通信方式。理解Binder能幫助我們更好的理解Android的系統設計,比如說四大組件,AMS,WMS等系統服務的底層通信機制就都是基於Binder機制的。當然了,Binder機制的底層驅動實現很複雜,本文的目的只是爲了理清Binder的使用和在應用層的結構和流程,對於Binder在底層是如何實現的,目前能力還沒到這一步去分析,不會涉及到。對於這部分,不妨將它看成是一個黑盒子,我們輸入什麼,然後底層會給我們提供什麼。

代理模式

我們知道,A進程如果想要執行B進程的b方法,是沒辦法直接辦得到的,但是通過Binder機制,B進程可以返回給A進程一個代理對象Proxy,然後A進程通過調用Proxy的方法,由Proxy幫我們將信息傳遞給B進程,從而間接調用b方法。沒錯,Binder實現過程中用到了代理模式。所以在繼續前行之前,有必要簡單瞭解下代理模式先。

代理模式相對來說好理解一些,因爲在生活中,到處都有代理的影子,比如說我們想去香港買個Mac,但是自己不方便去,於是我們找了代購;比如說現在年底了要搶火車票,但是在12306手動搶票根本搶不到啊,所以我們找了第三方搶票軟件,它會每隔幾十ms就幫我們查詢一次,有票的話就幫我們下單。這裏就以搶火車票爲例來說明代理模式的結構。

模式比較簡單,就直接上代碼了。

// 聲明買票接口
public interface ITicket {
    boolean buyTicket();
}

// 官方的12306
public class Real12306 implements ITicket {
    @Override
    public boolean buyTicket() {
        if (搶票成功) return true;
        return false;
    }
}

// 第三方搶票軟件
public class ThirdParty12306 implements ITicket {

    private Real12306 real12306;
    
    public ThirdParty12306(Real12306 real12306) {
        this.real12306 = real12306;
    }
    
    @Override
    public boolean buyTicket() {
        while (true) {
            if (real12306.buyTicket()) {
                return true;
            }
            // 10ms查詢一次結果
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class Main {

    public static void main(String[] args) {

        // 初始化我們的購票信息
        Real12306 real12306 = new Real12306();
        
        ThirdParty12306 thirdParty12306 = new ThirdParty12306(real12306);
        
        // 開始不斷搶票,釋放我們的勞動力
        thirdParty12306.buyTicket();
    }
}

使用了代理模式之後,我們就不用時時刻刻盯着12306刷票了,只需要把這些重複無聊的工作交給代理去幫我們幹就好了。

AIDL

一般來說,我們使用Binder都是通過AIDL來完成的。我們新建一個aidl文件,然後定義一個接口,這樣Android Studio就會幫我們生成一個java接口文件。以一個最簡單的接口來說吧。

package example.com.aidl;
interface IMath {
    int add(int a, int b);
}

生成的IMath.java文件中,代碼有點亂,整理一下之後,結構大致是這樣子的:

簡單來說,生成了一個IMath接口,接口內定義了一個抽象類IMath.Stub,繼承了Binder,IMath.Stub又有一個內部類IMath.Stub.Proxy。IMath.Stub和IMath.Stub.Proxy都實現了IMath這個接口。結合上面的代理模式,從這裏我們就可以猜出,在跨進程通信中,由於各個進程都是獨立的,我們的客戶端拿不到服務端的IMath.Stub類,只能獲得它的代理IMath.Stub.Proxy,再通過它來間接幫我們訪問IMath.Stub類,從而完成跨進程通信。

Binder流程

看了上面的結構圖之後,估計大家還是看不懂的。不急,我們再結合上面這個例子來說明。Binder機制是基於C/S模型的,也就是說,需要一個client進程和一個Server進程。Client和Server是相對的,誰發消息,誰就是Client,誰接收消息,誰就是Server。在實際開發中,Server進程通常是四大組件中的Service(Service必須在Manifest文件中指定進程名字)。

class RemoteService : Service() {

    val math = Math()

    override fun onCreate() {
        super.onCreate()
        Log.d(TAG, "onCreate")
    }
    
    override fun onBind(intent: Intent): IBinder {
        return math
    }

    inner class Math : IMath.Stub() {
        override fun add(a: Int, b: Int): Int {
            return a + b
        }

    }
}

在RemoteService中,我們先定義一個Math類,繼承自IMath.Stub,在這裏實現我們具體的服務端邏輯。因爲IMath.Stub繼承自Binder,Binder又實現了IBinder接口,所以在onBind()方法中直接返回math對象。接着再來看客戶端的業務邏輯。

// 定義ServiceConnection類
inner class MyServiceConnection : ServiceConnection {
    override fun onServiceDisconnected(name: ComponentName?) {
        Log.d(TAG, "onServiceDisconnected")
    }

    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        if (service == null) return

        // 將IBinder轉換成IMath
        math = IMath.Stub.asInterface(service)

        Log.d(TAG, "result is ${math.add(1, 2)}")
    }
}

// 在onCreate中綁定RemoteService
val intent = Intent(this, RemoteService::class.java)
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)

當連接上Service後,就會回調客戶端的onServiceConnected()方法,這裏傳進來的service是一個BinderProxy對象。BinderProxy是Binder的代理類,同樣也實現了IBinder接口。我們在Server端返回的明明是一個Math對象,到這裏就變成了BinderProxy對象了,是不是有點神奇?別忘了,Math本身就是一個Binder對象。由於是跨進程通信,我們無法直接拿到這個Binder對象,只能由BinderProxy對象來幫助我們完成任務。至於Binder是怎麼變成BinderProxy的,這就是Binder機制的底層原理了,將它當成一個黑盒子就好了。

拿到BinderProxy對象後,再將它轉換成我們定義的IMath接口。

// IMath.java
private static final java.lang.String DESCRIPTOR = "example.com.aidl.IMath";

public static example.com.aidl.IMath asInterface(android.os.IBinder obj) {
    if ((obj == null)) {
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin != null) && (iin instanceof example.com.aidl.IMath))) {
        return ((example.com.aidl.IMath) iin);
    }
    return new example.com.aidl.IMath.Stub.Proxy (obj);
}

// Binder.java
public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
    if (mDescriptor != null && mDescriptor.equals(descriptor)) {
        return mOwner;
    }
    return null;
}

asInterface()方法中可以看到,根據Key值DESCRIPTOR在Binder中匹配mOwner,它是一個IInterface對象。但既然是去取值,就應該有地方將他們存進來的,我們好像錯過了什麼。這裏還得回到Math的初始化過程,Math繼承自IMath.Stub,看一下它的構造方法就能明白了。

// IMath.java
public Stub() {
    this.attachInterface(this, DESCRIPTOR);
}

// Binder.java
public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
    mOwner = owner;
    mDescriptor = descriptor;
}

到了這裏,IInterface的獲取已經很明顯了吧。但其實,這裏取出來的是Null。What?爲什麼?別忘了,RemoteService是運行在一個單獨的進程中的,attachInterface()方法是Binder調用的。而我們的客戶端拿到的只是BinderProxy,查詢到的IInterface當然是Null了,所以我們還得接着看asInterface()方法。(當然了,如果RemoteService和客戶端運行在同一個進程的話,這裏就能直接拿到IInterface了,但這與跨進程通信就沒有半毛錢關係了。)

return new example.com.aidl.IMath.Stub.Proxy(obj);

直接返回了一個代理對象。後續我們要跟Server端做交互就得靠它了。比如我們調用了Proxy.add()方法:

@Override
public int add(int a, int b) throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain ();
    int _result;
    try {
        // 使用Parcel來寫入數據以便於跨進程傳輸
        _data.writeInterfaceToken(DESCRIPTOR);
        _data.writeInt(a);
        _data.writeInt(b);
        
        // mRemote是在asInterface中獲得的BinderProxy對象
        mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
        
        // 使用Parcel來接收返回值
        _reply.readException();
        _result = _reply.readInt();
    } finally {
        _reply.recycle();
        _data.recycle();
    }
    return _result;
}

核心方法是mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);。這裏的mRemote是客戶端拿到的BinderProxy對象,然後就要開始跨進程傳輸了。又到了黑盒子出現的時候了,客戶端發起跨進程通信後,服務端就會在自己進程的onTranscat()方法中收到通知:

@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_add : {
            data.enforceInterface(descriptor);
            int _arg0;
            _arg0 = data.readInt();
            int _arg1;
            _arg1 = data.readInt();
            int _result = this.add(_arg0, _arg1);
            reply.writeNoException();
            reply.writeInt(_result);
            return true;
        } default: {
        return super.onTransact(code, data, reply, flags);
        }
    }
}

在Server端收到信息後,會先通過Parcel將信息解析出來,然後執行我們調用的add()方法,也就是我們在RemoteService中重寫IMath.Stub的add()方法。最後將結果寫回Parcel中再跨進程傳回給客戶端,從而完成了一次跨進程通信。

如果看到這裏,對於Binder的流程還有疑惑的話,那就再來一張時序圖好了。

看圖說話,當我們在客戶端中去bindService()的時候,Server端在onBind()中返回了一個Binder對象,經過Binder驅動的轉換,這個Binder到了客戶端中變成了BinderProxy,客戶端接着再把BinderProxy轉換成Stub.Proxy,後面我們與Server的跨進程通信就都是通過Stub.Proxy發起的,然後Binder驅動會幫我們將數據跨進程傳輸給真正的Binder,Binder執行完操作後再將結果寫入由Binder驅動傳回來。由此完成了一次跨進程通信。

從圖中我們也可以看出通信過程是同步的。當客戶端發起請求的同時,當前的線程會被掛起,直到結果返回。所以要注意的是如果請求太耗時的話,不應該在主線程中去請求,否則容易出現ANR。給個Systrace直觀感受一下。

相應的CPU信息是處於休眠狀態的。

最後

掌握了Binder的上層原理之後,後面再來深入Framework層學習就會簡單一些,這篇文章也是爲了後面的學習打下基礎。

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