3分鐘帶你看懂android的Binder機制

散人丶發表於2019-01-21

一.引言

最近一段時間由於工作,接觸到framework部分比較多一點,也難免要和Binder打一些交道,也整理了一些相關知識,但準備寫這篇文章時,還是有些慌。而且關於整個Binder機制的複雜程度不是三言兩語能描敘清楚的,也害怕自己的理解有些偏差,誤導一些朋友(ps:反正也沒人看....扎心)所以也參考了很多資料。

本文主要站在Android開發的角度來大致解析下Binder在java層的一些知識原理,給大家腦子形成一個完整的概念,比如AIDL的實現原理,Binder是怎麼通訊的等等,文章文字較多,請耐心觀看

熟悉的朋友可以看看下篇,將介紹Activity的啟動流程以及Android中的Hook技術:

震驚!Activity不用註冊?手把手教你Hook

二.Binder概述

2.1 Android為什麼選擇Binder

Android是基於Linux核心的,所以Android要實現程式間的通訊,其實大可使用linux原有的一些手段,比如管道,共享記憶體,socket等方式,但是Android還是採用了Binder作為主要機制,說明Binder具有無可比擬的優勢。

其實程式通訊大概就兩個方面因素,一者效能方面,傳輸效率問題,傳統的管道佇列模式採用記憶體緩衝區的方式,資料先從傳送方快取區拷貝到核心開闢的快取區中,然後再從核心快取區拷貝到接收方快取區,至少有兩次拷貝過程,而socket都知道傳輸效率低,開銷大,用於跨網路程式互動比較多,共享記憶體雖然無需拷貝。

二者這是安全問題,Android作為一個開放式,擁有眾多開發者的的平臺,應用程式的來源廣泛,確保終端安全是非常重要的,傳統的IPC通訊方式沒有任何措施,基本依靠上層協議,其一無法確認對方可靠的身份,Android為每個安裝好的應用程式分配了自己的UID,故程式的UID是鑑別程式身份的重要標誌,傳統的IPC要傳送類似的UID也只能放在資料包裡,但也容易被攔截,惡意進攻,socket則需要暴露自己的ip和埠,知道這些惡意程式則可以進行任意接入。

綜上所述,Android需要一種高效率,安全性高的程式通訊方式,也就是Binder,Binder只需要一次拷貝,效能僅次於共享記憶體,而且採用的傳統的C/S結構,穩定性也是沒得說,傳送新增UID/PID,安全性高。

2.2 Binder實現機制

2.2.1程式隔離

我們知道程式之間是無法直接進行互動的,每個程式獨享自己的資料,而且作業系統為了保證自身的安全穩定性,將系統核心空間和使用者空間分離開來,保證使用者程式程式崩潰時不會影響到整個系統,簡單的說就是,核心空間(Kernel)是系統核心執行的空間,使用者空間(UserSpace)是使用者程式執行的空間。為了保證安全性,它們之間是隔離的,所以使用者空間的程式要進行互動需要通過核心空間來驅動整個過程。

2.2.2 C/S結構

Binder是基於C/S機制的,要實現這樣的機制,server必須需要有特定的節點來接受到client的請求,也就是入口地址,像輸入一個網址,通過DNS解析出對應的ip,然後進行訪問,這個就是server提供出來的節點地址,而Binder而言的話,與傳統的C/S不太一樣,Binder本身來作為Server中提供的節點,client拿到Binder實體物件對應的地址去訪問Server,對於client而言,怎麼拿到這個地址並建立起整個通道是整個互動的關鍵所在,而且Binder作為一個Server中的實體,物件提供一系列的方法來實現服務端和客戶端之間的請求,只要client拿到這個引用就可以或者一個有著該方法代理物件的引用,就可以進行通訊了。

引用Android Bander設計與實現 - 設計篇一段話來總結:

物件導向思想的引入將程式間通訊轉化為通過對某個Binder物件的引用呼叫該物件的方法,而其獨特之處在於Binder物件是一個可以跨程式引用的物件,它的實體位於一個程式中,而它的引用卻遍佈於系統的各個程式之中。最誘人的是,這個引用和java裡引用一樣既可以是強型別,也可以是弱型別,而且可以從一個程式傳給其它程式,讓大家都能訪問同一Server,就象將一個物件或引用賦值給另一個引用一樣。Binder模糊了程式邊界,淡化了程式間通訊過程,整個系統彷彿執行於同一個物件導向的程式之中。形形色色的Binder物件以及星羅棋佈的引用彷彿粘接各個應用程式的膠水,這也是Binder在英文裡的原意。

2.2.3 Binder通訊模型

