Android Binder解密

前言

稍微看過Android FrameWork層的人應該都知道Binder,因為app與系統服務之間的通信基本上都是建立在Binder的基礎上。之前對Binder也是云里霧里,似懂非懂,于是花了不少時間,看了很多資料和源碼,才大致了解了Binder通信的原理,總結出來,如有錯誤,還望指正。

簡介

Binder是什么?Binder是為跨進程通信而生的產物。眾所周知,我們的app都是由Zygote進程fork出來的,每個app都運行在單獨的進程中,一個app想與另外一個app進行通信,只能采用跨進程的方式,傳統的Linux跨進程方式有如下幾種:管道、信號量、共享內存及Socket等,Android系統建立在Linux的基礎上,但其采用的是Binder來進行IPC的。

接下來讓我們用一個小Demo展示一下如何用Binder進行跨進程通信。

實例

既然是跨進程通信,那么至少得有兩個進程,最簡單的方式就是指定activity的process屬性,但是本文為了更清楚的講解binder,采用的方式是建立兩個app,一個作為服務端,一個作為客戶端。服務端提供一個簡單的生詞本功能,客戶端可以向服務端插入和查詢生詞。
使用Binder通信主要有以下幾個步驟:

Step1:編寫AIDL文件

想要使用Binder,必須要先了解AIDL(Android Interface Definition Language),也就是接口定義語言,提供接口給遠程調用者。
為了給客戶端提供生詞本的調用接口,我們在/src/main目錄下先新建一個文件夾aidl,并新建一個aidl文件IDictionaryManager.aidl。

// IDictionaryManager.aidl
package com.wanginbeijing.dictionaryserver;

interface IDictionaryManager {
    void add(String chinese,String english);
    String query(String chinese);
}

接口中提供了兩個方法:add()和query(),分別作為插入和查詢操作。Build一下工程,android studio會自動為我們生成一個java類:IDictionaryManager.java。


我們來看看這個java類里面都寫了什么

public interface IDictionaryManager extends android.os.IInterface {

    public static abstract class Stub extends android.os.Binder implements com.wanginbeijing.dictionaryserver.IDictionaryManager {
        private static final java.lang.String DESCRIPTOR = "com.wanginbeijing.dictionaryserver.IDictionaryManager";

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


