淺談項(xiàng)目架構(gòu)重構(gòu)之路——組件化與MVP

忙了一個(gè)多月慌烧,一直沒時(shí)間寫文章冀墨。終于把項(xiàng)目重構(gòu)完了,借此機(jī)會(huì)淺談一下對(duì)Android架構(gòu)的見解献丑。筆者將會(huì)把重構(gòu)分為三個(gè)部分講解末捣。
上一篇文章中,我們介紹了項(xiàng)目全局架構(gòu)重構(gòu)的方案创橄,即模塊化箩做。接下來這篇文章將介紹局部架構(gòu)重構(gòu)方案。
前兩篇為 概述篇模塊化篇

[如有解釋錯(cuò)誤的地方妥畏,歡迎評(píng)論區(qū)指正探討]


為什么要進(jìn)行局部架構(gòu)重構(gòu)

解決完全局架構(gòu)的優(yōu)化邦邦,整個(gè)項(xiàng)目已經(jīng)實(shí)現(xiàn)模塊化,然而這樣就足夠了嗎醉蚁?
來看一看模塊內(nèi)部是什么樣的燃辖,先看一下相對(duì)于簡(jiǎn)單的模塊:


簡(jiǎn)單的局部.png

看起來還挺干凈利落的對(duì)吧?采用mvc模式馍管,一個(gè)activity承載一個(gè)業(yè)務(wù)郭赐。
那么當(dāng)業(yè)務(wù)變得復(fù)雜且需要與其他activity共用又會(huì)變成什么樣薪韩?以我們項(xiàng)目中相冊(cè)業(yè)務(wù)為一個(gè)例子确沸。

復(fù)雜的局部.png

可以看到,這個(gè)層次結(jié)構(gòu)比上面的復(fù)雜了一些俘陷,在我們的項(xiàng)目中罗捎,有幾個(gè)業(yè)務(wù)具有相冊(cè)業(yè)務(wù),為了實(shí)現(xiàn)可復(fù)用性拉盾,我們抽取了共用的相冊(cè)業(yè)務(wù)Activity桨菜,提供一些相冊(cè)相關(guān)的基礎(chǔ)業(yè)務(wù)和操作。由于校園業(yè)務(wù)的相冊(cè)又分視頻相冊(cè)和圖片相冊(cè),而這兩個(gè)相冊(cè)又只有點(diǎn)擊和界面的些許不同倒得,大部分邏輯相同泻红,于是又抽取了校園相冊(cè)的基礎(chǔ)Activity。這是這個(gè)復(fù)雜業(yè)務(wù)的大致情況霞掺,實(shí)際為了復(fù)用谊路,這里并不止抽取了這幾個(gè)Activity,大概是一個(gè)不同業(yè)務(wù)就抽了一個(gè)共用的Activity菩彬,導(dǎo)致相冊(cè)這一塊的代碼邏輯和結(jié)構(gòu)層次極其復(fù)雜缠劝,混亂。

抽取共用的Activity確實(shí)提高了復(fù)用度骗灶,但是當(dāng)業(yè)務(wù)復(fù)雜起來惨恭,就帶來了嚴(yán)重的冗余問題,同時(shí)業(yè)務(wù)的不合理拆分容易造成開發(fā)時(shí)的混亂耙旦。

再看看mvc這塊脱羡,在圖中可能看不出什么端倪,但實(shí)際上免都,在復(fù)雜的業(yè)務(wù)模型下轻黑,view與model之間往往糾纏不清,view持有多個(gè)model琴昆,多個(gè)model又持有view氓鄙,同時(shí)大量的controller代碼和view代碼糅合在Activity中,不僅使得Activity的代碼量劇增业舍,難以維護(hù)抖拦,往往還帶來了不少內(nèi)存泄漏的問題。