Binder基於C/S的結構下,定義了4個角色:Server、Client、ServerManager、Binder驅動,其中前三者是在使用者空間的,也就是彼此之間無法直接進行互動,Binder驅動是屬於核心空間的,屬於整個通訊的核心,雖然叫驅動,但是實際上和硬體沒有太大關係,只是實現的方式和驅動差不多,驅動負責程式之間Binder通訊的建立,Binder在程式之間的傳遞,Binder引用計數管理,資料包在程式之間的傳遞和互動等一系列底層支援。

ServerManager的作用?

我們知道ServerManager也是屬於使用者空間的一個程式,主要作用就是作為Server和client的橋樑,client可以ServerManager拿到Server中Binder實體的引用,這麼說可能有點模糊,舉個簡單的例子,我們訪問,www.baidu.com,百度首頁頁面就顯示出來了,首先我們知道,這個頁面肯定是釋出在百度某個伺服器上的,DNS通過你這個地址,解析出對應的ip地址,再去訪問對應的頁面,然後再把資料返回給客戶端,完成互動。這個和Binder的C/S非常類似,這裡的DNS就是對應的ServerManager,首先,Server中的Binder實體物件,將自己的引用(也就是ip地址)註冊到ServerManager,client通過特定的key(也就是百度這個網址)和這個引用進行繫結,ServerManager內部自己維護一個類似MAP的表來一一對應,通過這個key就可以向ServerManager拿到Server中Binder的引用,對應到Android開發中,我們知道很多系統服務都是通過Binder去和AMS進行互動的,比如獲取音量服務:

AudioManager am = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);

細心的朋友應該發現ServerManager和Server也是兩個不同的程式呀,Server要向ServerManager去註冊不是也要涉及到程式間的通訊嗎,當前實現程式間通訊又要用到程式間的通訊,你這不是扯犢子嗎....莫急莫急,Binder的巧妙之處在於,當ServerManager作為Serve端的時候,它提供的Binder比較特殊,它沒有名字也不需要註冊,當一個程式使用BINDER_SET_CONTEXT_MGR命令將自己註冊成SMgr時Binder驅動會自動為它建立Binder實體,這個Binder的引用在所有Client中都固定為0而無須通過其它手段獲得。也就是說,一個Server若要向ServerManager註冊自己Binder就必需通過0這個引用號和ServerManager的Binder通訊,有朋友又要問了,server和client屬於兩個不同的程式,client怎麼能拿到server中物件,不妨先看看下面的互動圖

3分鐘帶你看懂android的Binder機制

從上圖很清晰的可以看出來整個的互動過程,原本從SM中拿到binder的引用,通過Binder驅動層的處理之後,返回給了client一個代理物件,實際上如果client和server處於同一個程式,返回的就是當前binder物件,如果client和server不處於同一個程式,返回給client的就是一個代理物件,這一點,有興趣可以看下原始碼。

2.2.4 Binder角色的定位

Binder本質上只是提供了一種通訊的方式,和我們具體要實現的內容沒有關係,為了實現這個服務,我們需要定義一些介面,讓client能夠遠端呼叫服務,因為是跨程式,這時候就要設計到代理模式,以介面函式位基準,client和server去實現介面函式,Server是服務真正的實現,client作為一個遠端的呼叫。

  • 從Server程式來看,Binder是存在的實體物件,client通過transact()函式,經過Binder驅動,最終回撥到Binder實體的onTransact()函式中。
  • 從 Client程式的角度看,Binder 指的是對 Binder 代理物件,是 Binder 實體物件的一個遠端代理,通過Binder驅動進行互動

3.手寫程式通訊

上面說了那麼多,大傢伙也看累了,下面通過程式碼的形式讓大家對Binder加深點理解,日常開發中,涉及到程式間通訊的話,我們首先想到的可能就是AIDL,但不知道有沒有和我感覺一樣的朋友。。第一次寫AIDL是矇蔽的,通過.aidl檔案,編譯器自動生成程式碼,生成一個java檔案,裡面又有Stub類,裡面還有Proxy類,完全不理解裡面的機制,確實不便於我們理解學習,為了加深理解,我們拋棄aidl,手寫一個通訊程式碼。

首先我們要定義一個介面服務,也就是上述的服務端要具備的能力來提供給客戶端,定義一個介面繼承IInterface,代表了服務端的能力

public interface PersonManger extends IInterface {
    void addPerson(Person mPerson);
    List<Person> getPersonList();
}
複製程式碼

接下來我們就要定義一個Server中的Binder實體物件了,首先肯定要繼承Binder,其次需要實現上面定義好的服務介面

public abstract class BinderObj extends Binder implements PersonManger {
    public static final String DESCRIPTOR = "com.example.taolin.hellobinder";
    public static final int TRANSAVTION_getPerson = IBinder.FIRST_CALL_TRANSACTION;
    public static final int TRANSAVTION_addPerson = IBinder.FIRST_CALL_TRANSACTION + 1;
    public static PersonManger asInterface(IBinder mIBinder){
        IInterface iInterface = mIBinder.queryLocalInterface(DESCRIPTOR);
        if (null!=iInterface&&iInterface instanceof PersonManger){
            return (PersonManger)iInterface;
        }
        return new Proxy(mIBinder);
    }
    @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 TRANSAVTION_getPerson:
                data.enforceInterface(DESCRIPTOR);
                List<Person> result = this.getPersonList();
                reply.writeNoException();
                reply.writeTypedList(result);
                return true;

