在一些中小型app中主動使用多進程的機率較低,可是也不表明小型app只有一個進程,好比集成了推送通常第三方就是重開的進程,只是咱們沒有注意到而已。可是大型app中多進程是很是常見的,就以微信爲例,咱們能夠查看一下微信的進程,發現有不少個,那麼既然多進程是一個較複雜的概念,爲何微信會使用這麼多進程呢?
java
一、突破虛擬機分配進程的運行內存限制;android
在Android中,虛擬機分配給各個進程的運行內存是有限制值的(根據設備:32M,48M,64M等)隨着項目不斷增大,app在運行時內存消耗也在不斷增長,甚至系統bug致使的內存泄漏,最終結果就是OOM。編程
二、提升各個進程的穩定性,單一進程崩潰後不影響整個程序;小程序
小程序進程崩潰,不影響其餘進程,不會致使閃退安全
三、對內存更可控,經過主動釋放進程,減少系統壓力,提升系統的流暢性;微信
在接收到系統發出的 trimMemory(int level) 中主動釋放重要級低的進程app
使用多進程很簡單,只須要清單文件中使用process進行標註便可
源碼分析
binder是Android跨進程的底層機制,跨線程之因此不推薦Socket、管道(pipe)來實現,是由於binder的性能更好,安全性更高。固然也有廣播、ContentProvicer也有各自的應用場景,只是不推薦使用,好比廣播有溯源難的問題。性能
咱們知道,在進程A中不能直接調用到B中的方法,由於不一樣進程是加載在不一樣的內存中,不能直接進行通訊。內存分爲用戶空間和內核空間,用戶空間供咱們使用,內核空間是公用。咱們app中的數據都是保存在用戶空間,想要實現進程中通訊,就必須將數據從用戶空間拷貝到內核空間,而後再從內核空間拷貝到用戶空間,這樣進行交互。因此就必須依靠內核空間進行通訊,其餘的通訊方式好比管道也是如此,可是binder是經過映射的方式,只須要賦值一次就能夠,因此效率比管道高。
學習
咱們已經知道了進程間通訊最好的方式是使用binder,那麼咱們應該如何告知系統咱們想作什麼事情呢,最簡單使用最多的方式就是AIDL語言了,咱們只須要經過AIDL語言進行編程,接下來AIDL會自動幫咱們生成binder文件,節省咱們的時間。接下來咱們經過一個demo來演示一下客戶端、服務端是如何經過AIDL進行交互的,咱們讓客戶端、服務端分別做爲一個獨立app可能會更加清晰明瞭。
步驟一 在服務端app中新建aidl文件
選中包名,右鍵new->aidl->aidl file,就會彈出給aidl取名的彈框,在裏面輸入名字肯定,系統就會自動在和java同級目錄下新建aidl文件夾,而且新建好了aidl文件。內容很簡單,首先導入了咱們自定義的實體類,而後寫好了添加和獲取列表的2個方法供客戶端來調用。
步驟二 服務端新建一個數據實體類Person做爲待傳輸數據
須要注意的是,這裏也要先新建AIDL文件,而後再新建Person文件。而且須要特別注意的是,這個AIDL文件的名字必定要和javaBean名字同樣,即文件名必須叫Person.aidl。否則是會報錯的,第一次發現的時候調很久,血的教訓。。。新建好Person類之後須要實現Parcelable接口,表明能夠傳輸數據,此次我在Person類中定義了2個屬性name和grade。
步驟三 服務端建立服務service
這裏面功能很簡單,至關因而一個倉庫,保管着全部的實體類對象。最後須要在清單文件中註冊service,而且加上android:exported='true'表明當前服務是公開出去,容許其餘進程調用。
步驟四 服務端中啓動service
在服務端的某個地方啓動service,這裏咱們在打開服務端app的首頁時直接啓動。
步驟五 客戶端配置AIDL和javabean
aidl:只須要將服務端新建的2個aidl文件所有複製到客戶端便可,注意這裏必定要保證同樣才能進行通訊,因此通常都是直接複製過去就好。
javabean:這裏須要注意,必須新建一個和服務端如出一轍的包,而後將服務端的Person複製過來,保證在客戶端也能調用到該類。
步驟六 客戶端使用服務端提供的接口進行通訊
我在客戶端這邊寫了一個按鈕,每次點擊之後調用服務端的addPerson方法添加一個用戶,而後將用戶信息打印出來。這裏須要注意的是,須要綁定好服務端的服務,具體代碼以下:
步驟七 運行代碼查看結果
這裏先運行服務端,而後運行客戶端,運行後在客戶端的logcat成功打印出日誌,說明跨進程調用成功。
binder使用總結:這裏咱們使用了aidl語言來操做binder實現了跨進程的調用,而實際上aidl的做用僅僅是用來生成binder文件而已,只不過是這個生成的文件比較複雜,可讀性低,通常不本身寫。若是咱們能本身寫出該binder文件的話徹底能夠不用aidl了,該文件能夠在build中查看到,並且客戶端、服務端中都有而且內容如出一轍。固然aidl也有缺陷,就是編寫aidl文件的時候沒有報錯提示,因此調試起來比較麻煩。以上咱們簡單使用了binder實現進程間通訊,接下來咱們再一塊兒分析一下binder核心原理。
咱們能夠從aidl生成的binder代碼來入手分析,繼承自IInterface.
接下來點擊AS左側的選項查看該類的結構圖,在覈心的Stub中有2個成員,一個是Proxy,另外一個是Stub(),其中proxy是發送數據,stub是接收數據。
服務端可能會有不少的aidl文件,具體調用哪一個是經過attachInterface進行綁定的。
接下來咱們來看看asInterface方法,該方法在客戶端調用,而後在服務端實現。客戶端傳入了binder,而後返回了一個aidl對象。調用以後首先搜尋本地的aidl接口,排除不跨進程的狀況,而後再調用proxy()將客戶端的引用傳給binder,binder拿到了引用之後就能夠操做客戶端了。
接下來再看看核心的proxy內部類裏有什麼,是否是似曾相識,這裏面addPerson、getPersonList方法均爲服務端定義的供客戶端使用的方法。其中_data是客戶端傳給服務端的數據,而_replay是服務端處理完返回給客戶端的結果。最後經過binder對象的transact將當前進程掛起,而後調用服務端中的方法。這裏的Stub.TRANSACTION_addPerson是谷歌官方考慮到傳遞方法名字符串比較耗性能,而後服務端和客戶端的方法和順序是如出一轍的,因此這裏就直接用數字來代替,表明第幾個方法。這也從側面說明了客戶端和服務端定義的aidl文件爲什麼要一致了,不一致的話這裏是會找不到指定方法的。注意其中的flags若是爲0表明雙向,若是爲1表明單向。
在調用完transact方法後就會調用服務端的onTransact(),咱們找到服務端的onTransact方法一看究竟。這裏就很清晰明瞭了,判斷剛剛傳過來的方法下標,而後進行指定的處理,處理完之後將結果經過writeXXX存到reply中,因此前面的_reply就有值了。
總結:本文對多進程的使用場景進行了分析,還簡單提到了多種跨進程的方式,通過對比AIDL的性能會更高。因此分享了經過AIDL語言生成了binder調用代碼實現了跨進程通訊,進程A成功獲取到並修改了進程B的值。而且查看了AIDL生成的binder文件的源碼,知道了AIDL語言的做用,以及簡單分析了binder工做的一整套流程。可是binder其實還有更多複雜的內容須要去學習,好比binder爲何要藉助service來完成通訊等,之後有空會繼續學習和更新。