忙了一個(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)單的模塊:
看起來還挺干凈利落的對(duì)吧?采用mvc模式馍管,一個(gè)activity承載一個(gè)業(yè)務(wù)郭赐。
那么當(dāng)業(yè)務(wù)變得復(fù)雜且需要與其他activity共用又會(huì)變成什么樣薪韩?以我們項(xiàng)目中相冊(cè)業(yè)務(wù)為一個(gè)例子确沸。
可以看到,這個(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è)圖片展示組件:
這個(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的圖片來解釋:(原文)
對(duì)于MVP模式伦腐,網(wǎng)上包括Google官方都有提出幾種不同的實(shí)現(xiàn)方法赢底,常見的有todo-mvp與mvp-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)系如下:
- View接收用戶的觸摸事件,傳遞action給Presenter
- Presenter接收到action顺囊,執(zhí)行對(duì)應(yīng)的業(yè)務(wù)邏輯肌索。如果涉及到數(shù)據(jù)請(qǐng)求,那么發(fā)送相應(yīng)的request給Model
- Model接收到request特碳,進(jìn)行本地或網(wǎng)絡(luò)數(shù)據(jù)請(qǐng)求诚亚。待請(qǐng)求結(jié)束,返回對(duì)應(yīng)的response給Presenter
- 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):
一目了然,整個(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ò)誤的地方胖腾,歡迎指正探討。