簡單明瞭,完全地理解Binder

個人簡書同步發佈:簡單明瞭,完全地理解Binder java

你是否是看過不少Binder文章可是仍是對Binder沒有一個深入理解?不是那些文章講得不夠好,主要是存在兩種狀況,一種是講的深,全C代碼~,對我這種專作Java的人來講沒有心情往下看;另外一種是隻講framework層,Binder驅動並無具體提,致使咱們會用Binder,也大體能說的出一些原理,可並無一個完整的深入認知。那麼接下來讓咱們一塊兒學習Binder吧,相信接下來的內容會讓你有必定的收穫~web

什麼是Binder

這個問題不少文章都有解釋,好比:Binder是Android跨進程通訊方式,它實現了IBinder接口,是ServiceManager鏈接各類Manager(如WindowManager、ActivityManager等)的橋樑。可是我以爲這些說法仍是過於抽象。剛接觸Binder時,看到這些定義仍是一頭霧水,只是心裏以爲Binder很牛逼、很底層,僅此而已。數組

那麼應該怎麼去理解Binder呢?我不打算介紹這個概念,而是介紹Binder是怎麼來到Android世界的。我是這樣理解的:Android團隊想要實現進程之間的通訊,須要解決如下幾個問題:架構

  1. 如何知道客戶端須要調用哪一個進程以及該進程中的函數
  2. 客戶端如何將函數形參發送給遠程進程中的函數,以及如何將遠程進程函數計算結果返回客戶端
  3. 如何去屏蔽底層通訊細節,讓實現客戶端調用遠程函數就像調用本地函數同樣

第一個問題,很容易解決,只要給每一個須要遠程通訊的類惟一標識就能夠經過包名+類名的字符串就能夠作到,而後在類裏面給每一個函數編號便可對函數惟一編碼。第二個問題,定義一個可打包的接口Parcelable,這個接口提供2個重要函數,分別是將對象中的屬性寫入到數組和從數組中的數據還原對象,每一個能夠發送到遠程函數做爲形參的對象只需實現Parcelable對象便可。Parcelable具體使用再也不本文討論範圍。第三個問題,爲了屏蔽進程之間的通訊細節,那麼Android團隊確定在想,定義一個類,由這個類來實現這些細節。這個類應該作哪些事情呢?首先,這個類得幫用戶發送遠程請求並將拿到返回結果提交給用戶,這是最重要的功能了,有了這個功能,媽媽不再用擔憂個人進程通訊。其次,若是我想實現服務端,何時客戶端調用我了,這些細節不用用戶操心。固然,這個類還要幫用戶封裝更多細節。既然打算定義這個類了,那總得取個響噹噹的名稱吧,什麼?你說取名爲Binder,好吧,那就叫Binder吧。Binder類既然封裝不少功能,那該怎麼用這個類呢?讓客戶端去繼承仍是服務端繼承呢?答案是服務端。接下來有個約定,本文後面所指的Binder類都是指遠程服務端的對象。服務端想要實現被跨進程訪問,就必須繼承Binder類。
首先咱們看看咱們的程序跨進程調用系統服務的簡單示例,實現浮動窗口部分代碼:svg

//獲取WindowManager服務引用
WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE);  
//佈局參數layoutParams相關設置略...
View view=LayoutInflater.from(getApplication()).inflate(R.layout.float_layout, null);  
//添加view
wm.addView(view, layoutParams);

系統服務都是運行在systemServer進程中,所以咱們調用系統服務都是跨進程的調用。第2行代碼中,獲得的wm是WindowManager對象的引用,第6行調用WindowManager的addView函數,將觸發遠程調用,調用的是運行在systemServer進程中的WindowManager的addView函數。是否是很想知道addView發生了什麼?咱們先看看Binder機制吧!看完Binder原理,再解釋!函數

Binder機制

先看看通常執行過程工具

代碼執行過程

假設你已經建立好服務端類MyService、客戶端類MyClient。在客戶端持有MyService的引用,而且調用了MyService的func函數,那麼Android內部調用過程以下:佈局