為此舷暮,筆者提出了采用組件化來改善現(xiàn)有架構(gòu)态罪,并引入MVP模式優(yōu)化業(yè)務(wù)邏輯的處理。
先來看看引入組件化+MVP的架構(gòu)下面,能解決什么問題:

  • 實(shí)現(xiàn)代碼上的解耦复颈,減少冗余的Activity
  • 模塊職責(zé)劃分明顯,層次清晰
  • 實(shí)現(xiàn)各通用功能的高復(fù)用性和靈活性
  • 通過組件組裝的方式沥割,使得整個(gè)模塊層次結(jié)構(gòu)分明
  • 組件可以作為獨(dú)立工程開發(fā)耗啦,因此對(duì)功能的開發(fā),測(cè)試不用再進(jìn)行全項(xiàng)目編譯
  • 解決在mvc模式容易因接口回調(diào)導(dǎo)致內(nèi)存泄漏的問題

下面來看一下机杜,組件化+MVP是什么樣的架構(gòu)帜讲。

什么是組件化與MVP

組件化

在上一篇文章中有提到組件和模塊的概念,所謂組件指的是構(gòu)成業(yè)務(wù)模塊或業(yè)務(wù)功能的基本單位椒拗。
正如上一篇文章中提到似将,朋友圈模塊由Uploader圖片上傳組件等多個(gè)組件以特定的邏輯組合而成获黔。

組件化與上一篇文章提到的模塊化并不是同一個(gè)層次的概念,組件化的粒度更小在验,筆者更偏向于用Lib來形容一個(gè)組件玷氏。就比如我們常用的Picasso,這是一個(gè)圖片加載組件腋舌,又如OkHttp是一個(gè)網(wǎng)絡(luò)請(qǐng)求組件预茄。這樣子的單一功能或單一功能集的Lib,便是組件侦厚。

實(shí)現(xiàn)組件化即將項(xiàng)目中的業(yè)務(wù)功能進(jìn)行細(xì)分耻陕,劃分出粒度更小的通用功能組件,這些組件可以在多個(gè)項(xiàng)目中實(shí)現(xiàn)復(fù)用刨沦,而不再單單只歸屬于某一特定項(xiàng)目诗宣。
舉個(gè)例子來說,前面有提到筆者參與項(xiàng)目有個(gè)相冊(cè)模塊想诅,看似抽離了很多通用的Activity召庞,好像可以在其他項(xiàng)目中復(fù)用,可實(shí)際上呢来破??jī)H僅只有最上層的一個(gè)Activity可以實(shí)現(xiàn)復(fù)用篮灼,其他Activity都與View強(qiáng)耦合。而最上層的Activity功能又是最少的徘禁,根本無法實(shí)現(xiàn)復(fù)用诅诱,只能說減少了一點(diǎn)代碼量。

對(duì)此送朱,筆者將整個(gè)相冊(cè)模塊抽離成了一個(gè)圖片展示組件:


圖片展示組件.png

這個(gè)組件依賴于圖片加載組件娘荡,也就是Picasso之類的框架,實(shí)現(xiàn)了通用的圖片列表驶沼,圖片九宮圖炮沐,圖片添加刪除等展示功能。原來所有冗余的Activity全部棄用回怜,只需實(shí)現(xiàn)一個(gè)承載特定業(yè)務(wù)邏輯的界面大年,并使用該組件進(jìn)行展示即可。

MVP

對(duì)于MVP模式相信大家都不陌生玉雾,MVP模式是傳統(tǒng)安卓MVC模式的改良版翔试,將原本承擔(dān)View和Controller功能的Activity改良為只承擔(dān)View這一功能,引入Presenter作為View與Model層的中介抹凳,架設(shè)特定的業(yè)務(wù)邏輯遏餐。
引用一張經(jīng)典MVP的圖片來解釋:(原文)

mvp.png

對(duì)于MVP模式伦腐,網(wǎng)上包括Google官方都有提出幾種不同的實(shí)現(xiàn)方法赢底,常見的有todo-mvpmvp-clean等,其實(shí)對(duì)于MVP的實(shí)現(xiàn)方式,不同人有不同的見解幸冻,不同方式也適用于不同的項(xiàng)目粹庞,因此只要理解MVP的思想,以最適用自己項(xiàng)目的方式實(shí)現(xiàn)即可洽损。在筆者項(xiàng)目中主要以todo-mvp為原型進(jìn)行改良庞溜,實(shí)現(xiàn)了一套自己的MVP模式

