Binder牌膠水,如雷貫耳,在Android中無處不在,是每個Android程序猿居家旅行必備。有了它的存在,我們甚至可以不用深入了解App進程和系統(tǒng)進程、用戶空間和內核空間、跨進程通訊等概念也可以做好應用層開發(fā)。但是呢,閱讀本文之前,還是建議先看看《Binder學習指南》了解下這些概念。
Binder屬于“問題領域”的命名?見名知意,作為膠水,“粘合”是他的主要職責。在Androd世界里,他到底粘合了什么?比如我們經(jīng)常使用的startActivity()
就涉及到進程通訊,Binder粘合了這些進程,弱化了我們對進程的感知,降低了開發(fā)難度。
本文來聊聊四瓶Binder膠水,他們功效不同,配方卻完全一致。此文的重點有兩個,其一,介紹下他們的配方,也就是類圖模型,了解他們,對我們閱讀Android源碼,尋找源碼的位置有很大的幫助;其二,既然作為膠水,我們要研究下他粘合了什么東西,粘合了哪些進程。這四瓶膠水的功效和配方分別是:
- AIDL:
IInterface--Stub--Proxy--Stub具體實現(xiàn)
- ContentProvider:
IContentProvider--ContentProviderNative--ContentProviderProxy--ContentProvider.Transport
- 管理四大組件的AMS:
IActivityManager--ActivityManagerNative--ActivityManagerProxy--ActivityManagerService
- 負責ActivityThread和AMS之間的通訊
IApplicationThread--ApplicationThreadNative--ApplicationThreadProxy--ApplicationThread
Binder文章很多,很難寫出彩,相比于一言不合就曬C++代碼,曬cpp文件的,本文重點還是從Java層出發(fā)解釋Binder機制。本文所有代碼在此:
https://github.com/geniusmart/binder-project
Binder膠水的原始配方
如上圖,IInterface/IBinder/Binder/BinderProxy是Binder機制的核心api,理解這些接口/類,是研究Binder的前提。
接口通常代表所具備的能力,比如我們熟悉的Api里,Serializable
和Parcelable
代表其實現(xiàn)類是可序列化的;Iterable
代表可迭代遍歷,因此其實現(xiàn)類HashSet
和ArrayList
可使用Iterator
進行遍歷。
在這個類圖的最頂層,有兩個接口,IInterface
和IBinder
。IBinder
代表跨進程傳輸?shù)哪芰?/strong>,而IInterface
則代表遠程服務端具備的能力。
Binder
是IBinder
的實現(xiàn)類,因此它具備跨進程傳輸?shù)哪芰Γ鼘嶋H上就是遠程Server端的Binder對象本身。
Binder
對象是給Server端對象本身,是Server進程用的,與此對應的BinderProxy
則是遠程Binder
的代理對象,給Client進程用的(源碼位于Binder
類內部)。在跨越進程的時候,Binder驅動會自動完成這兩個對象的轉換。
以上都是冷冰冰的理論,讀到這里我們仍然很困惑,我們需要能親手掌控兩個進程:Client進程和Server進程,并進行通訊,以此來熟悉Binder機制。因此,第一瓶膠水應運而生——AIDL,AIDL能實現(xiàn)這個需求。
以上四個類是
android.os
包給我們提供的Binder機制相關的api,基于這四個類,我們可以擴展出各種各樣的Binder模型,實現(xiàn)各種各樣的跨進程傳輸?shù)膱鼍啊6疚乃枋龅乃钠磕z水便是基于此套api的四種實現(xiàn)。
第一瓶膠水AIDL
假設你已經(jīng)熟練掌握了AIDL,首先寫個ICompute.aidl
,代碼很簡單,如下:
// ICompute.aidl
package com.geniusmart.binder.aidl;
interface ICompute {
int add(int a,int b);
}
此時,編譯器會幫我們生成一個ICompute.java文件,這個類的可讀性很差,為方便大家查閱,我將代碼做了格式化,并拷貝一份放在temp包下,點擊這里查看。
這個類的細節(jié)我不打算多講,大家可以查看一下文章開篇的那篇文章。對照著生成出來的ICompute.java文件,繪制如下Binder模型圖:
1. 熟悉的配方:ICompute-Proxy-Stub-Stub具體實現(xiàn)
通過上圖,我們發(fā)現(xiàn)AIDL的Binder模型是基于原始配方的擴展。當我們寫完ICompute.aidl之后,ICompute
,Stub
,Proxy
已經(jīng)自動生成出來,他們的作用如下:
-
ICompute
接口繼承了IInterface
,并寫了add(a,b)
的方法,代表Server端進程具備計算兩數(shù)相加的能力。此方法將在Stub的具體實現(xiàn)
類中重寫。 -
Stub
是一個抽象類,他本質是一個Binder
,他存在的目的之一是約定好asInterface
,asBinder
,onTransact
方法,減少我們開發(fā)具體Binder
對象的工作量。此外,Stub
即需要跨進程傳輸,又需要約定遠端服務端具備的能力,因此他需要同時實現(xiàn)IInterface
和IBinder
接口,通過上文的類圖可以看得出來。 -
Proxy
是代理對象,它在Client進程中使用,作用是以假亂真。他持有BinderProxy
對象,BinderProxy
能幫我們訪問服務端Binder對象(本例中即Stub具體實現(xiàn)類
)的能力。
AIDL讓我們可以飯來張口衣來伸手,但是,服務端具備的具體能力,AIDL是沒法給的,需要我們自力更生,所以接下來我們來寫個Stub的具體實現(xiàn)
,并驗證下這個Binder模型的準確性。
2.驗證遠程對象和代理對象
在Server進程啟動一個Service,定義Stub的實現(xiàn)類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() + "執(zhí)行add()");
return a + b;
}
}
}
接下來,我們來寫連接遠程服務的代碼 ,核心代碼如下:(完整代碼點擊查看)
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, TAG + "觸發(fā)onServiceConnected : " + service.getClass().getName());
mICompute = ICompute.Stub.asInterface(service);
Log.i(TAG, TAG + "觸發(fā)asInterface : " + mICompute.getClass().getName());
Log.i(TAG, TAG + "觸發(fā)add() : 遠程方法執(zhí)行結果為" + mICompute.add(3, 5));
}
};
這里我設計了兩種場景,分別讓Server進程和Client進程執(zhí)行連接遠程服務,輸出結果如下:
- Client進程綁定Server進程的Service獲取Binder
(1)Client進程輸出日志:
Client進程觸發(fā)onServiceConnected : android.os.BinderProxy
Client進程觸發(fā)asInterface : ICompute$Stub$Proxy
Client進程觸發(fā)add() : result = 8
(2)Server進程輸出日志:
Server進程ComputeService$ComputeBinder執(zhí)行add()
- Server進程綁定自身進程內的Service獲取Binder
Server進程輸出日志:
Server進程觸發(fā)onServiceConnected : ComputeService$ComputeBinder
Server進程觸發(fā)asInterface : ComputeService$ComputeBinder
Server進程ComputeService$ComputeBinder執(zhí)行add()
Server進程觸發(fā)add() : 遠程方法執(zhí)行結果為8
結論:
- 當Client進程連接遠程時,會經(jīng)過如下步驟:客戶端獲得
BinderProxy
對象->轉換為ICompute$Stub$Proxy
->服務端ComputeService$ComputeBinder
執(zhí)行相應的方法,并將數(shù)據(jù)返回給客戶端->客戶端取得數(shù)據(jù)。 - 當Server進程連接自身時,始終調用的是Binder本體對象
ComputeService$ComputeBinder
3.工作原理
說好了不談細節(jié),但是對于講清楚Binder機制來說,不談細節(jié)臣妾真的做不到,受限于篇幅,我還是克制住了,取而代之的是一張基于aidl的工作流程圖,與其貼代碼講細節(jié),還不如大家對照源碼和流程圖自己解析aidl的工作原理。如下:
4. 這瓶膠水粘合了什么?
這個問題對于AIDL來說很簡單,他粘合了需要通訊的兩個進程,在上文我們稱之為Server進程和Client進程,在上文的UML類圖中也有所體現(xiàn)。
第二瓶膠水ContentProvider
為了鞏固這個模型,Binder牌膠水隆重推出第二個款式ContentProvider。談起跨進程,我們自然而然會想起內容提供者。即使你不了解ContentProvider的原理,我們平常在使用api的時候,所做的事情就是讓內容使用者進程去訪問內容提供者進程的數(shù)據(jù),所以這第二瓶膠水很貼近我們日常的Android開發(fā)生活。
首先先給出結論,也就是有關ContentProvider的Binder模型圖:
1. ContentProvider Binder模型來源
接下來我們來分析下這張圖是怎么來的:
作為一名Coder,應該永遠保持好奇心,使用ContentProvider
時,我們經(jīng)常用這樣的api:getContentResolver().query()/insert()/update()/delete()
,你只要點進去看看源碼,都能很輕易發(fā)現(xiàn)這個模型圖,這個過程大概是這樣的:
- (1) 從內容使用者的角度出發(fā),查看
insert()
源碼 - (2) 發(fā)現(xiàn)關鍵代碼
IContentProvider provider = acquireProvider(url);
- (3) 活捉一只
IInterface
的子接口IContentProvider
,對應AIDL的ICompute
- (4) 那么問題來了,對應的
Proxy\Stub\Stub具體實現(xiàn)
在哪?一籌莫展中。 - (5) 此路不通,換一條路,即從內容提供者角度出發(fā),查看
ContentProvider
的源碼,找找線索。 - (6) AS切換到Structure視圖,觀察類的結構,此時應該有點運氣成分,我們發(fā)現(xiàn)了內部類
Transport
,他的繼承關系是:class Transport extends ContentProviderNative{}
。雖說此步需要點運氣,但是Transport
的中文意思為運輸,頗有點跨進程傳輸?shù)囊馑迹砸膊⒎呛翢o根據(jù)。 - (7) 看
ContentProviderNative
源碼,發(fā)現(xiàn)其繼承關系class ContentProviderNative extends Binder implements IContentProvider{}
,這就是我們熟悉的模型啊,對應AIDL中的Stub
,而Transport
則對應著Stub的
具體實現(xiàn)類ComputeBinder
。 - (8) 在
ContentProviderNative
內部,與他同一級的還有一個類,即ContentProviderProxy
,至此,Binder模型相關的4個類或接口,我們已經(jīng)集齊完畢。 - (9) 最后,召喚神龍,深入學習ContentProvider的原理。
2.驗證遠程對象和代理對象
ContentProvider
與AIDL不一樣,AIDL中Binder對象可以輕易拿到,而我們在使用ContentProvider
時候,通常都是通過ContentResolver
來執(zhí)行CRUD的操作,顯然,他并不是一個Binder對象,追溯其源碼,我們可以發(fā)現(xiàn)這樣一行關鍵的代碼:
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](https://link.jianshu.com?t=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中的驗證思路一樣,我們來分別驗證下客戶端和服務端執(zhí)行此段邏輯之后的結果:
- Client進程使用Server進程的ContentProvider
輸出結果為:class android.content.ContentProviderProxy
- Server進程使用自己的ContentProvider
輸出結果為:class android.content.ContentProvider$Transport
結論:Client進程通過ContentProviderProxy
訪問Server進程的ContentProvider$Transport
,實現(xiàn)進程間通訊。
以上所有代碼均在文章開篇的Github項目里。
3. 這瓶膠水粘合了什么?
這個問題也比較簡單,ContentProvider這瓶膠水粘合了內容使用者和內容提供者兩個進程,寫ContentProvider
的這一方稱之為Server進程,用ContentResolver
的這一方稱之為Client進程,兩個進程通過Binder進行通訊。
另外兩瓶膠水AMS和ApplicationThread
AIDL和ContentProvider這兩個Binder模型,都有清晰的Client進程和Server進程,理解起來相對容易,而另外兩個Binder模型所涉及到的進程雙方則比較模糊。AMS和ApplicationThread有著千絲萬縷的關系,所以我們放在一起講。首先貼一下這兩瓶膠水的配方:
看到這兩個Binder模型圖,真是感慨,這簡直是一樣的配方,熟悉的味道,所以以后如果有看到類似IXxxx/XxxxProxy/XxxxNative/Xxxx
的命名規(guī)則,一定要想起這是Binder的配方。
Binder模型來源
這兩個Binder模型圖從何而來?
在一個夜黑風高的晚上,你我寂寞難耐,準備Fuck Source Code,于是決定挑個并不軟的柿子來捏一捏,他就是startActivity()
,大概會這么一些個前戲:
(1)context.startActivity()
(2)mBase.startActivity()
(3)ContextImpl.startActivity()
(4)mMainThread.getInstrumentation().execStartActivity()
(5)Instrumentation.execStartActivity()
源碼閱到這里已經(jīng)有一個小高潮了,因為我們發(fā)現(xiàn)了如下代碼:
//IApplicationThread 是IInterface類型
IApplicationThread whoThread = (IApplicationThread) contextThread;
//ActivityManagerNative是Binder和IInterface類型
ActivityManagerNative.getDefault().startActivityAsUser()
此時Binder機制的雛形我們已經(jīng)心中有數(shù),之后少俠們可以各自發(fā)揮,深入理解這兩個Binder機制的作用,獲得更多的快感。
所以,理解這個模型的意義在于,當我們看到XxxxProxy執(zhí)行邏輯時,當前進程所屬的角色是Client進程,要查看該邏輯源碼的時候,應該找到Server進程的Xxxx類查看。
Activity的啟動流程的解析漫長而枯燥,不是此文的重點,大家了解下這兩個類的意義即可:
-
ActivityManagerService
AMS是Android中最核心的服務,主要負責系統(tǒng)中四大組件的啟動、切換、調度及應用進程的管理和調度等工作。 -
ApplicationThread
ApplicationThread
是ActivityThread
的內部類,負責ActivityThread
和ActivityManagerService
之間的通訊。
這兩瓶膠水粘合了哪些進程?
通過Activity啟動流程圖我們來看下這兩個Binder模型是如何發(fā)揮作用的,如下圖:
注:此張圖片來源于這篇文章《startActivity流程分析(一)》,已征得博主同意引用在此處。
在這張圖里,我們意識到除了第一、二瓶膠水描述到的App進程之外,Android世界里還有Launcher進程,系統(tǒng)進程等等,而Binder在其中如魚得水,散發(fā)著萬丈光芒。
AMS
位于系統(tǒng)進程,處于Server進程的位置,Launcher進程和App進程作為Client進程,持有ActivityManagerProxy
,與AMS進行通訊,召喚四大組件。而ApplicationThread
位于應用進程,處于Server進程的位置,系統(tǒng)進程則作為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寫了那么多,大牛們自成體系自然無需閱讀此小文,而小小牛們看完之后也許仍然十分懵懂,所以寫B(tài)inder文章略顯尷尬,而在這個過程中收獲最大的其實是作者本人。學習Binder,沒有捷徑,找準切入點(本文的切入點在于應用層的Binder模型以及每種模型涉及通訊的兩個進程),然后Read the Fucking Source Code,最后寫寫文章做總結。
參考文章
Binder學習指南
startActivity流程分析(一)
Android應用程序組件Content Provider簡要介紹和學習計劃
Android進程間通信(IPC)機制Binder簡要介紹和學習計劃
作者:geniusmart
鏈接:http://www.lxweimin.com/p/3d053abba04b
來源:簡書
著作權歸作者所有。商業(yè)轉載請聯(lián)系作者獲得授權,非商業(yè)轉載請注明出處。