        public static com.wanginbeijing.dictionaryserver.IDictionaryManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.wanginbeijing.dictionaryserver.IDictionaryManager))) {
                return ((com.wanginbeijing.dictionaryserver.IDictionaryManager) iin);
            }
            return new com.wanginbeijing.dictionaryserver.IDictionaryManager.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_add: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    java.lang.String _arg1;
                    _arg1 = data.readString();
                    this.add(_arg0, _arg1);
                    reply.writeNoException();
                    return true;
                }
            case TRANSACTION_query: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    java.lang.String _result = this.query(_arg0);
                    reply.writeNoException();
                    reply.writeString(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.wanginbeijing.dictionaryserver.IDictionaryManager {
            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 void add(java.lang.String chinese, java.lang.String english) 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.writeString(chinese);
                    _data.writeString(english);
                    mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public java.lang.String query(java.lang.String chinese) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.lang.String _result;
                try {
                 _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeString(chinese);
                    mRemote.transact(Stub.TRANSACTION_query, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readString();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

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

    public void add(java.lang.String chinese, java.lang.String english) throws android.os.RemoteException;

    public java.lang.String query(java.lang.String chinese) throws android.os.RemoteException;
}

這個接口類比較長,繼承了android.os.IInterface這個接口。這個類簡化的結構大致如下:

public interface IDictionaryManager extends android.os.IInterface {

    public static abstract class Stub extends android.os.Binder implements com.wanginbeijing.dictionaryserver.IDictionaryManager {
    
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }


        public static com.wanginbeijing.dictionaryserver.IDictionaryManager asInterface(android.os.IBinder obj) 
        
        @Override
        public android.os.IBinder asBinder() 
        
        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
        
        private static class Proxy implements com.wanginbeijing.dictionaryserver.IDictionaryManager {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote)
            
            @Override
            public android.os.IBinder asBinder() 
            
            public java.lang.String getInterfaceDescriptor() 
            
            @Override
            public void add(java.lang.String chinese, java.lang.String english)
            
            @Override
            public java.lang.String query(java.lang.String chinese)  
      }

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

    public void add(java.lang.String chinese, java.lang.String english) 

    public java.lang.String query(java.lang.String chinese)
}

該類首先包含了一個抽象內部類:Stub, 該類繼承自Binder并實現了IDictionary接口。在Stub的內部,又包含了一個靜態內部類:Proxy,Proxy類同樣實現了IDictionary接口。

Step2:定義一個Service,用于客戶端連接

public class DictionaryManagerService extends Service {
    private Map<String, String> mMap = new HashMap<>();

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new IDictionaryManager.Stub() {
            @Override
            public void add(String chinese, String english) throws RemoteException {
                mMap.put(chinese, english);
                Log.e("DictionaryManager", "add new word");
            }

            @Override
            public String query(String chinese) throws RemoteException {
                return mMap.get(chinese);
            }
        };
    }
}

該類中定義了一個HashMap用來保存生詞,并重寫了Service中的onBind方法,在onBinder方法中,返回了一個繼承自IDictionaryManager.Stub的匿名內部類,并重寫了IDictionaryManager接口中add和query方法,實現真正的生詞本業務邏輯。

Step3:客戶端連接服務端Service

客戶端要調用服務端接口,先要將服務端定義好的aidl文件拷貝到客戶端相同目錄下,并build生成java文件。然后開始連接服務器端:

public class MainActivity extends Activity {

    private IDictionaryManager mDictionaryManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent();
        intent.setAction("android.intent.action.DictionaryManagerService");
        intent.setPackage("com.wanginbeijing.dictionaryserver");
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        //添加一個新單詞
        findViewById(R.id.btn_add).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    mDictionaryManager.add("你好", "Hello");
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

        //查詢單詞
        findViewById(R.id.btn_query).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    String english = mDictionaryManager.query("你好");
                    Toast.makeText(MainActivity.this, english, Toast.LENGTH_SHORT).show();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            IDictionaryManager dictionaryManager = IDictionaryManager.Stub.asInterface(iBinder);
            try {
                mDictionaryManager = dictionaryManager;
                Toast.makeText(MainActivity.this, "connect success", Toast.LENGTH_SHORT).show();
            } catch (Exception e) {
                Toast.makeText(MainActivity.this, "connect failed", Toast.LENGTH_SHORT).show();
                e.printStackTrace();
            }
        }

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

客戶端的MainActivty的onCreate()方法中,首先自動連接遠程service,拿到遠程service傳回的Binder對象后,強轉為IDictionaryService類型的變量mDictionaryManager。然后在add按鈕和query按鈕的監聽事件中分別添加和查詢單詞(你好,Hello)。下面展示一下操作Demo.



可以看見,在連接遠端service成功后,首先點擊add按鈕插入單詞,接著調用query接口,可以成功查詢到剛剛插入的單詞。

以上就是使用Binder進行IPC的主要過程,但是僅僅掌握了使用方法怎么能滿足我們的好奇心呢,我們還必須要挖挖它的原理。

原理

java層

說到原理,還得先從代碼看起,回到上述所說的Android Studio自動生成的那個java類:IDictionaryManager。剛才已經說過,該類中有一個繼承自Binder的Stub類,它實現了我們所需要的IDictionaryManager接口。我們先來看看它的幾個方法:

public static IDictionaryManager asInterface(IBinder obj)

這方法將一個IBinder對象轉換成實現了IDictionaryManager。如果請求調用的客戶端在同一進程,就直接返回Stub對象本身,如果不在同一進程,就返回Stub類中的Proxy對象。Proxy類同樣實現了IDictionaryManager接口。

asBinder

這個方法很簡單,就直接返回了Stub對象自己。

onTransact

這個方法主要是進行用來調用其他函數的,它會根據傳入的參數code,選擇調用add方法還是query方法。

接下來看看Stub.Proxy類中的方法

asBinder

這個方法返回了Proxy對象中的mRemote變量,mRemote變量的賦值發生在Proxy類的構造函數中,而在Stub類的asInterface()方法中調用了該構造函數Proxy(),并傳入了一個Binder對象,將mRemote指向了這個Binder對象。

add

該add方法繼承自IDictionary接口。里面主要是將客戶端傳參寫入_data中,然后調用transact()方法,在transact方法中,會回調Stub類中的onTransact()。

query

同上述add方法一樣,只不過多了一個返回值。

介紹完這些方法之后,我們再來將這些方法串起來,講一下完整的IPC過程??蛻舳诉B接遠程Service時,Service會返回一個Binder對象,即我們在DictionaryManagerService.onBind()中返回的繼承自Stub的匿名對象,該類實現了生詞本邏輯。
客戶端調用有兩種情況:

1:客戶端和服務端在同一個進程

服務器端就直接將該Stub對象返回,即我們在onBind()中返回的Stub對象,客戶端調用返回對象的asInterface()方法,將Stub對象強轉為IDictionManager對象,直接調用IDictionManager對象中的方法,就像調用本地對象的方法一樣。

2:客戶端和服務端不在同一個進程

因為是跨進程,兩端的內存資源是不能共享的,服務器端不可能將真正的Stub對象返回,它只能將Stub中的代理類Prxoy對象返回。客戶端同樣調用返回對象的asInterface()方法,將Proxy對象強轉為IDictionManager對象,所以這次調用IDictionManager中add方法的時候,調用的實際上是Prxoy.add()。這次調用發生在客戶端進程,然后在Proxy.add()方法中,將參數和目標函數code寫入Binder驅動中,并通過回調服務端Stub.onTransact()方法,實現調用真正的Stub.add()方法。

C++層

我們開發時所見到的Binder是Android系統提供給我們的java接口,java層的Binder對象只是Android對底層Binder的一個封裝,提供給上層開發人員使用,真正的Binder其實隱藏在系統底層,默默的替我們進行著跨進程通信。

Java層的服務端Binder對象在C++層對應的對象為BBinder,而客戶端拿到的BinderProxy對象對應的則為BpBinder,BBindder和BpBinder都繼承自IBinder,上層Binder和BinderProxy之間的通信其實是BBinder和BpBinder之間的通信。

Binder的核心是Binder Driver,即Binder驅動,雖然各個進程不能共享自己的內存空間,但是系統的內核空間是共享的,每個進程都可以訪問,而Binder Driver即存在于內核空間,BBinder和BpBinder都是通過它來通信的,所以可以把Binder驅動當作是BBinder和BpBinder之間的一座橋梁。當然BBinder和BpBinder也不是直接和Binder驅動打交道,它們中間還隔著一個IPCThreadState。每一個進程都有一個ProcessState對象,它負責打開Binder驅動,這樣該進程中的所有線程都可以通過Binder驅動通信,而每個線程都會有一個IPCThreadState對象,它才是真正讀寫Binder驅動的主角,它通過talkWithDriver()和驅動打交道,將IPC數據寫入驅動中。

Java-->C++對象轉換

回到我們上面的Demo,客戶端連接成功后拿到了BinderProxy對象,那么這個服務端的Binder對象是如何轉為BinderPrxoy的呢?這點我們要看bindService的源碼,bindService流程很長,感興趣的讀者可以自己去看后者直接看老羅的分析,我這里直接告訴讀者大致過程:

1.客戶端請求bindService,先會請求ActivityManagerService;

2.ActvityManagerService再去找到對應的Service,讓Service所在進程創建并啟動Service;

3.Service調用AMS.publishService()將Binder對象傳遞給AMS;

4.AMS拿到的Binder對象同樣為BinderProxy對象,然后調用 c.conn.connected(r.name, service)方法,將BinderProxy對象傳遞給客戶端。

這里要知道的是,AMS也是處在一個單獨的進程中,所以Binder對象不是直接返回給AMS,AMS也不是直接返回給客戶端的,而是經過了Binder驅動。服務端Service將Binder對象傳遞給AMS時,會調用AMS在服務端的代理對象ActivityManagerProxy.publishService()方法,準確的說,Service端在此時成了AMS的客戶端。

public void publishService(IBinder token,
        Intent intent, IBinder service) throws RemoteException {
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    data.writeInterfaceToken(IActivityManager.descriptor);
    data.writeStrongBinder(token);
    intent.writeToParcel(data, 0);
    data.writeStrongBinder(service);
    mRemote.transact(PUBLISH_SERVICE_TRANSACTION, data, reply, 0);
    reply.readException();
    data.recycle();
    reply.recycle();
}

該方法中通過Parcel.writeStrongBinder(),將服務端的Binder對象轉化成C++中的flat_binder_object對象,并將Binder對象的地址賦值給flat_binder_object對象中的cookie。

struct flat_binder_object {
      unsigned long type;
      unsigned long flags;
      union {
              void *binder; /* local object */
              signed long handle; /* remote object */
      };
      void *cookie;
};

flat_binder_object對象最終被包裝在了data中,然后通過mRemote.transact(),將data數據傳送到IPCThreadState, IPCThreadState再次將data包裝成binder_transaction_data,并調用talkWithDriver(),將包裝好的數據傳遞給Binder驅動。

Binder驅動收到數據后,并不會急著將數據傳給AMS,因為傳送的數據中有flat_binder_object,所以它會查詢flat_binder_object對應的Binder對象在驅動中是否有Binder節點,如果沒有,則會利用flat_binder_object中的cookie值(指向BBinder)創建一個Binder節點,并為該節點生成一個索引值,將索引值賦給flat_binder_object的handle。

然后AMS就可以取數據了,AMS同樣有一個IPCThreadState對象,Binder驅動將數據傳遞給該對象,接收端拿到了數據后,會調用Parcel.readStrongBinder(),這在java層是一個jni方法,該方法會先調用Native層的readStrongBinder(),利用flat_binder_object中的handle值創建BpBinder對象,然后該Native方法返回BpBinder的指針,jni方法再根據BpBinder指針創建java層的BinderProxy對象并返回給java層。至此BpBinder和BinderProxy對象都已經創建完畢。

BpBinder與BBinder的通信

上面我們已經拿到了BpBinder,那BpBinder又將如何與BBinder通信呢?這里不得不提一個重要的變量:handle。還記得在創建BpBiner的時候,需要傳入flat_binder_object的handle嗎,而這個handle是Binder節點在驅動中的索引,即位置。這樣當BpBinder通過transact()調用BBinder時,Binder驅動就可以根據BpBinder提供的handle值找到Binder在驅動中的節點,Binder節點保存了BBinder的地址,從而找到了BBinder,實現對BBinder的調用。

系統服務的注冊

上面的例子展示的匿名Binder的通信,為什么說是匿名,因為Binder對象并沒有在ContextManager中實名注冊。

ContextManager又是什么?它是系統服務的管理者,它在java層和C++層的代理對象都為ServiceManager,系統服務可以通過ServiceManager.addService()將自己實名注冊到ContextManager中。為什么要實名注冊?如果不采用實名的方法,系統服務那么多,你難道一個個的去記得它的handle?

ServiceManager.addService()會將服務的Binder對象傳到Binder Driver,Binder Driver為其生成Binder節點后,ContextManager就會將服務的實名和其Binder節點的索引handle保存到自身。當客戶端需要找一個系統服務時,只需將服務名ServiceManager.getService(),ServiceManager就可以找到服務的索引handle,并創建對應的BpBinder對象,從而建立通信。比如java層的ActivityManagerService就是一個Binder類,我們就可以通過ServiceManager.getService("activity"),拿到它的代理對象,從而進行Activity生命周期的調度。

上面說到ServiceManager是ContextManager的代理,因為ContextManager本身也就是一個服務,服務端和客戶端想調用它的addService或getService時,也必須通過ServiceManager來跨進程??墒俏以撛趺茨玫竭@個代理呢?我總不能調用它的getService來獲取它自己吧。Binder這一點設計的很奇妙,ContextManager在BinderDriver中的節點索引為0,誰讓它是老大呢。這樣大家都知道了,我想調用ContextManger,直接根據handle=0就可以生成它的代理對象BpBinder了,從而創建代理對象ServiceManager。

總結

系統中每個app和系統服務都活在自己的進程中,無法訪問彼此的內存空間,正是因為相互通信的強烈需求,從而誕生了Binder。Binder驅動就像所有進程共同的地盤,系統服務可以這里留下自己的地址,而客戶端可以可以根據這些地址找到對應的服務,相互通信,從而千里姻緣一線牽,百年恩愛雙心結。

(轉載請注明ID:半棧工程師,歡迎訪問個人博客https://halfstackdeveloper.github.io/)

歡迎關注我的知乎專欄:https://zhuanlan.zhihu.com/halfstack

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內容