Android架構思考(模塊化、多進程)(轉載學習)

原文地址: Android架構思考(模塊化、多進程)android

關於模塊化(組件化)這個問題,我想每一個開發者可能都認真的思考過。隨着項目的開發,業務不斷壯大,業務模塊愈來愈多,各個模塊間相互引用,耦合愈來愈嚴重,同時有些項目(好比咱們公司)還伴隨着子應用單獨包裝推廣,影子應用單獨發佈等等需求,從新調整架構迫在眉睫。今天,咱們就來聊聊模塊化(組件化),這篇文章同時也是我這幾年,對項目架構的理解。編程

最初的超小型項目設計模式

當咱們最開始作Android項目的時候,大多數人都是沒考慮項目架構的,咱們先上一張圖。服務器

這裏寫圖片描述

這個分包結構有沒有很熟悉,各類組件都碼在一個包裏,徹底沒有層級結構,業務、界面、
邏輯都耦合在一塊兒。這是我12年末剛開始入門Android的時候開發的一個小項目,半年後,來了個小夥伴,而後咱們一塊兒開發,而後每天由於誰修改了誰的代碼打的不可開交。markdown

架構改進,小型項目數據結構

再後來開發App,人員比以前多了,因此不能按照之前那樣了,必須得重構。因而我把公用的代碼提取出來製做成SDK基礎庫,把單獨的功能封裝成Library包,不一樣業務經過分包結構分到不一樣module下,組內每人開發本身的module。剛開始都還輕鬆加愉快,並行開發啥的,一片融洽的場景,以下圖。架構

這裏寫圖片描述

剛剛重構以後的架構併發

隨着時間推移,咱們的App迭代了幾個版本,這幾個版本也沒什麼別的,大致來說就是三件事情:app

擴展了一些新業務模塊,同時模塊間相互調用也增長了。
修改增長了一些新的庫文件,來支持新的業務模塊。
對Common SDK進行了擴展、修復。
很慚愧,就作了一些微小的工做,可是架構就變成下圖這樣。框架

這裏寫圖片描述

作了幾件微小的工做以後

能夠看到,隨着幾個版本業務的增長,各個業務某塊之間耦合愈發嚴重,致使代碼很難維護,更新,更別說寫測試代碼了。雖而後期引入統一廣播系統,必定程度改善了模塊間相互引用的問題,可是侷限性和耦合性仍是很高,沒辦法根治這個問題。這個架構作到最後,擴展性和可維護性都是不好,而且難以測試,因此最終被歷史的進程所拋棄。

中小型項目,路由架構

時間很快就來到了2015年,這一年動態加載、熱修復很火,360、阿里等大公司前後開源了本身的解決方案,如droidplugin、andfix等。在研究了一圈發現,這些技術對架構升級有必定的幫助,尤爲是droidplugin的加載apk的思想,能很好地解決耦合度高、方法數超過6553五、動態修復bug等問題,不過因爲項目自己不是很大,而且沒有專門的人來維護架構,因此最後放棄了功能強大、可是問題也一樣多的插件化,退而求其次,選擇了利用路由機制來實現組件化解耦。

關於路由機制,熟悉iOS開發的朋友可能並不陌生,在iOS上有不少架構方案都是採用路由機制來時間模塊之間的解耦的,好比VIPER(View Interactor Presenter Entity Routing)思想等等。其實思路都是相同的,Android上面組件化也是經過公用的路由,來實現模塊與模塊之間的隔離。

實現原理

咱們先來看下路由架構圖。

這裏寫圖片描述

經過上圖能夠看到,咱們在最基礎的Common庫中,建立了一個路由Router,中間有n個模塊Module,這個Module實際上就是Android Studio中的module,這些Module都是Android Library Module,最上面的Module Main是可運行的Android Application Module。

這幾個Module都引用了Common庫,同時Main Module還引用了A、B、N這幾個Module,通過這樣的處理以後,全部的Module之間的相互調用就都消失了,耦合性下降,全部的通訊統一都交給Router來處理分發,而註冊工做則交由Main Module去進行初始化。這個架構思想其實和Binder的思想很相似,採用C/S模式,模塊之間隔離,數據經過共享區域進行傳遞。模塊與模塊之間只暴露對外開放的Action,因此也具有面向接口編程思想。

圖中的紅色矩形表明的是行動Action,Action是具體的執行類,其內部的invoke方法是具體執行的代碼邏輯。若是涉及到併發操做的話,能夠在invoke方法內加入鎖,或者直接在invoke方法上加上synchronized描述。

圖中的黃色矩形表明的是供應商Provider,每一個Provider中包含1個或多個Action,其內部的數據結構以HashMap來存儲Action。首先HashMap查詢的時間複雜度是O(1),符合咱們對調用速度上的要求,其次,因爲咱們是統一進行註冊,因此在寫入時並不存在併發線程併發問題,在讀取時,併發問題則交由Action的invoke去具體處理。在每個Module內都會有1個或多個供應商Provider(若是不包含Provider,那麼這個Module將沒法爲其餘Module提供服務)。