如何實(shí)現(xiàn)組件化和MVP

如何實(shí)現(xiàn)組件化

對(duì)于組件化開發(fā)碑定,從功能的角度,筆者將組件分為兩種:功能型組件View型組件。簡(jiǎn)單的來說允坚,View型組件一般比功能型組件多了View層的實(shí)現(xiàn)极谊。

  • 功能型組件
    也就是日志組件、網(wǎng)絡(luò)組件碘赖、圖片加載組件等基礎(chǔ)組件驾荣。這些組件一般不具備View,只負(fù)責(zé)提供邏輯功能普泡,需要開發(fā)者自己實(shí)現(xiàn)特定的View來搭載這些功能播掷。
    對(duì)于功能型組件的實(shí)現(xiàn)大家應(yīng)該都比較熟悉,就算沒有實(shí)現(xiàn)過也應(yīng)該用過各種第三方的開源組件撼班。

  • View型組件
    而對(duì)于View型組件也就是筆者項(xiàng)目中的圖片展示組件以及知乎的Matisse組件歧匈。這樣的組件不僅僅具有邏輯功能,還附帶View的實(shí)現(xiàn)砰嘁。
    對(duì)于知乎實(shí)現(xiàn)的Matisse組件眯亦,其采取的是Activity的方式來作為載體,暴露出該Activity的啟動(dòng)方法作為入口般码。這是大多數(shù)View組件會(huì)采取的實(shí)現(xiàn)方式妻率。以Activity做為載體,那么實(shí)現(xiàn)過程與我們平時(shí)進(jìn)行開發(fā)的過程差別不會(huì)很多板祝,然而卻不可避免的減少的了靈活性和復(fù)用性宫静。并且只能通過繼承來擴(kuò)展其他功能,組件的通信也十分局限券时。這也正是筆者項(xiàng)目一開始的實(shí)現(xiàn)方式孤里。

具筆者了解,還有另外兩種實(shí)現(xiàn)View型組件的方式橘洞,一種基于Fragment捌袜,另一個(gè)種基于Custom View。兩種實(shí)現(xiàn)方式各有各有的優(yōu)缺點(diǎn)炸枣。

基于Fragment的View組件

基于Fragment的實(shí)現(xiàn)類似于Matisse基于Activity的方式虏等。一樣的生命周期處理弄唧,同樣可以套用MVP模式。實(shí)現(xiàn)過程與我們平時(shí)開發(fā)也不會(huì)差很多霍衫。最后只需要暴露Fragment的創(chuàng)建方式即可候引。

不同于Activity,采用Fragment使得整個(gè)組件靈活了許多敦跌〕胃桑可以采用接口通信,并同時(shí)保留了與Activity相應(yīng)的生命周期處理柠傍。很好的劃分了各個(gè)組件之間的界限麸俘,解決了復(fù)用和耦合的問題。

不過惧笛,采用Fragment作為主體疾掰,那么不可避免的就帶了一系列碎片化的問題,包括旋轉(zhuǎn)屏幕徐紧、狀態(tài)改變静檬、Fragment的重建等問題。

基于Custom View組件

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

以圖片選擇組件為例子愈涩,我們需要的東西有:圖片加載組件(Picasso)望抽、獲取本地圖片的工具類(Model)、承載這兩者的Custom View履婉、以及協(xié)調(diào)CustomView和Model的Presenter煤篙。這個(gè)Custom View可以繼承自RecyclerView或者ViewGroup,不同父類保留給外界使用者的特性不同毁腿,根據(jù)不同情況可以選擇不同父類(如果希望保持足夠多的列表特性給外界辑奈,那么可以繼承自RecyclerView,如果不希望外界對(duì)內(nèi)部干預(yù)過多已烤,那么繼承自ViewGroup)鸠窗。而統(tǒng)籌這三者邏輯類似于Matisse,只不過Matisse以Activity為承載胯究。

