如何巧用Android多進程,你不知道的點全總結!微信,微博等主流App都在用



Java和Android架構

歡迎關注我們,一起討論技術,掃描和長按下方的二維碼可快速關注我們。搜索微信公衆號:JANiubility。

公衆號:JANiubility






目錄

  1. 前言

  2. 爲什麼要使用多進程?

  3. 爲什麼需要“跨進程通訊”?

  4. 跨進程通訊的方式有哪些?

  5. 使用AIDL實現一個多進程消息推送

  6. 實現思路

  7. 例子具體實現

  8. 知其然,知其所以然。

  9. 跨進程的回調接口

  10. DeathRecipient

  11. 權限驗證

  12. 根據不同進程,做不同的初始化工作

  13. 總結

  14. 結語


1

前言

對於進程的概念,就不再囉嗦了,相信大家都能背出來。


2

爲什麼要使用多進程?

相信很多同學在實際開發中,基本都不會去給app劃分進程,而且,在Android中使用多進程,還可能需要編寫額外的進程通訊代碼,還可能帶來額外的Bug,這無疑加大了開發的工作量,在很多創業公司中工期也不允許,這導致了整個app都在一個進程中。


整個app都在一個進程有什麼弊端?


在Android中,虛擬機分配給各個進程的運行內存是有限制值的(這個值可以是32M,48M,64M等,根據機型而定),試想一下,如果在app中,增加了一個很常用的圖片選擇模塊用於上傳圖片或者頭像,加載大量Bitmap會使app的內存佔用迅速增加,如果你還把查看過的圖片緩存在了內存中,那麼OOM的風險將會大大增加,如果此時還需要使用WebView加載一波網頁,我就問你怕不怕!


微信,微博等主流app是如何解決這些問題的?


微信移動開發團隊在 《
Android內存優化雜談》 一文中就說到:“對於webview,圖庫等,由於存在內存系統泄露或者佔用內存過多的問題,我們可以採用單獨的進程。微信當前也會把它們放在單獨的tools進程中”。


下面我們使用adb查看一下微信和微博的進程信息(Android 5.0以下版本可直接在“設置 -> 應用程序”相關條目中查看):

進入adb shell後,使用 “ps | grep 條目名稱” 可以過濾出想要查看的進程。


可以看到,微信的確有一個tools進程,而新浪微博也有image相關的進程,而且它們當中還有好些其它的進程,比如微信的push進程,微博的remote進程等,這裏可以看出,他們不單單只是把上述的WebView、圖庫等放到單獨的進程,還有推送服務等也是運行在獨立的進程中的。一個消息推送服務,爲了保證穩定性,可能需要和UI進程分離,分離後即使UI進程退出、Crash或者出現內存消耗過高等情況,仍不影響消息推送服務。


可見,合理使用多進程不僅僅是有多大好處的問題,我個人認爲而且是很有必要的。


所以說,我們最好還是根據自身情況,考慮一下是否需要拆分進程。這也是本文的初衷:給大家提供一個多進程的參考思路,在遇到上述問題和場景的時候,可以考慮用多進程的方法來解決問題,又或者,在面試的時候,跟面試官聊到這方面的知識時候也不至於尷尬。


3

爲什麼需要“跨進程通訊”?

Android的進程與進程之間通訊,有些不需要我們額外編寫通訊代碼,例如:把選擇圖片模塊放到獨立的進程,我們仍可以使用startActivityForResult方法,將選中的圖片放到Bundle中,使用Intent傳遞即可。(看到這裏,你還不打算把你項目的圖片選擇弄到獨立進程麼?)


但是對於把“消息推送Service”放到獨立的進程,這個業務就稍微複雜點了,這個時候可能會發生Activity跟Service傳遞對象,調用Service方法等一系列複雜操作。


由於各個進程運行在相對獨立的內存空間,所以它們是不能直接通訊的,因爲程序裏的變量、對象等初始化後都是具有內存地址的,舉個簡單的例子,讀取一個變量的值,本質是找到變量的內存地址,取出存放的值。不同的進程,運行在相互獨立的內存(其實就可以理解爲兩個不同的應用程序),顯然不能直接得知對方變量、對象的內存地址,這樣的話也自然不能訪問對方的變量,對象等。此時兩個進程進行交互,就需要使用跨進程通訊的方式去實現。簡單說,跨進程通訊就是一種讓進程與進程之間可以進行交互的技術。


4

跨進程通訊的方式有哪些?

  1. 四大組件間傳遞Bundle;


  2. 使用文件共享方式,多進程讀寫一個相同的文件,獲取文件內容進行交互;


  3. 使用Messenger,一種輕量級的跨進程通訊方案,底層使用AIDL實現(實現比較簡單,博主開始本文前也想了一下是否要說一下這個東西,最後還是覺得沒有這個必要,Google一下就能解決的問題,就不囉嗦了);


  4. 使用AIDL(Android Interface Definition Language),Android接口定義語言,用於定義跨進程通訊的接口;


  5. 使用ContentProvider,常用於多進程共享數據,比如系統的相冊,音樂等,我們也可以通過ContentProvider訪問到;


  6. 使用Socket傳輸數據。


