淺談項目架構重構之路——組件化與MVP

忙了一個多月,一直沒時間寫文章。終于把項目重構完了,借此機會淺談一下對Android架構的見解。筆者將會把重構分為三個部分講解。
上一篇文章中,我們介紹了項目全局架構重構的方案,即模塊化。接下來這篇文章將介紹局部架構重構方案。
前兩篇為 概述篇模塊化篇

[如有解釋錯誤的地方,歡迎評論區指正探討]


為什么要進行局部架構重構

解決完全局架構的優化,整個項目已經實現模塊化,然而這樣就足夠了嗎?
來看一看模塊內部是什么樣的,先看一下相對于簡單的模塊:


簡單的局部.png

看起來還挺干凈利落的對吧?采用mvc模式,一個activity承載一個業務。
那么當業務變得復雜且需要與其他activity共用又會變成什么樣?以我們項目中相冊業務為一個例子。

復雜的局部.png

可以看到,這個層次結構比上面的復雜了一些,在我們的項目中,有幾個業務具有相冊業務,為了實現可復用性,我們抽取了共用的相冊業務Activity,提供一些相冊相關的基礎業務和操作。由于校園業務的相冊又分視頻相冊和圖片相冊,而這兩個相冊又只有點擊和界面的些許不同,大部分邏輯相同,于是又抽取了校園相冊的基礎Activity。這是這個復雜業務的大致情況,實際為了復用,這里并不止抽取了這幾個Activity,大概是一個不同業務就抽了一個共用的Activity,導致相冊這一塊的代碼邏輯和結構層次極其復雜,混亂。

抽取共用的Activity確實提高了復用度,但是當業務復雜起來,就帶來了嚴重的冗余問題,同時業務的不合理拆分容易造成開發時的混亂。

再看看mvc這塊,在圖中可能看不出什么端倪,但實際上,在復雜的業務模型下,view與model之間往往糾纏不清,view持有多個model,多個model又持有view,同時大量的controller代碼和view代碼糅合在Activity中,不僅使得Activity的代碼量劇增,難以維護,往往還帶來了不少內存泄漏的問題。

為此,筆者提出了采用組件化來改善現有架構,并引入MVP模式優化業務邏輯的處理。
先來看看引入組件化+MVP的架構,能解決什么問題:

  • 實現代碼上的解耦,減少冗余的Activity
  • 模塊職責劃分明顯,層次清晰
  • 實現各通用功能的高復用性和靈活性
  • 通過組件組裝的方式,使得整個模塊層次結構分明
  • 組件可以作為獨立工程開發,因此對功能的開發,測試不用再進行全項目編譯
  • 解決在mvc模式容易因接口回調導致內存泄漏的問題

下面來看一下,組件化+MVP是什么樣的架構。

什么是組件化與MVP

組件化

在上一篇文章中有提到組件和模塊的概念,所謂組件指的是構成業務模塊或業務功能的基本單位
正如上一篇文章中提到,朋友圈模塊由Uploader圖片上傳組件等多個組件以特定的邏輯組合而成。

組件化與上一篇文章提到的模塊化并不是同一個層次的概念,組件化的粒度更小,筆者更偏向于用Lib來形容一個組件。就比如我們常用的Picasso,這是一個圖片加載組件,又如OkHttp是一個網絡請求組件。這樣子的單一功能或單一功能集的Lib,便是組件。

實現組件化即將項目中的業務功能進行細分,劃分出粒度更小的通用功能組件,這些組件可以在多個項目中實現復用,而不再單單只歸屬于某一特定項目。
舉個例子來說,前面有提到筆者參與項目有個相冊模塊,看似抽離了很多通用的Activity,好像可以在其他項目中復用,可實際上呢?僅僅只有最上層的一個Activity可以實現復用,其他Activity都與View強耦合。而最上層的Activity功能又是最少的,根本無法實現復用,只能說減少了一點代碼量。

對此,筆者將整個相冊模塊抽離成了一個圖片展示組件:


圖片展示組件.png

這個組件依賴于圖片加載組件,也就是Picasso之類的框架,實現了通用的圖片列表,圖片九宮圖,圖片添加刪除等展示功能。原來所有冗余的Activity全部棄用,只需實現一個承載特定業務邏輯的界面,并使用該組件進行展示即可。

MVP

對于MVP模式相信大家都不陌生,MVP模式是傳統安卓MVC模式的改良版,將原本承擔View和Controller功能的Activity改良為只承擔View這一功能,引入Presenter作為View與Model層的中介,架設特定的業務邏輯。
引用一張經典MVP的圖片來解釋:(原文)

mvp.png

對于MVP模式,網上包括Google官方都有提出幾種不同的實現方法,常見的有todo-mvpmvp-clean等,其實對于MVP的實現方式,不同人有不同的見解,不同方式也適用于不同的項目,因此只要理解MVP的思想,以最適用自己項目的方式實現即可。在筆者項目中主要以todo-mvp為原型進行改良,實現了一套自己的MVP模式

如何實現組件化和MVP

如何實現組件化

對于組件化開發,從功能的角度,筆者將組件分為兩種:功能型組件View型組件。簡單的來說,View型組件一般比功能型組件多了View層的實現。

  • 功能型組件
    也就是日志組件、網絡組件、圖片加載組件等基礎組件。這些組件一般不具備View,只負責提供邏輯功能,需要開發者自己實現特定的View來搭載這些功能。
    對于功能型組件的實現大家應該都比較熟悉,就算沒有實現過也應該用過各種第三方的開源組件。

  • View型組件
    而對于View型組件也就是筆者項目中的圖片展示組件以及知乎的Matisse組件。這樣的組件不僅僅具有邏輯功能,還附帶View的實現。
    對于知乎實現的Matisse組件,其采取的是Activity的方式來作為載體,暴露出該Activity的啟動方法作為入口。這是大多數View組件會采取的實現方式。以Activity做為載體,那么實現過程與我們平時進行開發的過程差別不會很多,然而卻不可避免的減少的了靈活性和復用性。并且只能通過繼承來擴展其他功能,組件的通信也十分局限。這也正是筆者項目一開始的實現方式。

