Binder概述

原文連接:banshan.tech/Binder概述/java

本文分析基於Android P(9.0) 源碼android

在編程的世界中,不一樣進程間的通訊、協同、合做隨處可見。不少時候,人們習慣用IPC(Inter Process Communication,跨進程通訊)來稱呼它們。譬如Binder在多數狀況下也被稱爲Android世界中的IPC機制。但若是以應用開發者的視角來看,Binder也能夠稱爲Android世界中的RPC(Remote Procedure Call,遠程過程調用)機制。git

  • IPC(Inter Process Communication,跨進程通訊)github

    泛指一切用於進程間傳遞信息量(傳輸數據只是傳遞信息量的一個子集)的方式,譬如socket/pipe/FIFO/semaphore等。這些名稱表徵的是信息傳輸的具體方式,而不涉及信息的處理和加工。算法

  • RPC(Remote Procedure Call,遠程過程調用)編程

    一種建構於IPC基礎之上的方法調用。對於客戶端而言,它能夠感知到的僅僅是方法調用和獲取返回值兩個過程。然而實際上這個方法內部完成了客戶端參數打包,數據傳輸,服務端數據解包,服務端數據處理,服務端結果返回等一系列中間過程。只不過這些過程對於客戶端而言都是「透明」的。因此能夠說IPC只是RPC中的一個環節,除此以外,RPC還包含數據打包,解包以及處理的過程,它們能夠統稱爲信息的處理和加工過程。設計模式

1. Android中爲何須要大量的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與非RPC的區別

在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。

2. Binder世界裏的service究竟是什麼概念?

在進行具體闡述以前,咱們先要作一個限定。如下所討論的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實例化的對象」都無所謂。這就比如整個飯店就一個廚師,你叫他「廚師」仍是「王廚師」都不影響理解。

2.1 service與進程/線程的關係

若是將service看做一個能夠提供服務的Java對象,那麼這個問題將會迎刃而解。

Java對象是存放在堆上的,所以能夠在同一個進程裏的不一樣線程間共享,因此service(準確來講應該是service對象的方法)也能夠在不一樣的線程裏運行。

另外,一個進程裏能夠構造出成千上萬的對象,所以一個進程裏也能夠存在成千上萬的service。並且同一個類型的service也能夠存在不少個,譬如咱們能夠在system_server進程中同時構造ActivityManagerService對象A/B/C,三個AMS對象表示三個能夠提供服務的實體,這就比如飯店裏如今有了三個廚師,你能夠跟其中任何一個請求服務。固然,在實際的Android系統中,同一個類型的service多數狀況下只有一個對象。但現實狀況不存在並不表明理論上不可實現,因此理論上同一個類型的service能夠在一個進程中存在多個對象。

3. RPC的內部是如何構成的?

本文開篇提到,對於客戶端而言,它能夠感知到的僅僅是方法調用和獲取返回值兩個過程。那麼具體到剪貼板這個例子來的話,對於Android應用的開發者而言,他感知到的只有下面兩件事:

對於Android應用的開發者而言,他感知到的只有下面兩件事:

  1. 我調用了clipboard.setPrimaryClip()方法
  2. 剪貼板上出現了我想要複製的文字

在這個過程當中,應用開發者根本感知不到這是一次跨進程的調用,也感覺不到調用背後的數據傳輸。RPC機制將這一切都封裝了起來,所以開發者能夠天真地認爲全部這一切都發生在應用進程。而這也正是系統設計者但願給開發者帶去的便利和簡化,既是理解上的簡化,也是使用上的簡化。

不過一個有追求的開發者一般只會選擇使用上的簡化,而不會侷限在理解上的簡化。因此下面我將用一種頗具趣味性的方式來繼續闡述。

3.1 「人才中心」的例子

全部的算法和設計模式都是從社會生活中抽象提煉出來的。因此本着「從羣衆中來,到羣衆中去」的原則,咱們賦予冰冷的源碼以生命,從社會生活的角度來理解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中。

3.2 剪貼板的例子

接下來以上文中剪貼板複製文本的代碼爲樣本,來闡述一次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);
複製代碼

3.2.1 service對象的建立過程

剪貼板服務對象位於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();

複製代碼

3.2.2 尋找service對象的代理對象

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(剪貼板)能力。這樣一來,獲得的代理對象就同時兼具了跨進程通訊的能力和剪貼板的能力。跨進程通訊的能力對開發者而言是透明的,而剪貼板的能力纔是他們真正關心的。

3.2.3 經過代理對象進行RPC

最終調用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進程。

3.2.4 服務對象處理接收到的請求

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過程便結束了。

4. 總結

本文從應用開發者的視角出發,將Binder看做Android世界中的RPC(Remote Procedure Call,遠程過程調用)機制。首先介紹了RPC的通用概念,以及Android中爲何須要大量的RPC。接着進入到Binder機制內部,完整闡述了一次Binder RPC的過程,並經過「人才中心」的案例形象化地展示Binder的本質。

此外,就service/進程/線程之間的關係進行了明確的梳理,但願可以幫助你們掃除平常開發中的混淆和困惑。

原文連接:banshan.tech/Binder概述/

相關文章
相關標籤/搜索