接下來本文將重點介紹使用AIDL進行多進程通訊,因爲AIDL是Android提供給我們的標準跨進程通訊API,非常靈活且強大(貌似面試也經常會問到,但是真正用到的也不多…)。上面所說的Messenger也是使用AIDL實現的一種跨進程方式,Messenger顧名思義,就像是一種串行的消息機制,它是一種輕量級的IPC方案,可以在不同進程中傳遞Message對象,我們在Message中放入需要傳遞的數據即可輕鬆實現進程間通訊。但是當我們需要調用服務端方法,或者存在併發請求,那麼Messenger就不合適了。而四大組件傳遞Bundle,這個就不需要解釋了,把需要傳遞的數據,用Intent封裝起來傳遞即可,其它方式不在本文的討論範圍。


下面開始對AIDL的講解,各位道友準備好渡劫了嗎?


5

使用AIDL實現一個多進程消息推送

像圖片選擇這樣的多進程需求,可能並不需要我們額外編寫進程通訊的代碼,使用四大組件傳輸Bundle就行了,但是像推送服務這種需求,進程與進程之間需要高度的交互,此時就繞不過進程通訊這一步了。
下面我們就用即時聊天軟件爲例,手動去實現一個多進程的推送例子,具體需求如下:


  1. UI和消息推送的Service分兩個進程;


  2. UI進程用於展示具體的消息數據,把用戶發送的消息,傳遞到消息Service,然後發送到遠程服務器;


  3. Service負責收發消息,並和遠程服務器保持長連接,UI進程可通過Service發送消息到遠程服務器,Service收到遠程服務器消息通知UI進程;


  4. 即使UI進程退出了,Service仍需要保持運行,收取服務器消息。


6

實現思路

先來整理一下實現思路:

  1. 創建UI進程(下文統稱爲客戶端);


  2. 創建消息Service(下文統稱爲服務端);


  3. 把服務端配置到獨立的進程(AndroidManifest.xml中指定process標籤);


  4. 客戶端和服務端進行綁定(bindService);


  5. 讓客戶端和服務端具備交互的能力。(AIDL使用);


7

例子具體實現

爲了閱讀方便,下文中代碼將省略非重點部分,可以把本文完整代碼Clone到本地再看文章:

https://github.com/V1sk/AIDL


Step0. AIDL調用流程概覽


開始之前,我們先來概括一下使用AIDL進行多進程調用的整個流程:


  1. 客戶端使用bindService方法綁定服務端;


  2. 服務端在onBind方法返回Binder對象;


  3. 客戶端拿到服務端返回的Binder對象進行跨進程方法調用;

整個AIDL調用過程概括起來就以上3個步驟,下文中我們使用上面描述的例子,來逐步分解這些步驟,並講述其中的細節。


Step1.客戶端使用bindService方法綁定服務端


1.1 創建客戶端和服務端,把服務端配置到另外的進程


  1. 創建客戶端 -> MainActivity;


  2. 創建服務端 -> MessageService;


  3. 把服務端配置到另外的進程 -> android:process=”:remote”


上面描述的客戶端、服務端、以及把服務端配置到另外進程,體現在AndroidManifest.xml中,如下所示:

開啓多進程的方法很簡單,只需要給四大組件指定android:process標籤。


1.2 綁定MessageService到MainActivity


創建MessageService:

此時的MessageService就是剛創建的模樣,onBind中返回了null,下一步中我們將返回一個可操作的對象給客戶端。

客戶端MainActivity調用bindService方法綁定MessageService


這一步其實是屬於Service組件相關的知識,在這裏就比較簡單地說一下,啓動服務可以通過以下兩種方式:


  1. 使用bindService方法 -> bindService(Intent service, ServiceConnection conn, int flags);


  2. 使用startService方法 -> startService(Intent service);


bindService & startService區別:


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支持的數據類型:


  • Java 編程語言中的所有基本數據類型(如 int、long、char、boolean 等等)


  • String和CharSequence


  • Parcelable:實現了Parcelable接口的對象


  • List:其中的元素需要被AIDL支持,另一端實際接收的具體類始終是 ArrayList,但生成的方法使用的是 List 接口


  • Map:其中的元素需要被AIDL支持,包括 key 和 value,另一端實際接收的具體類始終是 HashMap,但生成的方法使用的是 Map 接口


其他注意事項:


  • 在AIDL中傳遞的對象,必須實現Parcelable序列化接口;


  • 在AIDL中傳遞的對象,需要在類文件相同路徑下,創建同名、但是後綴爲.aidl的文件,並在文件中使用parcelable關鍵字聲明這個類;


  • 跟普通接口的區別:只能聲明方法,不能聲明變量;


  • 所有非基礎數據類型參數都需要標出數據走向的方向標記。可以是 in、out 或 inout,基礎數據類型默認只能是 in,不能是其他方向。


