目錄java
爲何要使用多進程android
整個app都在一個進程有什麼弊端?git
微信,微博等主流app是如何解決這些問題的?github
下面咱們使用adb查看一下微信和微博的進程信息(Android 5.0如下版本可直接在「設置 -> 應用程序」相關條目中查看):web
進入adb shell後,使用 「ps | grep 條目名稱」 能夠過濾出想要查看的進程。面試
能夠看到,微信的確有一個tools進程,而新浪微博也有image相關的進程,並且它們當中還有好些其它的進程,好比微信的push進程,微博的remote進程等,這裏能夠看出,他們不僅僅只是把上述的WebView、圖庫等放到單獨的進程,還有推送服務等也是運行在獨立的進程中的。shell
一個消息推送服務,爲了保證穩定性,可能須要和UI進程分離,分離後即便UI進程退出、Crash或者出現內存消耗太高等狀況,仍不影響消息推送服務。編程
可見,合理使用多進程不只僅是有多大好處的問題,我我的認爲並且是頗有必要的。緩存
因此說,咱們最好仍是根據自身狀況,考慮一下是否須要拆分進程。這也是本文的初衷:給你們提供一個多進程的參考思路,在遇到上述問題和場景的時候,能夠考慮用多進程的方法來解決問題,又或者,在面試的時候,跟面試官聊到這方面的知識時候也不至於尷尬。安全
爲何須要「跨進程通信」
Android的進程與進程之間通信,有些不須要咱們額外編寫通信代碼,例如:把選擇圖片模塊放到獨立的進程,咱們仍可使用startActivityForResult方法,將選中的圖片放到Bundle中,使用Intent傳遞便可。(看到這裏,你還不打算把你項目的圖片選擇弄到獨立進程麼?)
可是對於把「消息推送Service」放到獨立的進程,這個業務就稍微複雜點了,這個時候可能會發生Activity跟Service傳遞對象,調用Service方法等一系列複雜操做。
因爲各個進程運行在相對獨立的內存空間,因此它們是不能直接通信的,由於程序裏的變量、對象等初始化後都是具備內存地址的,舉個簡單的例子,讀取一個變量的值,本質是找到變量的內存地址,取出存放的值。
不一樣的進程,運行在相互獨立的內存(其實就能夠理解爲兩個不一樣的應用程序),顯然不能直接得知對方變量、對象的內存地址,這樣的話也天然不能訪問對方的變量,對象等。此時兩個進程進行交互,就須要使用跨進程通信的方式去實現。簡單說,跨進程通信就是一種讓進程與進程之間能夠進行交互的技術。
跨進程的通信方式有哪些
下面開始對AIDL的講解,各位道友準備好渡劫了嗎?
使用AIDL使用一個跨進程消息推送
像圖片選擇這樣的多進程需求,可能並不須要咱們額外編寫進程通信的代碼,使用四大組件傳輸Bundle就好了,可是像推送服務這種需求,進程與進程之間須要高度的交互,此時就繞不過進程通信這一步了。下面咱們就用即時聊天軟件爲例,手動去實現一個多進程的推送例子,具體需求以下:
實現思路
先來整理一下實現思路:
例子具體實現
爲了閱讀方便,下文中代碼將省略非重點部分,能夠把本文完整代碼Clone到本地再看文章:
Step0. AIDL調用流程概覽
開始以前,咱們先來歸納一下使用AIDL進行多進程調用的整個流程:
整個AIDL調用過程歸納起來就以上3個步驟,下文中咱們使用上面描述的例子,來逐步分解這些步驟,並講述其中的細節。
Step1.客戶端使用bindService方法綁定服務端
1.1 建立客戶端和服務端,把服務端配置到另外的進程
上面描述的客戶端、服務端、以及把服務端配置到另外進程,體如今AndroidManifest.xml中,以下所示:
開啓多進程的方法很簡單,只須要給四大組件指定android:process標籤。
1.2 綁定MessageService到MainActivity
建立MessageService:
此時的MessageService就是剛建立的模樣,onBind中返回了null,下一步中咱們將返回一個可操做的對象給客戶端。
客戶端MainActivity調用bindService方法綁定MessageService
這一步實際上是屬於Service組件相關的知識,在這裏就比較簡單地說一下,啓動服務能夠經過如下兩種方式:
bindService & startService區別:使用bindService方式,多個Client能夠同時bind一個Service,可是當全部Client unbind後,Service會退出,一般狀況下,若是但願和Service交互,通常使用bindService方法,使用onServiceConnected中的IBinder對象能夠和Service進行交互,不須要和Service交互的狀況下,使用startService方法便可。
正如上面所說,咱們是要和Service交互的,因此咱們須要使用bindService方法,可是咱們但願unbind後Service仍保持運行,這樣的狀況下,能夠同時調用bindService和startService(好比像本例子中的消息服務,退出UI進程,Service仍須要接收到消息),代碼以下:
Stpe2.服務端在onBind方法返回Binder對象
2.1 首先,什麼是Binder?
要說Binder,首先要說一下IBinder這個接口,IBinder是遠程對象的基礎接口,輕量級的遠程過程調用機制的核心部分,該接口描述了與遠程對象交互的抽象協議,而Binder實現了IBinder接口,簡單說,Binder就是Android SDK中內置的一個多進程通信實現類,在使用的時候,咱們不用也不要去實現IBinder,而是繼承Binder這個類便可實現多進程通信。
2.2 其次,這個須要在onBind方法返回的Binder對象從何而來?
在這裏就要引出本文中的主題了——AIDL多進程中使用的Binder對象,通常經過咱們定義好的 .adil 接口文件自動生成,固然你能夠走野路子,直接手動編寫這個跨進程通信所需的Binder類,其本質無非就是一個繼承了Binder的類,鑑於野路子走起來麻煩,並且都是重複步驟的工做,Google提供了 AIDL 接口來幫咱們自動生成Binder這條正路,下文中咱們圍繞 AIDL 這條正路繼續展開討論(可不能把人給帶偏了是吧)
2.3 定義AIDL接口
很明顯,接下來咱們須要搞一波上面說的Binder,讓客戶端能夠調用到服務端的方法,而這個Binder又是經過AIDL接口自動生成,那咱們就先從AIDL搞起,搞以前先看看注意事項,以避免出事故:
AIDL支持的數據類型:
其餘注意事項:
下面繼續咱們的例子,開始對AIDL的講解~
2.4 建立一個AIDL接口,接口中提供發送消息的方法(Android Studio建立AIDL:項目右鍵 -> New -> AIDL -> AIDL File),代碼以下:
一個比較尷尬的事情,看了不少文章,歷來沒有一篇能說清楚in、out、inout這三個參數方向的意義,後來在stackoverflow上找到能理解答案(stackoverflow.com/questions/4…我翻譯一下大概意思:
上述的MessageModel爲消息的實體類,該類在AIDL中傳遞,實現了Parcelable序列化接口,代碼以下:
手動實現Parcelable接口比較麻煩,安利一款AS自動生成插件android-parcelable-intellij-plugin建立完MessageModel這個實體類,別忘了還有一件事要作:」在AIDL中傳遞的對象,須要在類文件相同路徑下,建立同名、可是後綴爲.aidl的文件,並在文件中使用parcelable關鍵字聲明這個類「。代碼以下:
對於沒有接觸過aidl的同窗,光說就能讓人懵逼,來看看此時的項目結構壓壓驚:
咱們剛剛新增的3個文件:
OK,相信此時懵逼已解除~
2.5 在服務端建立MessageSender.aidl這個AIDL接口自動生成的Binder對象,並返回給客戶端調用,服務端MessageService代碼以下:
MessageSender.Stub是Android Studio根據咱們MessageSender.aidl文件自動生成的Binder對象(至因而怎樣生成的,下文會有答案),咱們須要把這個Binder對象返回給客戶端。
2.6 客戶端拿到Binder對象後調用遠程方法
調用步驟以下:
此時客戶端代碼以下:
在客戶端中咱們調用了MessageSender的sendMessage方法,向服務端發送了一條消息,並把生成的MessageModel對象做爲參數傳遞到了服務端,最終服務端打印的結果以下:
這裏有兩點要說:
到這裏,咱們已經完成了最基本的使用AIDL進行跨進程方法調用,也是Step.0的整個細化過程,能夠再回顧一下Step.0,既然已經學會使用了,接下來…全劇終。。。
若是寫到這裏全劇終,那跟鹹魚有什麼區別…
知其然,知其因此然
咱們經過上述的調用流程,看看從客戶端到服務端,都經歷了些什麼事,看看Binder的上層是如何工做的,至於Binder的底層,這是一個很是複雜的話題,本文不深究。(若是看到這裏你又想問什麼是Binder的話,請手動倒帶往上看…)
咱們先來回顧一下從客戶端發起的調用流程:
拋開其它無關代碼,客戶端調跨進程方法就這兩個步驟,而這兩個步驟都封裝在 MessageSender.aidl 最終生成的 MessageSender.java 源碼(具體路徑爲:build目錄下某個子目錄,本身找,不爽你來打我啊 )
請看下方代碼和註釋,前方高能預警…
只看代碼的話,可能會有點懵逼,相信結合代碼再看下方的流程圖會更好理解:
從客戶端的sendMessage開始,整個AIDL的調用過程如上圖所示,asInterface方法,將會判斷onBind方法返回的Binder是否存處於同一進程,在同一進程中,則進行常規的方法調用,若處於不一樣進程,整個數據傳遞的過程則須要經過Binder底層去進行編組(序列化,傳輸,接收和反序列化),獲得最終的數據後再進行常規的方法調用。
敲黑板:對象跨進程傳輸的本質就是 序列化,傳輸,接收和反序列化 這樣一個過程,這也是爲何跨進程傳輸的對象必須實現Parcelable接口
跨進程的回調接口
在上面咱們已經實現了從客戶端發送消息到跨進程服務端的功能,接下來咱們還須要將服務端接收到的遠程服務器消息,傳遞到客戶端。有同窗估計會說:「這不就是一個回調接口的事情嘛」,設置回調接口思路是對的,可是在這裏使用的回調接口有點不同,在AIDL中傳遞的接口,不能是普通的接口,只能是AIDL接口,因此咱們須要新建一個AIDL接口傳到服務端,做爲回調接口。
新建消息收取的AIDL接口MessageReceiver.aidl:
接下來咱們把回調接口註冊到服務端去,修改咱們的MessageSender.aidl:
以上就是咱們最終修改好的aidl接口,接下來咱們須要作出對應的變動:
客戶端變動,修改MainActivity:
客戶端主要有3個變動:
下面對服務端MessageServie進行變動:
服務端主要變動:
這裏還有一個須要講一下的,就是RemoteCallbackList,爲何要用RemoteCallbackList,普通ArrayList不行嗎?固然不行,否則幹嗎又整一個RemoteCallbackList ,registerReceiveListener 和 unregisterReceiveListener在客戶端傳輸過來的對象,通過Binder處理,在服務端接收到的時候實際上是一個新的對象,這樣致使在 unregisterReceiveListener 的時候,普通的ArrayList是沒法找到在 registerReceiveListener 時候添加到List的那個對象的,可是它們底層使用的Binder對象是同一個,RemoteCallbackList利用這個特性作到了能夠找到同一個對象,這樣咱們就能夠順利反註冊客戶端傳遞過來的接口對象了。
RemoteCallbackList在客戶端進程終止後,它能自動移除客戶端所註冊的listener,它內部還實現了線程同步,因此咱們在註冊和反註冊都不須要考慮線程同步,的確是個666的類。(至於使用ArrayList的幺蛾子現象,你們能夠本身試試,篇幅問題,這裏就不演示了)
到此,服務端通知客戶端相關的代碼也寫完了,運行結果無非就是正確打印就不貼圖了,能夠本身Run一下,打印的時候注意去選擇不一樣的進程,否則瞪壞屏幕也沒有日誌。
DeathRecipient
你覺得這樣就完了?too young too simple…
不知道你有沒有感受到,兩個進程交互老是以爲缺少那麼一點安全感…好比說服務端進程Crash了,而客戶端進程想要調用服務端方法,這樣就調用不到了。此時咱們能夠給Binder設置一個DeathRecipient對象,當Binder意外掛了的時候,咱們能夠在DeathRecipient接口的回調方法中收到通知,並做出相應的操做,好比重連服務等等。
DeathRecipient的使用以下:
在客戶端MainActivity聲明DeathRecipient:
Binder中兩個重要方法:
此外,Binder中的isBinderAlive也能夠判斷Binder是否死亡
權限驗證
就算是公交車,上車也得嘀卡對不,若是但願咱們的服務進程不想像公交車同樣誰想上就上,那麼咱們能夠加入權限驗證。
介紹兩種經常使用驗證方法:
自定義permission,在Androidmanifest.xml中增長自定義的權限:
服務端檢查權限的方法:
根據不一樣進程,作不一樣的初始化工做
相信前一兩年不少朋友還在使用Android-Universal-Image-Loader來加載圖片,它是須要在Application類進行初始化的。打個好比,咱們用它來加載圖片,並且還有一個圖片選擇進程,那麼咱們但願分配更多的緩存給圖片選擇進程,又或者是一些其餘的初始化工做,不須要在圖片選擇進程初始化怎麼辦?
這裏提供一個簡單粗暴的方法,博主也是這麼幹的…直接拿到進程名判斷,做出相應操做便可:
每一個進程建立,都會調用Application的onCreate方法,這是一個須要注意的地方,咱們也能夠根據當前進程的pid,拿到當前進程的名字去作判斷,而後作一些咱們須要的邏輯,咱們這個例子,拿到的兩個進程名分別是:
總結
結語
這篇文章斷斷續續寫了好久,並且我相信真正使用起來的同窗可能很少,選擇這樣一個話題我是吃力不討好… 可是我仍是但願能夠在這裏提供一個完整的解決方案給你們。簡單的多進程使用,並且效果顯著的,好比把圖片選擇和WebView配置到獨立的進程,這個我但願能夠你們行動起來。這篇文章的知識點很是多,理解起來可能不是太容易,若是有興趣,我建議你手動去寫一下,而後不理解的地方,打斷點看看是什麼樣的運行步驟。
對於面試的同窗,若是在面試過程當中說到多進程,跟面試官聊得開,估計也是能加點分的,或者在實際工做中,一些使用多進程能夠更好地解決問題的地方,你能夠在會議中拍桌猛起,跟主管說:「我有一個大膽的想法…」,這樣裝逼也不錯(固然,被炒了的話就不關個人事了…)
文章不易,若是你們喜歡這篇文章,或者對你有幫助但願你們多多點贊轉發關注哦。文章會持續更新的。絕對乾貨!!!