具筆者了解,還有另外兩種實現View型組件的方式,一種基于Fragment,另一個種基于Custom View。兩種實現方式各有各有的優缺點。

基于Fragment的View組件

基于Fragment的實現類似于Matisse基于Activity的方式。一樣的生命周期處理,同樣可以套用MVP模式。實現過程與我們平時開發也不會差很多。最后只需要暴露Fragment的創建方式即可。

不同于Activity,采用Fragment使得整個組件靈活了許多。可以采用接口通信,并同時保留了與Activity相應的生命周期處理。很好的劃分了各個組件之間的界限,解決了復用和耦合的問題。

不過,采用Fragment作為主體,那么不可避免的就帶了一系列碎片化的問題,包括旋轉屏幕、狀態改變、Fragment的重建等問題。

基于Custom View組件

首先要說的是這類Custom View不同于常見的自定義View,常見的自定義View往往不具有太多的非View邏輯操作。這類Custom View以自定義View為載體,具備自己的Model層和Presenter層。可以說是另類的MVP模式。

以圖片選擇組件為例子,我們需要的東西有:圖片加載組件(Picasso)、獲取本地圖片的工具類(Model)、承載這兩者的Custom View、以及協調CustomView和Model的Presenter。這個Custom View可以繼承自RecyclerView或者ViewGroup,不同父類保留給外界使用者的特性不同,根據不同情況可以選擇不同父類(如果希望保持足夠多的列表特性給外界,那么可以繼承自RecyclerView,如果不希望外界對內部干預過多,那么繼承自ViewGroup)。而統籌這三者邏輯類似于Matisse,只不過Matisse以Activity為承載。

最后我們只需要將這個Custom View暴露給外界即可,其余邏輯由內部實現。這樣的實現方式避免了采用Fragment而帶來的碎片化問題,也很好的解決了復用和耦合的問題。不過也因此Custom View的生命周期難以管理,無法與Activity對應上,需另行處理。

小結View型組件

對于View型組件的兩種實現方式,筆者選擇了Fragment的方式,一來團隊對于Fragment與MVP的結合比較熟悉,而來Custom View生命周期的管理確實是一個不小的問題,即使解決了管理問題,也帶了不小的研發與學習負擔。而對于Fragment的帶來的碎片化問題,目前來說只需要要求組件繼承BaseFragment基類,由基類統一解決即可。

管理組件

對于組件化,其實實現難點并不多,更多是理解組件化的思想和如何管理各個組件。
對于管理組件而言,尤其是View型組件,網上有很多方案,有不少人建議用Router統一管理,在筆者看來,組件化使用Router,有些大材小用。
在筆者項目中,對于組件化的管理,采用上傳到私有Nexus倉庫,直接通過Gradle依賴管理。

如何實現MVP模式

對于MVP模式,網上已經有很多文章進行解釋,GitHub也有很多案例,這里簡單介紹一下思想,更多詳細建議學習官方DEMO

在MVP模式中,將架構分為三層:

  • View
    View層主要負責界面元素的展示,包括Button、TextView等。由XML和Activity / Fragment組成。前者主要負責靜態界面布局,負責負責動態界面。
  • Model
    Model層通常是MVP模式中最復雜的一層,主要負責數據的請求、讀寫。這一層往往可以分為本地數據和網絡數據。
  • Presenter
    Presenter是MVP模式中極其關鍵的一層,作為View和Model的管理者,也即是中介者。承載著相應的業務邏輯,接收來自View的請求,轉換為對Model的請求,進而接收Model的回復,從而通知View進行刷新。

三個層次之間的關系如下:


mvp層次關系.png
  1. View接收用戶的觸摸事件,傳遞action給Presenter
  2. Presenter接收到action,執行對應的業務邏輯。如果涉及到數據請求,那么發送相應的request給Model
  3. Model接收到request,進行本地或網絡數據請求。待請求結束,返回對應的response給Presenter
  4. Presenter接收到Response,執行對應的業務邏輯。如果需要更新界面元素,那么通知View進行處理

整個流程看起來十分清晰,實現也不難,需要注意的一點是要注意各個層次的調用關系。View層不能主動調用Model,Model亦不可調用View層。兩者需要通過Presener作為橋梁。

再思考

其實對于局部架構的重構,并沒有太多的技巧,更多的是一種思想。
無論是組件化還是MVP,都是力求實現代碼層次的解耦,實現更高程度的復用,是我們的代碼更加優雅。
縱然使用組件化和MVP都會增加我們的代碼量和研發周期(稍微,可接受)。然而,這樣的付出是值得的,但我們回觀整個架構,會感覺一切都是可控的,不管是控件的管理,還是View、Model的管理,都十分靈活。

最后再看一下結合模塊化重構完之后,整個項目的架構:


系統架構設計.png

一目了然,整個App劃分為多個業務模塊,這些業務模塊以Module的形式管理。對于每個Module需要使用的通用功能,也就劃分為了組件,這些組件以Lib的形式管理,藉由Gradle進行依賴管理。
而不管是模塊亦或是組件,我們基本都采用了MVP的模式(部分簡單的模塊、組件除外)。這樣便使得不管是整體架構,亦或是局部架構,都十分靈活可管理。

這樣的重構,何樂而不為?


最后希望筆者分享的一點經驗能對大家提高代碼有些幫助,如有錯誤的地方,歡迎指正探討。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容