最后我們只需要將這個(gè)Custom View暴露給外界即可稍计,其余邏輯由內(nèi)部實(shí)現(xiàn)。這樣的實(shí)現(xiàn)方式避免了采用Fragment而帶來的碎片化問題裕循,也很好的解決了復(fù)用和耦合的問題臣嚣。不過也因此Custom View的生命周期難以管理净刮,無法與Activity對(duì)應(yīng)上,需另行處理茧球。

小結(jié)View型組件

對(duì)于View型組件的兩種實(shí)現(xiàn)方式庭瑰,筆者選擇了Fragment的方式星持,一來團(tuán)隊(duì)對(duì)于Fragment與MVP的結(jié)合比較熟悉抢埋,而來Custom View生命周期的管理確實(shí)是一個(gè)不小的問題,即使解決了管理問題督暂,也帶了不小的研發(fā)與學(xué)習(xí)負(fù)擔(dān)揪垄。而對(duì)于Fragment的帶來的碎片化問題,目前來說只需要要求組件繼承BaseFragment基類逻翁,由基類統(tǒng)一解決即可饥努。

管理組件

對(duì)于組件化,其實(shí)實(shí)現(xiàn)難點(diǎn)并不多八回,更多是理解組件化的思想和如何管理各個(gè)組件酷愧。
對(duì)于管理組件而言,尤其是View型組件缠诅,網(wǎng)上有很多方案溶浴,有不少人建議用Router統(tǒng)一管理,在筆者看來管引,組件化使用Router士败,有些大材小用。
在筆者項(xiàng)目中褥伴,對(duì)于組件化的管理谅将,采用上傳到私有Nexus倉庫,直接通過Gradle依賴管理重慢。

如何實(shí)現(xiàn)MVP模式

對(duì)于MVP模式饥臂,網(wǎng)上已經(jīng)有很多文章進(jìn)行解釋,GitHub也有很多案例似踱,這里簡(jiǎn)單介紹一下思想擅笔,更多詳細(xì)建議學(xué)習(xí)官方DEMO

在MVP模式中,將架構(gòu)分為三層:

  • View
    View層主要負(fù)責(zé)界面元素的展示屯援,包括Button猛们、TextView等。由XML和Activity / Fragment組成狞洋。前者主要負(fù)責(zé)靜態(tài)界面布局弯淘,負(fù)責(zé)負(fù)責(zé)動(dòng)態(tài)界面。
  • Model
    Model層通常是MVP模式中最復(fù)雜的一層吉懊,主要負(fù)責(zé)數(shù)據(jù)的請(qǐng)求庐橙、讀寫假勿。這一層往往可以分為本地?cái)?shù)據(jù)和網(wǎng)絡(luò)數(shù)據(jù)。
  • Presenter
    Presenter是MVP模式中極其關(guān)鍵的一層态鳖,作為View和Model的管理者转培,也即是中介者。承載著相應(yīng)的業(yè)務(wù)邏輯浆竭,接收來自View的請(qǐng)求浸须,轉(zhuǎn)換為對(duì)Model的請(qǐng)求,進(jìn)而接收Model的回復(fù)邦泄,從而通知View進(jìn)行刷新删窒。

三個(gè)層次之間的關(guān)系如下:


mvp層次關(guān)系.png
  1. View接收用戶的觸摸事件,傳遞action給Presenter
  2. Presenter接收到action顺囊,執(zhí)行對(duì)應(yīng)的業(yè)務(wù)邏輯肌索。如果涉及到數(shù)據(jù)請(qǐng)求,那么發(fā)送相應(yīng)的request給Model
  3. Model接收到request特碳,進(jìn)行本地或網(wǎng)絡(luò)數(shù)據(jù)請(qǐng)求诚亚。待請(qǐng)求結(jié)束,返回對(duì)應(yīng)的response給Presenter
  4. Presenter接收到Response午乓,執(zhí)行對(duì)應(yīng)的業(yè)務(wù)邏輯站宗。如果需要更新界面元素,那么通知View進(jìn)行處理

