原文連接:banshan.tech/Binder概述/java
本文分析基於Android P(9.0) 源碼android
在編程的世界中,不一樣進程間的通訊、協同、合做隨處可見。不少時候,人們習慣用IPC(I
nter P
rocess C
ommunication,跨進程通訊)來稱呼它們。譬如Binder在多數狀況下也被稱爲Android世界中的IPC機制。但若是以應用開發者的視角來看,Binder也能夠稱爲Android世界中的RPC(R
emote P
rocedure C
all,遠程過程調用)機制。git
IPC(I
nter P
rocess C
ommunication,跨進程通訊)github
泛指一切用於進程間傳遞信息量(傳輸數據只是傳遞信息量的一個子集)的方式,譬如socket/pipe/FIFO/semaphore等。這些名稱表徵的是信息傳輸的具體方式,而不涉及信息的處理和加工。算法
RPC(R
emote P
rocedure C
all,遠程過程調用)編程
一種建構於IPC基礎之上的方法調用。對於客戶端而言,它能夠感知到的僅僅是方法調用和獲取返回值兩個過程。然而實際上這個方法內部完成了客戶端參數打包,數據傳輸,服務端數據解包,服務端數據處理,服務端結果返回等一系列中間過程。只不過這些過程對於客戶端而言都是「透明」的。因此能夠說IPC只是RPC中的一個環節,除此以外,RPC還包含數據打包,解包以及處理的過程,它們能夠統稱爲信息的處理和加工過程。設計模式
下面舉一個剪貼板的例子,來直觀地呈現RPC的內涵。app
經過以下代碼,咱們能夠將一段文字複製到剪貼板。在執行第8行代碼後,即可以將文本複製到剪貼板上。這裏事先透露下,第8行代碼本質上是一個RPC。socket
1 // 獲取系統剪貼板
2 ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
3
4 // 建立一個剪貼數據集,包含一個普通文本數據條目(須要複製的數據)
5 ClipData clipData = ClipData.newPlainText(null, "須要複製的文本數據");
6
7 // 把數據集設置(複製)到剪貼板
8 clipboard.setPrimaryClip(clipData);
複製代碼
那麼RPC和非RPC的差別到底在什麼地方呢?下面用一幅圖來解釋這個問題。ide
在RPC的過程當中,進程B來進行真正的方法執行,至關於爲進程A提供了某種服務,所以進程B在RPC的過程當中也能夠稱爲Server進程,進程A對應地能夠稱爲Client進程。
接着再來討論一個問題,爲何Android中須要使用大量的RPC?將RPC換成本地方法不能夠麼?
答案是不能夠。由於Android自己是一箇中心化管理的系統,RPC能夠保證一個Server進程管理衆多Client進程的調用請求,且可以實現系統狀態的統一管理。舉個例子,若是咱們在一個App中將一段文字複製到剪貼板,假設這個複製過程是調用本地方法完成的(複製狀態僅侷限於App進程),那麼離開這個App後新的剪貼板就不會再有這段文字。反之,若是咱們採用RPC來完成複製,那麼最終的文字將傳遞給了Server進程。而Server進程一般是常駐內存,因此即使咱們離開App,剪貼板上的文字也依然存在,保證它能夠被粘貼到其餘App中。在Android中,大量的系統服務都是在system_server進程中完成各自功能的,譬如ActivityManagerService。
在進行具體闡述以前,咱們先要作一個限定。如下所討論的service是相似於ActivityManagerService、PackageManagerService、WindowManagerService等一系列的服務,它們是Binder語義下的service,而不是Android四大組件中的service。
不過「服務(service)」究竟是什麼意思呢?它是一類近似功能的統稱。譬如「商人的服務」,就能夠包含「購買薯片」、「購買可樂」、「購買沙發」、「購買電視」等一系列功能。ActivityManagerService是一個類,它裏面定義實現了不少方法,詳細描述了每一項功能應該來如何提供。可是它只是一個模板,是沒法實際提供服務的。真正提供服務的是ActivityManagerService實例化出來的對象。在Android中,ActivityManagerService只會實例化出來一個對象,而它就是真正爲應用提供AMS服務的人。
/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
2877 public static final class Lifecycle extends SystemService {
2878 private final ActivityManagerService mService;
2879
2880 public Lifecycle(Context context) {
2881 super(context);
2882 mService = new ActivityManagerService(context);
2883 }
複製代碼
這個對象是在system_server進程啓動的時候建立出來的,具體是在ActivityManagerService.Lifecycle的構造過程當中完成。
/frameworks/base/services/java/com/android/server/SystemServer.java
559 mActivityManagerService = mSystemServiceManager.startService(
560 ActivityManagerService.Lifecycle.class).getService();
複製代碼
因爲整個系統中,ActivityManagerService實例化的對象只有一個,因此將它稱爲「AMS」、「AMS實例化的對象」都無所謂。這就比如整個飯店就一個廚師,你叫他「廚師」仍是「王廚師」都不影響理解。
若是將service看做一個能夠提供服務的Java對象,那麼這個問題將會迎刃而解。
Java對象是存放在堆上的,所以能夠在同一個進程裏的不一樣線程間共享,因此service(準確來講應該是service對象的方法)也能夠在不一樣的線程裏運行。
另外,一個進程裏能夠構造出成千上萬的對象,所以一個進程裏也能夠存在成千上萬的service。並且同一個類型的service也能夠存在不少個,譬如咱們能夠在system_server進程中同時構造ActivityManagerService對象A/B/C,三個AMS對象表示三個能夠提供服務的實體,這就比如飯店裏如今有了三個廚師,你能夠跟其中任何一個請求服務。固然,在實際的Android系統中,同一個類型的service多數狀況下只有一個對象。但現實狀況不存在並不表明理論上不可實現,因此理論上同一個類型的service能夠在一個進程中存在多個對象。
本文開篇提到,對於客戶端而言,它能夠感知到的僅僅是方法調用和獲取返回值兩個過程。那麼具體到剪貼板這個例子來的話,對於Android應用的開發者而言,他感知到的只有下面兩件事:
對於Android應用的開發者而言,他感知到的只有下面兩件事:
在這個過程當中,應用開發者根本感知不到這是一次跨進程的調用,也感覺不到調用背後的數據傳輸。RPC機制將這一切都封裝了起來,所以開發者能夠天真地認爲全部這一切都發生在應用進程。而這也正是系統設計者但願給開發者帶去的便利和簡化,既是理解上的簡化,也是使用上的簡化。
不過一個有追求的開發者一般只會選擇使用上的簡化,而不會侷限在理解上的簡化。因此下面我將用一種頗具趣味性的方式來繼續闡述。
全部的算法和設計模式都是從社會生活中抽象提煉出來的。因此本着「從羣衆中來,到羣衆中去」的原則,咱們賦予冰冷的源碼以生命,從社會生活的角度來理解RPC,來理解Binder。
前幾年,城市裏面新建了幾座各具特點的人才中心,每個中心都匯聚了來自五湖四海的奇人異士。他們中有手藝精湛的廚師,有投機倒把的商人,有妙筆生花的做家,還有勤勤懇懇的果農。人才中心有不少電話間,方便他們打電話的時候互不干擾。有一天,小明想從人才中心A裏面的賈商人那裏買一個鍵盤,因而撥通了A大廈的電話……
上面的例子徹底能夠映射成一次RPC。人才中心表示進程,電話間表示線程,職業表示service所屬的類,賈商人表示一個具體的提供service的對象,買鍵盤表示服務的方法,打電話表示數據傳輸的方式,小明表示Client進程。
小明打電話給人才中心A:Client進程跟Server進程A通訊,除此以外,Client進程也能夠跟Server進程B/C/D通訊。
小明想找賈商人買一個鍵盤(僞代碼表示以下):
第1行表示賈商人出現了,第2行表示他在人才登記處(非人才中心)將本身的名字登記在冊。
1 Merchant jia = new Merchant("Jia");
2 ServiceManager.addService("MerchantJia", jia);
複製代碼
下面的代碼表示小明打電話購買鍵盤的過程。第1行表示他從人才登記處要到了賈商人的號碼,第2行表示他打電話給賈商人提出購買鍵盤的請求。賈商人接到請求後,立馬發貨,返回值result代表小明收到了鍵盤。
1 IMerchant merchantJia = IMerchant.Stub.asInterface(ServiceManager.getService("MerchantJia"));
2 Keyboard result = merchantJia.buyKeyboard();
複製代碼
那若是小明不想找賈商人買了,換成甄商人會怎麼樣?
甄商人在人才登記處(非人才中心)登記:
1 Merchant zhen = new Merchant("Zhen");
2 ServiceManager.addService("MerchantZhen", zhen);
複製代碼
小明打電話購買鍵盤:
1 IMerchant merchantZhen = IMerchant.Stub.asInterface(ServiceManager.getService("MerchantZhen"));
2 Keyboard result = merchantZhen.buyKeyboard();
複製代碼
不論是賈商人仍是甄商人,他們都是商人,所以都是由Merchant類實例化而來。所以,職業「商人」就映射爲了「Merchant」類,而類實例化出來的對象就是具體提供service的對象(賈商人和甄商人),表示提供某一類服務的實體。
賈商人去電話間接電話:
人才中心接線員收到小明的電話,說要找賈商人,因而給賈商人分配了電話間D。之因此分配D,是由於A/B/C三個電話間如今都有人在使用。類比回源碼,人才中心表示Server進程, 電話間表示線程。
人才中心接收到小明的請求,表示Server進程接收到Client進程的數據。以後便決定將它交由賈商人處理,表示Server進程會將Client傳輸過來的數據交由MerchantJia這個對象來處理。接着是分配電話間,A/B/C三個電話間正在被使用,表示目前有三個Binder線程正在處理其餘請求。因而最終將電話間D分配給賈商人,表示service的方法接下來將運行在線程D中。
接下來以上文中剪貼板複製文本的代碼爲樣本,來闡述一次RPC中所涵蓋的具體過程。
1 // 獲取系統剪貼板
2 ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
3
4 // 建立一個剪貼數據集,包含一個普通文本數據條目(須要複製的數據)
5 ClipData clipData = ClipData.newPlainText(null, "須要複製的文本數據");
6
7 // 把數據集設置(複製)到剪貼板
8 clipboard.setPrimaryClip(clipData);
複製代碼
剪貼板服務對象位於system_server進程,而它的代理對象則能夠分佈在全部須要此項服務的App進程中。
所以,剪貼板服務對象的建立也發生在system_server進程(Server進程)。它是ClipboardImpl類型,這個類裏面的方法就是剪貼板服務的具體實現。
/frameworks/base/services/core/java/com/android/server/clipboard/ClipboardService.java
232 private class ClipboardImpl extends IClipboard.Stub {
233 @Override
234 public boolean onTransact(int code, Parcel data, Parcel reply, int flags) 235 throws RemoteException {
236 try {
237 return super.onTransact(code, data, reply, flags);
238 } catch (RuntimeException e) {
239 if (!(e instanceof SecurityException)) {
240 Slog.wtf("clipboard", "Exception: ", e);
241 }
242 throw e;
243 }
244
245 }
246
247 @Override
248 public void setPrimaryClip(ClipData clip, String callingPackage) {
249 synchronized (this) {
250 if (clip == null || clip.getItemCount() <= 0) {
251 throw new IllegalArgumentException("No items");
252 }
253 final int callingUid = Binder.getCallingUid();
254 if (!clipboardAccessAllowed(AppOpsManager.OP_WRITE_CLIPBOARD, callingPackage,
255 callingUid)) {
256 return;
257 }
258 checkDataOwnerLocked(clip, callingUid);
259 setPrimaryClipInternal(clip, callingUid);
260 }
261 }
複製代碼
剪貼板服務對象是在ClipboardService.onStart方法中建立並註冊到ServiceManager中的,整個過程都發生在system_server進程的啓動過程當中。
/frameworks/base/services/core/java/com/android/server/clipboard/ClipboardService.java
192 @Override
193 public void onStart() {
194 publishBinderService(Context.CLIPBOARD_SERVICE, new ClipboardImpl());
195 }
複製代碼
/frameworks/base/services/java/com/android/server/SystemServer.java
1064 traceBeginAndSlog("StartClipboardService");
1065 mSystemServiceManager.startService(ClipboardService.class);
1066 traceEnd();
複製代碼
1 // 獲取系統剪貼板
2 ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
3
4 // 建立一個剪貼數據集,包含一個普通文本數據條目(須要複製的數據)
5 ClipData clipData = ClipData.newPlainText(null, "須要複製的文本數據");
6
7 // 把數據集設置(複製)到剪貼板
8 clipboard.setPrimaryClip(clipData);
複製代碼
尋找代理對象的過程發生在Client進程中。上面第2行的clipboard對象是一層封裝,其內部就是剪貼板服務對象的代理對象(原諒我表述得這麼拗口,但爲了含義的準確表達,犧牲一些語言的美感也無可厚非)。
/frameworks/base/core/java/android/app/ContextImpl.java
1719 @Override
1720 public Object getSystemService(String name) {
1721 return SystemServiceRegistry.getSystemService(this, name);
1722 }
複製代碼
/frameworks/base/core/java/android/app/SystemServiceRegistry.java
1012 public static Object getSystemService(ContextImpl ctx, String name) {
1013 ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
1014 return fetcher != null ? fetcher.getService(ctx) : null;
1015 }
複製代碼
/frameworks/base/core/java/android/app/SystemServiceRegistry.java
178 private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
179 new HashMap<String, ServiceFetcher<?>>();
複製代碼
/frameworks/base/core/java/android/app/SystemServiceRegistry.java
261 registerService(Context.CLIPBOARD_SERVICE, ClipboardManager.class,
262 new CachedServiceFetcher<ClipboardManager>() {
263 @Override
264 public ClipboardManager createService(ContextImpl ctx) throws ServiceNotFoundException {
265 return new ClipboardManager(ctx.getOuterContext(),
266 ctx.mMainThread.getHandler());
267 }});
複製代碼
SYSTEM_SERVICE_FETCHERS是一個HashMap,它以鍵值對的方式存儲了不少系統服務對象的代理對象(或其wrapper對象)。對剪貼板而言,getSystemService方法最終會建立一個ClipboardManager對象並返回。
/frameworks/base/core/java/android/content/ClipboardManager.java
85 public ClipboardManager(Context context, Handler handler) throws ServiceNotFoundException {
86 mContext = context;
87 mHandler = handler;
88 mService = IClipboard.Stub.asInterface(
89 ServiceManager.getServiceOrThrow(Context.CLIPBOARD_SERVICE));
90 }
複製代碼
ClipboardManager的構造方法裏,88行尤其關鍵。首先根據字符串"clipboard"去ServiceManger中找到剪貼板服務對象的代理對象,此時得到的代理對象只具備跨進程通訊的能力。接着經過asInterface爲這個代理對象賦予剪貼板的能力。
/frameworks/base/core/java/android/content/Context.java
3727 public static final String CLIPBOARD_SERVICE = "clipboard";
複製代碼
對於Java而言,接口至關於一種能力。在Binder的世界中,一個服務對象的代理對象一般封裝了特定的服務接口,譬如剪貼板就是IClipboard,表示該對象具備剪貼板服務所能提供的諸多能力,譬如複製文字、粘貼文字等。另外,該代理對象內部有個字段封裝了IBinder接口,表示該字段具備跨進程通訊的能力,它在每次IPC的過程當中都會發揮做用。
IClipboard.Stub是AIDL文件自動生成的一個類,在最終生成的文件中還有一個類是IClipboard.Stub.Proxy,它們是Binder原生類的一層封裝。相較於Binder原生類,它們多了一層數據打包和解包的過程。
IClipboard.java(源碼中沒有,是在編譯時由IClipboard.aidl文件間接生成的)
1 /** Local-side IPC implementation stub class. */
2 public static abstract class Stub extends android.os.Binder implements android.content.IClipboard 複製代碼
1 private static class Proxy implements android.content.IClipboard 複製代碼
IClipboard.Stub.asInterface方法給本來只具備IBinder(跨進程通訊)能力的對象賦予IClipboard(剪貼板)能力。這樣一來,獲得的代理對象就同時兼具了跨進程通訊的能力和剪貼板的能力。跨進程通訊的能力對開發者而言是透明的,而剪貼板的能力纔是他們真正關心的。
最終調用clipboard.setPrimaryClip(clipData)往剪貼板上寫數據時,實際底層調用的倒是mService.setPrimaryClip方法,mService就是剛剛經過asInterface獲得的代理對象。
/frameworks/base/core/java/android/content/ClipboardManager.java
100 public void setPrimaryClip(@NonNull ClipData clip) {
101 try {
102 Preconditions.checkNotNull(clip);
103 clip.prepareToLeaveProcess(true);
104 mService.setPrimaryClip(clip, mContext.getOpPackageName());
105 } catch (RemoteException e) {
106 throw e.rethrowFromSystemServer();
107 }
108 }
複製代碼
mService.setPrimaryClip方法最終調用的是IClipboard.Stub.Proxy.setPrimaryClip方法,將參數打包放入 _data中,並從 _reply中解包讀出Server端傳輸過來的返回值(這個用來示例的方法中沒有返回值)。而真正的跨進程傳輸是經過下面第16行完成的。mRemote的類型爲android.os.IBinder,代表它具備跨進程傳輸的能力。調用它的transact方法表示將打包後的參數發送給Server進程。
IClipboard.java(源碼中沒有,是在編譯時由IClipboard.aidl文件間接生成的)
1 @Override public void setPrimaryClip(android.content.ClipData clip, java.lang.String callingPackage, int userId) throws android.os.RemoteException 2 {
3 android.os.Parcel _data = android.os.Parcel.obtain();
4 android.os.Parcel _reply = android.os.Parcel.obtain();
5 try {
6 _data.writeInterfaceToken(DESCRIPTOR);
7 if ((clip!=null)) {
8 _data.writeInt(1);
9 clip.writeToParcel(_data, 0);
10 }
11 else {
12 _data.writeInt(0);
13 }
14 _data.writeString(callingPackage);
15 _data.writeInt(userId);
16 boolean _status = mRemote.transact(Stub.TRANSACTION_setPrimaryClip, _data, _reply, 0);
17 if (!_status && getDefaultImpl() != null) {
18 getDefaultImpl().setPrimaryClip(clip, callingPackage, userId);
19 return;
20 }
21 _reply.readException();
22 }
23 finally {
24 _reply.recycle();
25 _data.recycle();
26 }
27 }
複製代碼
數據傳輸的過程最終由Binder Driver來負責,因此上述的transact方法最終會經過ioctl
的系統調用進入到內核空間,經過一系列的驅動函數將數據發送給Server進程。
Server進程接收到Client進程傳輸來的參數數據後,就會開始實際的處理。這裏咱們跳過Binder線程選取的過程,由於這個選擇過程發生在Binder Driver中,等到之後專門寫Binder Driver的時候咱們再展開討論。
剪貼板服務對象接收到請求後,最終會調用ClipboardImpl.onTransact方法。
/frameworks/base/services/core/java/com/android/server/clipboard/ClipboardService.java
234 public boolean onTransact(int code, Parcel data, Parcel reply, int flags) 235 throws RemoteException {
236 try {
237 return super.onTransact(code, data, reply, flags);
238 } catch (RuntimeException e) {
239 if (!(e instanceof SecurityException)) {
240 Slog.wtf("clipboard", "Exception: ", e);
241 }
242 throw e;
243 }
244
245 }
複製代碼
這個方法接着會調用父類IClipboard.Stub的onTransact方法。
IClipboard.java(源碼中沒有,是在編譯時由IClipboard.aidl文件間接生成的)
1 @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException 2 {
3 java.lang.String descriptor = DESCRIPTOR;
4 switch (code)
5 {
6 case INTERFACE_TRANSACTION:
7 {
8 reply.writeString(descriptor);
9 return true;
10 }
11 case TRANSACTION_setPrimaryClip:
12 {
13 data.enforceInterface(descriptor);
14 android.content.ClipData _arg0;
15 if ((0!=data.readInt())) {
16 _arg0 = android.content.ClipData.CREATOR.createFromParcel(data);
17 }
18 else {
20 _arg0 = null;
21 }
22 java.lang.String _arg1;
23 _arg1 = data.readString();
24 int _arg2;
25 _arg2 = data.readInt();
26 this.setPrimaryClip(_arg0, _arg1, _arg2);
27 reply.writeNoException();
28 return true;
29 }
....
....
131 }
複製代碼
onTransact方法是一個大型switch-case現場,經過傳輸數據中的code來判斷將要調用哪一個方法。譬如當Client進程以下RPC時,Server進程便會走進上述代碼中第11行的代碼分支。
1 clipboard.setPrimaryClip(clipData);
複製代碼
走進分支後,會將參數解包,並最終調用this.setPrimaryClip方法。這時回到原來的ClipboardImpl類,執行它的setPrimaryClip方法。
/frameworks/base/services/core/java/com/android/server/clipboard/ClipboardService.java
247 @Override
248 public void setPrimaryClip(ClipData clip, String callingPackage) {
249 synchronized (this) {
250 if (clip == null || clip.getItemCount() <= 0) {
251 throw new IllegalArgumentException("No items");
252 }
253 final int callingUid = Binder.getCallingUid();
254 if (!clipboardAccessAllowed(AppOpsManager.OP_WRITE_CLIPBOARD, callingPackage,
255 callingUid)) {
256 return;
257 }
258 checkDataOwnerLocked(clip, callingUid);
259 setPrimaryClipInternal(clip, callingUid);
260 }
261 }
複製代碼
當setPrimaryClip執行完畢後,(假設它有返回值)返回值將會一層層傳遞到native層,並最終再次經過系統調用進入Binder Driver將返回值發送回Client進程。
Client進程接收到返回值以後,便會結束這次RPC,而後繼續執行RPC後面的代碼。
至此,一次完整的RPC過程便結束了。
本文從應用開發者的視角出發,將Binder看做Android世界中的RPC(R
emote P
rocedure C
all,遠程過程調用)機制。首先介紹了RPC的通用概念,以及Android中爲何須要大量的RPC。接着進入到Binder機制內部,完整闡述了一次Binder RPC的過程,並經過「人才中心」的案例形象化地展示Binder的本質。
此外,就service/進程/線程之間的關係進行了明確的梳理,但願可以幫助你們掃除平常開發中的混淆和困惑。