途中藍色矩形表明的是路由Router,每一個Router中包含多個Provider,其內部的數據結構也是以HashMap來存儲Provider,原理也和Provider是同樣的。之因此用了兩次HashMap,有兩點緣由,一個是由於這樣作,不容易致使Action的重名,另外一個是由於在註冊的時候,只註冊Provider會減小注冊代碼,更易讀。而且因爲HashMap的查詢時間複雜度是O(1),因此兩次查找不會浪費太多時間。當查找不到對應Action的時候,Router會生成一個ErrorAction,會告之調用者沒有找到對應的Action,由調用者來決定接下來如何處理。

一次請求流程

經過Router調用的具體流程是這樣的:

這裏寫圖片描述

Router時序圖

任意代碼建立一個RouterRequest,包含Provider和Action信息,向Router進行請求。
Router接到請求,經過RouterRequest的Provider信息,在內部的HashMap中查找對應的Provider。
Provider接到請求,在內部的HashMap中查找到對應的Action信息。
Action調用invoke方法。
返回invoke方法生成的ActionResult。
將Result封裝成RouterResponse,返回給調用者。
耦合下降

全部的Module之間的相互依賴沒有了,咱們能夠在主app中,取消任意的Module引用而不影響總體App的編譯及運行。

這裏寫圖片描述

取消對Module N的依賴

如圖所示,咱們取消了對Module N的依賴,總體應用依然能夠穩定運行,遇到調用Module N的地方,會返回Not Found提示,實際開發中能夠根據需求作具體的處理。

可測試性加強

因爲每一個Module並不依賴其餘的Module,因此在開發過程當中,咱們只針對本身的模塊進行開發,並能夠建一個測試App來進行白盒測試。

這裏寫圖片描述

測試Module A

複用性加強

關於複用性這塊。做者所處的行業是招商投資這塊,這個行業須要圍繞主業務開發不少影子APP,將覆蓋面擴大(有點相似58->58租房、58招聘,美團->美團外賣等)。這個時候,這個架構的複用性就體現出來了,咱們能夠把業務進行拆分,而後寫一個包裝App,就能夠生成一個獨立的影子APP,這個影子APP用到哪些Module就引用哪些就能夠了,開發迅速,而且後期Module業務有變化,也不用更改全部的代碼,減小了代碼的複製。好比咱們就曾經把IM模塊和投資諮詢模塊單獨拿出來,寫了一些界面和樣式,就生成了「招商經紀人」App。

支持並行開發

整套架構很相似Git的Branch思想,基於主線,分支單獨開發,最後再回歸主線這種思路。這裏只是思路和branch類似,實際的開發過程當中,咱們每一個module能夠是一個branch,也能夠是一個倉庫。每一個模塊都須要本身有單獨的版本控制,便於問題管理及溯源。主項目對各個模塊的引用能夠是直接引用,也能夠是導出aar引用,或者是上傳JCenter Maven等等方式。不過思路是統一的:繼承公共->獨立開發->主線合併。

基礎庫

2017.2.20新增

最近有朋友在評論裏問公共的類還有共有資源怎麼處理,其實很是簡單,咱們在Router和Module之間再加一層,加一層CommonBaseLibrary,裏面放一些全部項目都會用到的資源文件,Model類,工具類等等,而後CommonBaseLibrary再引入Router便可。

以下圖

這裏寫圖片描述

須要注意的是,咱們的Module A,不須要CommonBaseLibrary中的公共資源,因此沒有引用CommonBaseLibrary,可是實際其餘仍是能夠被其餘模塊所調用,由於它內部有Router。

多進程思考,中型項目

隨着項目的不斷擴大,App在運行時的內存消耗也在不斷增長,並且有時線上的BUG也會致使總體崩潰。爲了保證良好的用戶體驗,減小對系統資源的消耗,咱們開始考慮採起多進程從新架構程序,經過按需加載,及時釋放,達到優化的目的。

多進程優點

多進程的優勢和使用場景,以前在《Android多進程使用場景》中也作過介紹,大致優勢有這麼幾個:

提升各個進程的穩定性,單一進程崩潰後不影響整個程序。
對於內存的時候更可控,能夠經過手工釋放進程,達到內存優化目的。
基於獨立的JVM,各個模塊能夠充分解耦。
只保留daemon進程的狀況下,會使應用存活時間更長,不容易被回收掉。
潛在問題

可是啓用多進程,那就意味着Router系統的失效。Router是JVM級別的單例模式,並不支持跨進程訪問。也就是說,你的後臺進程的全部Provider、Action,是註冊給後臺Router的。當你在前臺進程調用的時候,根本調用不到其餘進程的Action。

解決方案

其實解決的方法也並不複雜。原來的路由系統還能夠繼續使用,咱們能夠把整套架構想象成互聯網,如今多個進程有多個路由,咱們只須要把多個路由鏈接到一塊兒,那麼整個路由系統仍是能夠正常運行的。因此咱們把原有的路由Router稱之爲本地路由LocalRouter,如今,咱們須要提供一個IPS、DNS供應商,那就建立一個進程,該進程的做用就是註冊路由,連接路由,轉發報文,咱們稱之爲廣域路由WideRouter。

