Binder 牌膠水


Binder牌膠水,如雷貫耳,在Android中無處不在,是每個Android程序猿居家旅行必備。有了它的存在,我們甚至可以不用深入了解App進程和系統進程、用戶空間和內核空間、跨進程通訊等概念也可以做好應用層開發。但是呢,閱讀本文之前,還是建議先看看[《Binder學習指南》][1]了解下這些概念。

Binder屬于“問題領域”的命名?見名知意,作為膠水,“粘合”是他的主要職責。在Androd世界里,他到底粘合了什么?比如我們經常使用的startActivity()就涉及到進程通訊,Binder粘合了這些進程,弱化了我們對進程的感知,降低了開發難度。

本文來聊聊四瓶Binder膠水,他們功效不同,配方卻完全一致。此文的重點有兩個,其一,介紹下他們的配方,也就是類圖模型,了解他們,對我們閱讀Android源碼,尋找源碼的位置有很大的幫助;其二,既然作為膠水,我們要研究下他粘合了什么東西,粘合了哪些進程。這四瓶膠水的功效和配方分別是:

  1. AIDL:
    IInterface--Stub--Proxy--Stub具體實現
  2. ContentProvider:
    IContentProvider--ContentProviderNative--ContentProviderProxy--ContentProvider.Transport
  3. 管理四大組件的AMS:
    IActivityManager--ActivityManagerNative--ActivityManagerProxy--ActivityManagerService
  4. 負責ActivityThread和AMS之間的通訊
    IApplicationThread--ApplicationThreadNative--ApplicationThreadProxy--ApplicationThread

Binder文章很多,很難寫出彩,相比于一言不合就曬C++代碼,曬cpp文件的,本文重點還是從Java層出發解釋Binder機制。本文所有代碼在此:
https://github.com/geniusmart/binder-project

Binder膠水的原始配方

如上圖,IInterface/IBinder/Binder/BinderProxy是Binder機制的核心api,理解這些接口/類,是研究Binder的前提。

接口通常代表所具備的能力,比如我們熟悉的Api里,SerializableParcelable代表其實現類是可序列化的;Iterable代表可迭代遍歷,因此其實現類HashSetArrayList可使用Iterator進行遍歷。

在這個類圖的最頂層,有兩個接口,IInterfaceIBinderIBinder代表跨進程傳輸的能力,而IInterface則代表遠程服務端具備的能力

BinderIBinder的實現類,因此它具備跨進程傳輸的能力,它實際上就是遠程Server端的Binder對象本身

Binder對象是給Server端對象本身,是Server進程用的,與此對應的BinderProxy則是遠程Binder的代理對象,給Client進程用的(源碼位于Binder類內部)。在跨越進程的時候,Binder驅動會自動完成這兩個對象的轉換。

以上都是冷冰冰的理論,讀到這里我們仍然很困惑,我們需要能親手掌控兩個進程:Client進程和Server進程,并進行通訊,以此來熟悉Binder機制。因此,第一瓶膠水應運而生——AIDL,AIDL能實現這個需求。

以上四個類是android.os包給我們提供的Binder機制相關的api,基于這四個類,我們可以擴展出各種各樣的Binder模型,實現各種各樣的跨進程傳輸的場景。而本文所描述的四瓶膠水便是基于此套api的四種實現。

第一瓶膠水AIDL

假設你已經熟練掌握了AIDL,首先寫個ICompute.aidl,代碼很簡單,如下:

// ICompute.aidl
package com.geniusmart.binder.aidl;

interface ICompute {
    int add(int a,int b);
}

此時,編譯器會幫我們生成一個ICompute.java文件,這個類的可讀性很差,為方便大家查閱,我將代碼做了格式化,并拷貝一份放在temp包下,點擊這里查看

這個類的細節我不打算多講,大家可以查看一下文章開篇的那篇文章。對照著生成出來的ICompute.java文件,繪制如下Binder模型圖:

AIDL的Binder模型

1. 熟悉的配方:ICompute-Proxy-Stub-Stub具體實現

通過上圖,我們發現AIDL的Binder模型是基于原始配方的擴展。當我們寫完ICompute.aidl之后,IComputeStubProxy已經自動生成出來,他們的作用如下:

  • ICompute接口繼承了IInterface,并寫了add(a,b)的方法,代表Server端進程具備計算兩數相加的能力。此方法將在Stub的具體實現類中重寫。
  • Stub是一個抽象類,他本質是一個Binder,他存在的目的之一是約定好asInterfaceasBinderonTransact方法,減少我們開發具體Binder對象的工作量。此外,Stub即需要跨進程傳輸,又需要約定遠端服務端具備的能力,因此他需要同時實現IInterfaceIBinder接口,通過上文的類圖可以看得出來。
  • Proxy是代理對象,它在Client進程中使用,作用是以假亂真。他持有BinderProxy對象,BinderProxy能幫我們訪問服務端Binder對象(本例中即Stub具體實現類)的能力。