Created with Raphaël 2.1.0 MyClient MyClient Binder驅動 Binder驅動 MyService MyService 請求相關數據:Parcel 客戶端當前 線程被掛起 從線程池中取出線程並 執行客戶端指定的函數 等待執行結果 執行函數func 計算結果返回 獲得返回結果 喚醒被掛起的線程並將 返回結果傳給客戶端 當前線程被喚醒, 取出返回結果, 繼續往下執行。

看了這個圖之後,相信你對你的代碼在調用遠程進程函數時有個全局的認識。這張圖有一點很重要,就是客戶端當前線程會被掛起!所以,若是遠程進程是執行長時間的運算,請不要使用主線程去調用遠程函數,以防止ANR。學習

Binder的C/S架構

上面一節咱們對遠程進程調用代碼執行過程有個初步瞭解,在Android開發中,咱們大量使用到了系統Service,好比媒體播放、各類傳感器以及WindowManagerService等等等等(太多了~)。那麼Android是怎麼管理這些服務,而且讓用戶跨進程調用這些服務呢?首先咱們看看調用系統服務的過程。在Android開機啓動過程當中,Android會初始化系統的各類Service,並將這些Service向ServiceManager註冊(即讓ServiceManager管理)。客戶端想要獲得具體的Service直接向ServiceManager要便可。客戶端首先向ServiceManager查詢獲得具體的Service引用,而後經過這個引用向具體的服務端發送請求,服務端執行完成後就返回。編碼

客戶端調用系統服務過程

Binder驅動實現原理

一直以來,我有個困惑!!!這個困惑讓我迷茫了好久:客戶端持有遠程進程的某個對象引用,而後調用引用類中的函數,遠程進程的函數就執行了。我在想,憑什麼?學過操做系統都知道,不一樣的進程之間是不共享資源的。也就是說,客戶端持有的這個對象跟遠程進程中的實際對象徹底是兩個不一樣的對象。客戶端調用引用的對象跟遠程進程半毛錢關係都沒有,憑啥遠程進程就調用了執行了?相信也有一部分人跟我有一樣的困惑!仔細研讀一下下面這張圖,相信你會豁然開朗!

Binder驅動實現原理
服務端跨進程的類都要繼承Binder類。咱們所持有的Binder引用(即服務端的類引用)並非實際真實的遠程Binder對象,咱們的引用在Binder驅動裏還要作一次映射。也就是說,設備驅動根據咱們的引用對象找到對應的遠程進程。客戶端要調用遠程對象函數時,只需把數據寫入到Parcel,在調用所持有的Binder引用的transact()函數,transact函數執行過程當中會把參數、標識符(標記遠程對象及其函數)等數據放入到Client的共享內存,Binder驅動從Client的共享內存中讀取數據,根據這些數據找到對應的遠程進程的共享內存,把數據拷貝到遠程進程的共享內存中,並通知遠程進程執行onTransact()函數,這個函數也是屬於Binder類。遠程進程Binder對象執行完成後,將獲得的寫入本身的共享內存中,Binder驅動再將遠程進程的共享內存數據拷貝到客戶端的共享內存,並喚醒客戶端線程。

Binder機制運用

好了,如今對Binder機制已經理解了,咱們再看看Android是怎麼運用Binder的。再現前面代碼:

//獲取WindowManager服務引用
WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE);  
//佈局參數layoutParams相關設置略...
View view=LayoutInflater.from(getApplication()).inflate(R.layout.float_layout, null);  
//添加view
wm.addView(view, layoutParams);

這段代碼前面已經出現過。getSystemService(getApplication().WINDOW_SERVICE);函數內部原理就是向ServiceManager查詢標識符爲getApplication().WINDOW_SERVICE的遠程對象的引用。即WindowManager對象的引用,這個引用的真正實現是WindowManager的某個代理。獲得這個引用後,在調用addView時,真正的實現是在代理裏面,代理把參數打包到Parcel對象中,而後調用transact函數(該函數繼承自Binder),再觸發Binder驅動的一系列調用過程,在Binder驅動實現原理一節中有具體介紹,忘記了的同窗能夠返回繼續看。關於Binder的代理對象,能夠參考AIDL工具生成的代碼,這裏再也不具體介紹。

相信到如今,你收穫很多吧~若是你喜歡就給我一個贊吧~有疑問歡迎評論~