咱們先來看下路由鏈接架構圖

這裏寫圖片描述

路由鏈接架構

如圖所示,豎直方向上,每一列,表明一個進程,經過虛線隔開,分別有Process WideRouter、Process Main、Process A、···、Process N這些進程。淺黃色的表明WideRouter,深黃色的表明WideRouter的守護Service。淺藍色的表明每一個進程的LocalRouter,深藍色的表明每一個LocalRouter的守護Service。WideRouter經過AIDL與每一個進程LocalRouter的守護Service綁定到一塊兒,每一個LocalRouter也是經過AIDL與WideRouter的守護Service綁定到一塊兒,這樣,就達到了全部路由都是雙向互連的目的。

事件分發

以前單一路由的事件分發是經過兩層HashMap查找Provider和Action,進行事件下發。那麼如今在外面加了一層WideRouter,那麼咱們再加一層Domain,Domain對應的是Android應用內,各個進程的進程名。一般狀況下,若是事件是在同一進程下,那麼就相似於局域網內部事件傳遞,不須要經過WideRouter,直接內部按照以前的路由邏輯進行轉發,若是不在相同進程內,就由WideRouter進行進程間通訊,達到跨進程調用的效果。

事件請求RouterRequest能夠寫成兩種,一種是URL,一種JSON。(內部處理的時候統一使用JSON),同時也提供了對URL和JSON的解析方法,方便使用。

URL:xxxDomain/xxxProvider/xxxAction?data1=xxx&data2=xxx
這就和Http請求很像了。這樣作的好處就是對後續WebView上能夠很是便利得直接調用本地Action。

JSON:

{
domain: xxx,
provider: xxx,
action: xxx,
data{
data1: xxx,
data2: xxx
}
}
JSON方式簡單明瞭,可做爲接口返回值由服務器下發給客戶端。

下面仔細講一下一次跨進程請求,事件是如何傳遞的:

這裏寫圖片描述

事件傳遞圖

從圖中能夠清晰地看出,咱們主要是分兩大部分去完成事件分發傳遞的。

第一部分,跨進程判斷目標Action是不是異步程序。
第二部分,跨進程執行目標Action調用。
首先咱們先經過Domain、Provider、Action去跨進程查找是不是異步程序。若是是異步程序,那麼咱們直接生成RouterResponse(Step13),而且,將Step14-Step24統一封裝成Future,放在RouterResponse中,直接返回。若是是同步程序,那麼就在當前方法內執行Step14-Step24,將返回結果放入RouterResponse內(Step25),直接返回。這麼作的目的是,咱們的路由調用方法route(RouterRequest)默認是同步方法,不耗時的,能夠直接在主線程裏調用而不形成阻塞,不形成ANR。若是調用的目標Action是異步的,那麼能夠利用Java的FutureTask原理,調用RouterResponse的get()方法,獲取結果。這個get()方法有多是耗時的,是否耗時,取決於RouterResponse.isAsync的值是不是true。

至於本地事件分發,仍是與以前的Router模式,從Step17到Step21,都是咱們上文中,單進程同步Router分發機制,沒有做任何改變。

多進程Application邏輯分發

在多進程中,每啓動一個新的進程,都會從新建立一次Application,因此,咱們須要把各個進程的Application邏輯剝離出來,而後根據不一樣的Process Name,選擇不一樣的Application邏輯進行處理。

實際的Application啓動流程以下:

這裏寫圖片描述

多進程Application啓動流程

首先,咱們先把全部ApplicationLogic註冊到Application中,而後,Application會根據註冊時的進程名信息進行篩選,選擇相同進程名的ApplicationLogic,保存到本進程中,而後,對這些本進程的ApplicationLogic進行實例化,最後,調用ApplicationLogic的onCreate方法,實現ApplicationLogic與Application生命週期同步,同時還有onTerminate、onLowMemory、onTrimMemory、onConfigurationChanged等方法,與onCreate一致。

結束進程,釋放內存

在咱們不使用某些進程的時候,好比聽音樂的時候,能夠把主界面關掉等等。咱們能夠調用對應進程的LocalRouter的stopSelf()方法,該方法可使本進程與WideRouter進行解綁,而後咱們在手動關掉進程內的其餘組件,最後調用System.exit(),達到釋放內存的目的。合理的釋放內存,能有效的改善用戶體驗。

小結

這篇文章大概講了一下做者這幾年對Android架構的理解。其實本文中沒有什麼很深的技術點,大可能是一些設計模式,架構思想。這套框比起大公司的一些優秀的動態更新、編譯分包、apk插件化加載,仍是簡單不少的,更適合中小型應用。

這套框架目前還有比較多能夠改進的地方,目前正在整理的:

增長對Action的動態關閉功能。
經過Instant Run原理,實現Action的熱更新。
增長Message Pool,實現Request、Response的循環利用,減小GC觸發。
已解決《高併發對象池思考》
優化Message在傳遞過程當中的打包,拆包的速度,提高總體性能。

原文地址: Android架構思考(模塊化、多進程)

相關文章
相關標籤/搜索