AIDL讓我們可以飯來張口衣來伸手,但是,服務端具備的具體能力,AIDL是沒法給的,需要我們自力更生,所以接下來我們來寫個Stub的具體實現,并驗證下這個Binder模型的準確性。

2.驗證遠程對象和代理對象

在Server進程啟動一個Service,定義Stub的實現類ComputeBinder,等待Client進程的連接,代碼如下:

public class ComputeService extends Service {

    public static final String TAG = "Server進程";

    @Override
    public IBinder onBind(Intent intent) {
        return new ComputeBinder();
    }

    private static class ComputeBinder extends ICompute.Stub {

        @Override
        public int add(int a, int b) throws RemoteException {
            Log.i(TAG, TAG + this.getClass().getName() + "執行add()");
            return a + b;
        }
    }
}

接下來,我們來寫連接遠程服務的代碼 ,核心代碼如下:(完整代碼點擊查看)

private ServiceConnection conn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.i(TAG, TAG + "觸發onServiceConnected : " + service.getClass().getName());
        mICompute = ICompute.Stub.asInterface(service);
        Log.i(TAG, TAG + "觸發asInterface : " + mICompute.getClass().getName());
        Log.i(TAG, TAG + "觸發add() : 遠程方法執行結果為" + mICompute.add(3, 5));
    }
};```
這里我設計了兩種場景,分別讓Server進程和Client進程執行連接遠程服務,輸出結果如下:
- Client進程綁定Server進程的Service獲取Binder
  (1)Client進程輸出日志:

Client進程觸發onServiceConnected : android.os.BinderProxy
Client進程觸發asInterface : ICompute$Stub$Proxy
Client進程觸發add() : result = 8

(2)Server進程輸出日志:

Server進程ComputeService$ComputeBinder執行add()

- Server進程綁定自身進程內的Service獲取Binder
Server進程輸出日志:

Server進程觸發onServiceConnected : ComputeService$ComputeBinder
Server進程觸發asInterface : ComputeService$ComputeBinder
Server進程ComputeService$ComputeBinder執行add()
Server進程觸發add() : 遠程方法執行結果為8


結論:
- 當Client進程連接遠程時,會經過如下步驟:客戶端獲得`BinderProxy`對象->轉換為`ICompute$Stub$Proxy`->服務端`ComputeService$ComputeBinder`執行相應的方法,并將數據返回給客戶端->客戶端取得數據。
- 當Server進程連接自身時,始終調用的是Binder本體對象`ComputeService$ComputeBinder`

#### 3.工作原理
說好了不談細節,但是對于講清楚Binder機制來說,不談細節臣妾真的做不到,受限于篇幅,我還是克制住了,取而代之的是一張基于aidl的工作流程圖,與其貼代碼講細節,還不如大家對照源碼和流程圖自己解析aidl的工作原理。如下:

![](http://upload-images.jianshu.io/upload_images/638283-cba5ef060b5ffa63.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

####4. 這瓶膠水粘合了什么?
這個問題對于AIDL來說很簡單,他粘合了需要通訊的兩個進程,在上文我們稱之為Server進程和Client進程,在上文的UML類圖中也有所體現。

##第二瓶膠水ContentProvider

為了鞏固這個模型,Binder牌膠水隆重推出第二個款式ContentProvider。談起跨進程,我們自然而然會想起內容提供者。即使你不了解ContentProvider的原理,我們平常在使用api的時候,所做的事情就是讓**內容使用者進程**去訪問**內容提供者進程**的數據,所以這第二瓶膠水很貼近我們日常的Android開發生活。

首先先給出結論,也就是有關ContentProvider的Binder模型圖:

![](http://upload-images.jianshu.io/upload_images/638283-e2196c706abe8b14.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

####1. ContentProvider Binder模型來源
接下來我們來分析下這張圖是怎么來的:
作為一名Coder,應該永遠保持好奇心,使用`ContentProvider`時,我們經常用這樣的api:`getContentResolver().query()/insert()/update()/delete()`,你只要點進去看看源碼,都能很輕易發現這個模型圖,這個過程大概是這樣的:
- (1) 從內容使用者的角度出發,查看`insert()`源碼
- (2) 發現關鍵代碼`IContentProvider provider = acquireProvider(url);`
- (3) 活捉一只`IInterface`的子接口`IContentProvider`,對應AIDL的`ICompute`
- (4) 那么問題來了,對應的`Proxy\Stub\Stub具體實現`在哪?一籌莫展中。
- (5) 此路不通,換一條路,即從內容提供者角度出發,查看 `ContentProvider`的源碼,找找線索。
- (6) AS切換到Structure視圖,觀察類的結構,此時應該有點運氣成分,我們發現了內部類`Transport`,他的繼承關系是:`class Transport extends ContentProviderNative{}`。雖說此步需要點運氣,但是`Transport`的中文意思為運輸,頗有點跨進程傳輸的意思,所以也并非毫無根據。
- (7) 看`ContentProviderNative`源碼,發現其繼承關系`class ContentProviderNative extends Binder implements IContentProvider{}`,這就是我們熟悉的模型啊,對應AIDL中的`Stub`,而`Transport`則對應著`Stub的`具體實現類`ComputeBinder`。
- (8) 在`ContentProviderNative`內部,與他同一級的還有一個類,即`ContentProviderProxy`,至此,Binder模型相關的4個類或接口,我們已經集齊完畢。
- (9) 最后,召喚神龍,深入學習ContentProvider的原理。

#### 2.驗證遠程對象和代理對象

`ContentProvider`與AIDL不一樣,AIDL中Binder對象可以輕易拿到,而我們在使用`ContentProvider`時候,通常都是通過`ContentResolver`來執行CRUD的操作,顯然,他并不是一個Binder對象,追溯其源碼,我們可以發現這樣一行關鍵的代碼:

public final @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues values) {

IContentProvider provider = acquireProvider(url);
//省略若干代碼..

}

看到返回類型`IContentProvider`時就是你大聲呼喊“真相只有一個”的時候,閱讀源碼的樂趣就在于抽絲剝繭,輕解衣裳,窺探最本真和純潔的胴體。。。

這時候問題又來了,`acquireProvider(url)`是一個`@hide`方法,沒法直接調用。解決辦法很簡單,即使用反射。核心代碼如下:

Uri uri = Uri.parse("content://com.geniusmart.binder.AccountProvider/account");
ContentResolver contentResolver = getContentResolver();
//通過反射調用hide方法
Method method = ContentResolver.class.getMethod("acquireProvider", new Class[]{Uri.class});
Object object = method.invoke(contentResolver, uri);
//打印Binder類型
Log.i(TAG, object.getClass().toString());


與AIDL中的驗證思路一樣,我們來分別驗證下客戶端和服務端執行此段邏輯之后的結果:
- Client進程使用Server進程的ContentProvider
輸出結果為:`class android.content.ContentProviderProxy`
- Server進程使用自己的ContentProvider
輸出結果為:`class android.content.ContentProvider$Transport`

結論:Client進程通過`ContentProviderProxy`訪問Server進程的`ContentProvider$Transport`,實現進程間通訊。

以上所有代碼均在文章開篇的Github項目里。

#### 3. 這瓶膠水粘合了什么?
這個問題也比較簡單,ContentProvider這瓶膠水粘合了內容使用者和內容提供者兩個進程,寫`ContentProvider`的這一方稱之為Server進程,用`ContentResolver`的這一方稱之為Client進程,兩個進程通過Binder進行通訊。

##另外兩瓶膠水AMS和ApplicationThread
AIDL和ContentProvider這兩個Binder模型,都有清晰的Client進程和Server進程,理解起來相對容易,而另外兩個Binder模型所涉及到的進程雙方則比較模糊。AMS和ApplicationThread有著千絲萬縷的關系,所以我們放在一起講。首先貼一下這兩瓶膠水的配方:

![AMS的Binder模型](http://upload-images.jianshu.io/upload_images/638283-205b10c917ed116c.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


![ApplicationThread的Binder模型](http://upload-images.jianshu.io/upload_images/638283-0b389d01130fecd3.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

看到這兩個Binder模型圖,真是感慨,這簡直是一樣的配方,熟悉的味道,所以以后如果有看到類似`IXxxx/XxxxProxy/XxxxNative/Xxxx`的命名規則,一定要想起這是Binder的配方。

#### Binder模型來源
這兩個Binder模型圖從何而來?
在一個夜黑風高的晚上,你我寂寞難耐,準備Fuck Source Code,于是決定挑個并不軟的柿子來捏一捏,他就是`startActivity()`,大概會這么一些個前戲:
(1)`context.startActivity()`
(2)`mBase.startActivity()`
(3)`ContextImpl.startActivity()`
(4)`mMainThread.getInstrumentation().execStartActivity()`
(5)`Instrumentation.execStartActivity()`

源碼閱到這里已經有一個小高潮了,因為我們發現了如下代碼:

//IApplicationThread 是IInterface類型
IApplicationThread whoThread = (IApplicationThread) contextThread;
//ActivityManagerNative是Binder和IInterface類型
ActivityManagerNative.getDefault().startActivityAsUser()

此時Binder機制的雛形我們已經心中有數,之后少俠們可以各自發揮,深入理解這兩個Binder機制的作用,獲得更多的快感。

所以,理解這個模型的意義在于,當我們看到XxxxProxy執行邏輯時,當前進程所屬的角色是Client進程,要查看該邏輯源碼的時候,應該找到Server進程的Xxxx類查看。

Activity的啟動流程的解析漫長而枯燥,不是此文的重點,大家了解下這兩個類的意義即可:
- `ActivityManagerService`
AMS是Android中最核心的服務,主要負責系統中四大組件的啟動、切換、調度及應用進程的管理和調度等工作。
- `ApplicationThread`
`ApplicationThread`是`ActivityThread`的內部類,負責`ActivityThread`和`ActivityManagerService`之間的通訊。


#### 這兩瓶膠水粘合了哪些進程?

通過Activity啟動流程圖我們來看下這兩個Binder模型是如何發揮作用的,如下圖:

![Activity啟動流程圖](http://upload-images.jianshu.io/upload_images/638283-2168b0e80a01bd8f.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

*注:此張圖片來源于這篇文章[《startActivity流程分析(一)》][2],已征得博主同意引用在此處。*

在這張圖里,我們意識到除了第一、二瓶膠水描述到的App進程之外,Android世界里還有Launcher進程,系統進程等等,而Binder在其中如魚得水,散發著萬丈光芒。

`AMS`位于系統進程,處于Server進程的位置,Launcher進程和App進程作為Client進程,持有`ActivityManagerProxy`,與AMS進行通訊,召喚四大組件。而`ApplicationThread`位于應用進程,處于Server進程的位置,系統進程則作為Client進程,持有`ApplicationThreadProxy`,使得應用進程中的主線程(`ActivityThread`)和AMS之間可以進行通訊。

至此,對于Binder機制的認知應該要有所升華。

##總結
用一張表格描述一下上文所講的四個Binder模型:

| 能力     |    IInterface| Binder抽象|BinderProxy|Binder|
| -------- | --------| -- | -- | -- |
| AIDL|  `ICompute` |  `Stub`   | `Proxy`|`ComputeBinder`|
| 內容提供者     |   `IContentProvider` |  `ContentProviderNative`  | `ContentProviderProxy`|`ContentProvider.Transport`|
| AMS|   `IActivityManager` | `ActivityManagerNative`  |`ActivityManagerProxy`|`ActivityManagerService`|
| ActivityThread和AMS之間的通訊      |    `IApplicationThread` | `ApplicationThreadNative`  |`ApplicationThreadProxy`|`ApplicationThread`|

Binder并不神秘,對于應用層來說,Binder便是上文中我所繪制的各種模型圖,我們需要明確兩點,**第一,Proxy作為代理對象,以假亂真,Client進程通過持有Proxy對象,進而調用Server進程中實際Binder對象的能力;第二,在使用任何一個Binder對象時,我們要明確,此時進行通訊的是哪兩個進程,以及哪個進程充當Client或Server進程。**

最后多說一句,關于Binder寫了那么多,大牛們自成體系自然無需閱讀此小文,而小小牛們看完之后也許仍然十分懵懂,所以寫Binder文章略顯尷尬,而在這個過程中收獲最大的其實是作者本人。學習Binder,沒有捷徑,找準切入點(本文的切入點在于應用層的Binder模型以及每種模型涉及通訊的兩個進程),然后Read the Fucking Source Code,最后寫寫文章做總結。

##參考文章
[Binder學習指南][1]
[startActivity流程分析(一)][2]
[Android應用程序組件Content Provider簡要介紹和學習計劃](http://blog.csdn.net/luoshengyang/article/details/6946067)
[Android進程間通信(IPC)機制Binder簡要介紹和學習計劃](http://blog.csdn.net/luoshengyang/article/details/6618363)

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

推薦閱讀更多精彩內容

  • 原文:http://weishu.me/2016/01/12/binder-index-for-newer/ 要點...
    指尖流逝的青春閱讀 2,613評論 0 13
  • 毫不夸張地說,Binder是Android系統中最重要的特性之一;正如其名“粘合劑”所喻,它是系統間各個組件的橋梁...
    weishu閱讀 17,894評論 29 246
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,775評論 18 139
  • Android跨進程通信IPC整體內容如下 1、Android跨進程通信IPC之1——Linux基礎2、Andro...
    隔壁老李頭閱讀 11,973評論 11 56
  • Binder淺析 1. 背景知識 Binder在Android系統中是用來進行進程間通信的,所以在介紹Binder...
    蕉下孤客閱讀 2,312評論 0 8