下面繼續我們的例子,開始對AIDL的講解~


2.4 創建一個AIDL接口,接口中提供發送消息的方法(Android Studio創建AIDL:項目右鍵 -> New -> AIDL -> AIDL File),代碼如下:

一個比較尷尬的事情,看了很多文章,從來沒有一篇能說清楚in、out、inout這三個參數方向的意義,後來在stackoverflow上找到能理解答案(https://stackoverflow.com/questions/4700225/in-out-inout-in-a-aidl-interface-parameter-value),我翻譯一下大概意思:


被“in”標記的參數,就是接收實際數據的參數,這個跟我們普通參數傳遞一樣的含義。在AIDL中,“out” 指定了一個僅用於輸出的參數,換而言之,這個參數不關心調用方傳遞了什麼數據過來,但是這個參數的值可以在方法被調用後填充(無論調用方傳遞了什麼值過來,在方法執行的時候,這個參數的初始值總是空的),這就是“out”的含義,僅用於輸出。而“inout”顯然就是“in”和“out”的合體了,輸入和輸出的參數。區分“in”、“out”有什麼用?這是非常重要的,因爲每個參數的內容必須編組(序列化,傳輸,接收和反序列化)。in/out標籤允許Binder跳過編組步驟以獲得更好的性能。


上述的MessageModel爲消息的實體類,該類在AIDL中傳遞,實現了Parcelable序列化接口,代碼如下:

手動實現Parcelable接口比較麻煩,安利一款AS自動生成插件android-parcelable-intellij-plugin
創建完MessageModel這個實體類,別忘了還有一件事要做:”在AIDL中傳遞的對象,需要在類文件相同路徑下,創建同名、但是後綴爲.aidl的文件,並在文件中使用parcelable關鍵字聲明這個類“。代碼如下:

對於沒有接觸過aidl的同學,光說就能讓人懵逼,來看看此時的項目結構壓壓驚:

我們剛剛新增的3個文件:

  • MessageSender.aidl -> 定義了發送消息的方法,會自動生成名爲MessageSender.Stub的Binder類,在服務端實現,返回給客戶端調用


  • MessageModel.java -> 消息實體類,由客戶端傳遞到服務端,實現了Parcelable序列化


  • MessageModel.aidl -> 聲明瞭MessageModel可在AIDL中傳遞,放在跟MessageModel.java相同的包路徑下


OK,相信此時懵逼已解除~


2.5 在服務端創建MessageSender.aidl這個AIDL接口自動生成的Binder對象,並返回給客戶端調用,服務端MessageService代碼如下:

MessageSender.Stub是Android Studio根據我們MessageSender.aidl文件自動生成的Binder對象(至於是怎樣生成的,下文會有答案),我們需要把這個Binder對象返回給客戶端。


2.6 客戶端拿到Binder對象後調用遠程方法


調用步驟如下:


  1. 在客戶端的onServiceConnected方法中,拿到服務端返回的Binder對象;


  2. 使用MessageSender.Stub.asInterface方法,取得MessageSender.aidl對應的操作接口;


  3. 取得MessageSender對象後,像普通接口一樣調用方法即可。


此時客戶端代碼如下:

在客戶端中我們調用了MessageSender的sendMessage方法,向服務端發送了一條消息,並把生成的MessageModel對象作爲參數傳遞到了服務端,最終服務端打印的結果如下:

這裏有兩點要說:


  1. 服務端已經接收到客戶端發送過來的消息,並正確打印;


  2. 服務端和客戶端區分兩個進程,PID不一樣,進程名也不一樣;


到這裏,我們已經完成了最基本的使用AIDL進行跨進程方法調用,也是Step.0的整個細化過程,可以再回顧一下Step.0,既然已經學會使用了,接下來…全劇終。。。

如果寫到這裏全劇終,那跟鹹魚有什麼區別…


8

知其然,知其所以然。

我們通過上述的調用流程,看看從客戶端到服務端,都經歷了些什麼事,看看Binder的上層是如何工作的,至於Binder的底層,這是一個非常複雜的話題,本文不深究。(如果看到這裏你又想問什麼是Binder的話,請手動倒帶往上看…)


我們先來回顧一下從客戶端發起的調用流程:


  1. MessageSender messageSender = MessageSender.Stub.asInterface(service);


  2. messageSender.sendMessage(messageModel);


拋開其它無關代碼,客戶端調跨進程方法就這兩個步驟,而這兩個步驟都封裝在 MessageSender.aidl 最終生成的 MessageSender.java 源碼(具體路徑爲:build目錄下某個子目錄,自己找,不爽你來打我啊

發佈了26 篇原創文章 · 獲贊 4 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章