            case TRANSAVTION_addPerson:
                data.enforceInterface(DESCRIPTOR);
                Person arg0 = null;
                if (data.readInt() != 0) {
                    arg0 = Person.CREATOR.createFromParcel(data);
                }
                this.addPerson(arg0);
                reply.writeNoException();
                return true;
        }
        return super.onTransact(code, data, reply, flags);

    }

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

首先我們看asInterface方法,Binder驅動傳來的IBinder物件,通過queryLocalInterface方法,查詢本地Binder物件,如果返回的就是PersonManger,說明client和server處於同一個程式,直接返回,如果不是,返回給一個代理物件。

當然作為代理物件,也是需要實現服務介面

public class Proxy implements PersonManger {
    private IBinder mIBinder;
    public Proxy(IBinder mIBinder) {
        this.mIBinder =mIBinder;
    }

    @Override
    public void addPerson(Person mPerson) {
        Parcel data = Parcel.obtain();
        Parcel replay = Parcel.obtain();

        try {
            data.writeInterfaceToken(DESCRIPTOR);
            if (mPerson != null) {
                data.writeInt(1);
                mPerson.writeToParcel(data, 0);
            } else {
                data.writeInt(0);
            }
            mIBinder.transact(BinderObj.TRANSAVTION_addPerson, data, replay, 0);
            replay.readException();
        } catch (RemoteException e){
            e.printStackTrace();
        } finally {
            replay.recycle();
            data.recycle();
        }
    }

    @Override
    public List<Person> getPersonList() {
        Parcel data = Parcel.obtain();
        Parcel replay = Parcel.obtain();
        List<Person> result = null;
        try {
            data.writeInterfaceToken(DESCRIPTOR);
            mIBinder.transact(BinderObj.TRANSAVTION_getPerson, data, replay, 0);
            replay.readException();
            result = replay.createTypedArrayList(Person.CREATOR);
        }catch (RemoteException e){
            e.printStackTrace();
        } finally{
            replay.recycle();
            data.recycle();
        }
        return result;
    }

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

這裡的代理物件實質就是client最終拿到的代理服務,通過這個就可以和Server進行通訊了,首先通過Parcel將資料序列化,然後呼叫 remote.transact()將方法code,和data傳輸過去,對應的會回撥在在Server中的onTransact()中

然後是我們的Server程式,onBind方法返回mStub物件,也就是Server中的Binder實體物件

public class ServerSevice extends Service {
    private static final String TAG = "ServerSevice";
    private List<Person> mPeople = new ArrayList<>();


    @Override
    public void onCreate() {
        mPeople.add(new Person());
        super.onCreate();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mStub;
    }
    private BinderObj mStub = new BinderObj() {
        @Override
        public void addPerson(Person mPerson) {
            if (mPerson==null){
                mPerson = new Person();
                Log.e(TAG,"null obj");
            }
            mPeople.add(mPerson);
            Log.e(TAG,mPeople.size()+"");
        }

        @Override
        public List<Person> getPersonList() {
            return mPeople;
        }
    };
}
複製程式碼

最終我們在客戶端程式,bindService傳入一個ServiceConnection物件,在與服務端建立連線時,通過我們定義好的BinderObj的asInterface方法返回一個代理物件,再呼叫方法進行互動

public class MainActivity extends AppCompatActivity {
    private boolean isConnect = false;
    private static final String TAG = "MainActivity";
    private PersonManger personManger;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        start();
        findViewById(R.id.textView).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (personManger==null){
                    Log.e(TAG,"connect error");
                    return;
                }
                personManger.addPerson(new Person());
                Log.e(TAG,personManger.getPersonList().size()+"");
            }
        });
    }

    private void start() {
        Intent intent = new Intent(this, ServerSevice.class);
        bindService(intent,mServiceConnection,Context.BIND_AUTO_CREATE);
    }
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(TAG,"connect success");
            isConnect = true;
            personManger = BinderObj.asInterface(service);
            List<Person> personList = personManger.getPersonList();
            Log.e(TAG,personList.size()+"");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG,"connect failed");
            isConnect = false;
        }
    };
}
複製程式碼

這樣的話,一次完成的程式間的互動就完成了~是不是感覺沒有想象中那麼難,最後建議大家在不借助 AIDL 的情況下手寫實現 Client 和 Server 程式的通訊,加深對 Binder 通訊過程的理解。

本文在寫作過程中參考了蠻多的文章和原始碼,感謝大佬們的無私奉獻,溜了溜了~

參考文章

相關文章