binder核心原理解析

你的使用者名稱發表於2019-02-21

在一些中小型app中主動使用多程式的概率較低,但是也不代表小型app只有一個程式,比如整合了推送一般第三方就是重開的程式,只是我們沒有注意到而已。但是大型app中多程式是非常常見的,就以微信為例,我們可以檢視一下微信的程式,發現有很多個,那麼既然多程式是一個較複雜的概念,為什麼微信會使用這麼多程式呢?

binder核心原理解析


一、為什麼要使用多程式?

1、突破虛擬機器分配程式的執行記憶體限制;

      在Android中,虛擬機器分配給各個程式的執行記憶體是有限制值的(根據裝置:32M,48M,64M等)隨著專案不斷增大,app在執行時記憶體消耗也在不斷增加,甚至系統bug導致的記憶體洩漏,最終結果就是OOM。

2、提高各個程式的穩定性,單一程式崩潰後不影響整個程式;

      小程式程式崩潰,不影響其他程式,不會導致閃退

3、對記憶體更可控,通過主動釋放程式,減小系統壓力,提高系統的流暢性;

      在接收到系統發出的 trimMemory(int level) 中主動釋放重要級低的程式


二、如何使用多程式?

使用多程式很簡單,只需要清單檔案中使用process進行標註即可

binder核心原理解析


三、多程式如何通訊?

binder是Android跨程式的底層機制,跨執行緒之所以不推薦Socket、管道(pipe)來實現,是因為binder的效能更好,安全性更高。當然也有廣播、ContentProvicer也有各自的應用場景,只是不推薦使用,比如廣播有溯源難的問題。

跨程式通訊可以看做是CS模式,客戶端和服務端的通訊。A程式呼叫B程式的方法獲取返回值,或者是修改其屬性。


我們知道,在程式A中不能直接呼叫到B中的方法,因為不同程式是載入在不同的記憶體中,不能直接進行通訊。記憶體分為使用者空間和核心空間,使用者空間供我們使用,核心空間是公用。我們app中的資料都是儲存在使用者空間,想要實現程式中通訊,就必須將資料從使用者空間拷貝到核心空間,然後再從核心空間拷貝到使用者空間,這樣進行互動。所以就必須依靠核心空間進行通訊,其他的通訊方式比如管道也是如此,但是binder是通過對映的方式,只需要賦值一次就可以,所以效率比管道高。


binder核心原理解析


四、什麼是AIDL

Android Interface Definition Language,介面定義語言。

Binder是Android系統中程式間通訊的一種方式,也是Android系統中最重要的特性之一。 AIDL就是定義自動生成Binder相關程式碼的語言。

我們已經知道了程式間通訊最好的方式是使用binder,那麼我們應該如何告知系統我們想做什麼事情呢,最簡單使用最多的方式就是AIDL語言了,我們只需要通過AIDL語言進行程式設計,接下來AIDL會自動幫我們生成binder檔案,節省我們的時間。接下來我們通過一個demo來演示一下客戶端、服務端是如何通過AIDL進行互動的,我們讓客戶端、服務端分別作為一個獨立app可能會更加清晰明瞭。

步驟一   在服務端app中新建aidl檔案

選中包名,右鍵new->aidl->aidl file,就會彈出給aidl取名的彈框,在裡面輸入名字確定,系統就會自動在和java同級目錄下新建aidl資料夾,並且新建好了aidl檔案。內容很簡單,首先匯入了我們自定義的實體類,然後寫好了新增和獲取列表的2個方法供客戶端來呼叫。

binder核心原理解析

binder核心原理解析

步驟二   服務端新建一個資料實體類Person作為待傳輸資料

需要注意的是,這裡也要先新建AIDL檔案,然後再新建Person檔案。並且需要特別注意的是,這個AIDL檔案的名字一定要和javaBean名字一樣,即檔名必須叫Person.aidl。不然是會報錯的,第一次發現的時候調好久,血的教訓。。。新建好Person類以後需要實現Parcelable介面,代表可以傳輸資料,這次我在Person類中定義了2個屬性name和grade。

binder核心原理解析

步驟三 服務端建立服務service

這裡面功能很簡單,相當於是一個倉庫,保管著所有的實體類物件。最後需要在清單檔案中註冊service,並且加上android:exported='true'代表當前服務是公開出去,允許其他程式呼叫。

binder核心原理解析