整個(gè)流程看起來十分清晰硅瞧,實(shí)現(xiàn)也不難份乒,需要注意的一點(diǎn)是要注意各個(gè)層次的調(diào)用關(guān)系。View層不能主動(dòng)調(diào)用Model腕唧,Model亦不可調(diào)用View層或辖。兩者需要通過Presener作為橋梁。

再思考

其實(shí)對(duì)于局部架構(gòu)的重構(gòu)枣接,并沒有太多的技巧颂暇,更多的是一種思想。
無論是組件化還是MVP但惶,都是力求實(shí)現(xiàn)代碼層次的解耦耳鸯,實(shí)現(xiàn)更高程度的復(fù)用,是我們的代碼更加優(yōu)雅膀曾。
縱然使用組件化和MVP都會(huì)增加我們的代碼量和研發(fā)周期(稍微县爬,可接受)。然而添谊,這樣的付出是值得的财喳,但我們回觀整個(gè)架構(gòu),會(huì)感覺一切都是可控的,不管是控件的管理耳高,還是View扎瓶、Model的管理,都十分靈活泌枪。

最后再看一下結(jié)合模塊化重構(gòu)完之后概荷,整個(gè)項(xiàng)目的架構(gòu):


系統(tǒng)架構(gòu)設(shè)計(jì).png

一目了然,整個(gè)App劃分為多個(gè)業(yè)務(wù)模塊碌燕,這些業(yè)務(wù)模塊以Module的形式管理误证。對(duì)于每個(gè)Module需要使用的通用功能,也就劃分為了組件陆蟆,這些組件以Lib的形式管理雷厂,藉由Gradle進(jìn)行依賴管理惋增。
而不管是模塊亦或是組件叠殷,我們基本都采用了MVP的模式(部分簡(jiǎn)單的模塊、組件除外)诈皿。這樣便使得不管是整體架構(gòu)林束,亦或是局部架構(gòu),都十分靈活可管理稽亏。

這樣的重構(gòu)壶冒,何樂而不為?


最后希望筆者分享的一點(diǎn)經(jīng)驗(yàn)?zāi)軐?duì)大家提高代碼有些幫助截歉,如有錯(cuò)誤的地方胖腾,歡迎指正探討。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瘪松,一起剝皮案震驚了整個(gè)濱河市咸作,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宵睦,老刑警劉巖记罚,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異壳嚎,居然都是意外死亡桐智,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門烟馅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來说庭,“玉大人,你說我怎么就攤上這事郑趁】浚” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵穿撮,是天一觀的道長(zhǎng)缺脉。 經(jīng)常有香客問我痪欲,道長(zhǎng),這世上最難降的妖魔是什么攻礼? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任业踢,我火速辦了婚禮,結(jié)果婚禮上礁扮,老公的妹妹穿的比我還像新娘知举。我一直安慰自己,他們只是感情好太伊,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布雇锡。 她就那樣靜靜地躺著,像睡著了一般僚焦。 火紅的嫁衣襯著肌膚如雪锰提。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天芳悲,我揣著相機(jī)與錄音立肘,去河邊找鬼。 笑死名扛,一個(gè)胖子當(dāng)著我的面吹牛谅年,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肮韧,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼融蹂,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了弄企?” 一聲冷哼從身側(cè)響起超燃,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎桩蓉,沒想到半個(gè)月后淋纲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡院究,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年洽瞬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片业汰。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡伙窃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出样漆,到底是詐尸還是另有隱情为障,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站鳍怨,受9級(jí)特大地震影響呻右,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鞋喇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一声滥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧侦香,春花似錦落塑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至散吵,卻和暖如春龙考,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背错蝴。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國(guó)打工洲愤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留颓芭,地道東北人顷锰。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像亡问,于是被迫代替她去往敵國(guó)和親官紫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容