binder核心原理解析

步驟四  服務端中啟動service

在服務端的某個地方啟動service,這裡我們在開啟服務端app的首頁時直接啟動。

binder核心原理解析

步驟五  客戶端配置AIDL和javabean

aidl:只需要將服務端新建的2個aidl檔案全部複製到客戶端即可,注意這裡一定要保證一樣才能進行通訊,所以一般都是直接複製過去就好。

javabean:這裡需要注意,必須新建一個和服務端一模一樣的包,然後將服務端的Person複製過來,保證在客戶端也能呼叫到該類。

binder核心原理解析

步驟六  客戶端使用服務端提供的介面進行通訊

我在客戶端這邊寫了一個按鈕,每次點選以後呼叫服務端的addPerson方法新增一個使用者,然後將使用者資訊列印出來。這裡需要注意的是,需要繫結好服務端的服務,具體程式碼如下:

binder核心原理解析

步驟七  執行程式碼檢視結果

這裡先執行服務端,然後執行客戶端,執行後在客戶端的logcat成功列印出日誌,說明跨程式呼叫成功。

binder核心原理解析

binder使用總結:這裡我們使用了aidl語言來操作binder實現了跨程式的呼叫,而實際上aidl的作用僅僅是用來生成binder檔案而已,只不過是這個生成的檔案比較複雜,可讀性低,一般不自己寫。如果我們能自己寫出該binder檔案的話完全可以不用aidl了,該檔案可以在build中檢視到,而且客戶端、服務端中都有並且內容一模一樣。當然aidl也有缺陷,就是編寫aidl檔案的時候沒有報錯提示,所以除錯起來比較麻煩。以上我們簡單使用了binder實現程式間通訊,接下來我們再一起分析一下binder核心原理。

binder核心原理解析


五、binder原始碼分析

我們可以從aidl生成的binder程式碼來入手分析,繼承自IInterface.

binder核心原理解析

接下來點選AS左側的選項檢視該類的結構圖,在核心的Stub中有2個成員,一個是Proxy,另一個是Stub(),其中proxy是傳送資料,stub是接收資料。

binder核心原理解析

服務端可能會有很多的aidl檔案,具體呼叫哪個是通過attachInterface進行繫結的。

binder核心原理解析

接下來我們來看看asInterface方法,該方法在客戶端呼叫,然後在服務端實現。客戶端傳入了binder,然後返回了一個aidl物件。呼叫之後首先搜尋本地的aidl介面,排除不跨程式的情況,然後再呼叫proxy()將客戶端的引用傳給binder,binder拿到了引用以後就可以操作客戶端了。

binder核心原理解析

binder核心原理解析

接下來再看看核心的proxy內部類裡有什麼,是不是似曾相識,這裡面addPerson、getPersonList方法均為服務端定義的供客戶端使用的方法。其中_data是客戶端傳給服務端的資料,而_replay是服務端處理完返回給客戶端的結果。最後通過binder物件的transact將當前程式掛起,然後呼叫服務端中的方法。這裡的Stub.TRANSACTION_addPerson是谷歌官方考慮到傳遞方法名字串比較耗效能,然後服務端和客戶端的方法和順序是一模一樣的,所以這裡就直接用數字來代替,代表第幾個方法。這也從側面說明了客戶端和服務端定義的aidl檔案為何要一致了,不一致的話這裡是會找不到指定方法的。注意其中的flags如果為0代表雙向,如果為1代表單向。

binder核心原理解析

在呼叫完transact方法後就會呼叫服務端的onTransact(),我們找到服務端的onTransact方法一看究竟。這裡就很清晰明瞭了,判斷剛剛傳過來的方法下標,然後進行指定的處理,處理完以後將結果通過writeXXX存到reply中,所以前面的_reply就有值了。

binder核心原理解析


總結:本文對多程式的使用場景進行了分析,還簡單提到了多種跨程式的方式,經過對比AIDL的效能會更高。所以分享了通過AIDL語言生成了binder呼叫程式碼實現了跨程式通訊,程式A成功獲取到並修改了程式B的值。並且檢視了AIDL生成的binder檔案的原始碼,知道了AIDL語言的作用,以及簡單分析了binder工作的一整套流程。但是binder其實還有更多複雜的內容需要去學習,比如binder為什麼要藉助service來完成通訊等,以後有空會繼續學習和更新。


相關文章