忙了一個多月,一直沒時間寫文章。終于把項目重構完了,借此機會淺談一下對Android架構的見解。筆者將會把重構分為三個部分講解。
上一篇文章中,我們介紹了項目全局架構重構的方案,即模塊化。接下來這篇文章將介紹局部架構重構方案。
前兩篇為 概述篇與模塊化篇
[如有解釋錯誤的地方,歡迎評論區指正探討]
為什么要進行局部架構重構
解決完全局架構的優化,整個項目已經實現模塊化,然而這樣就足夠了嗎?
來看一看模塊內部是什么樣的,先看一下相對于簡單的模塊:
看起來還挺干凈利落的對吧?采用mvc模式,一個activity承載一個業務。
那么當業務變得復雜且需要與其他activity共用又會變成什么樣?以我們項目中相冊業務為一個例子。
可以看到,這個層次結構比上面的復雜了一些,在我們的項目中,有幾個業務具有相冊業務,為了實現可復用性,我們抽取了共用的相冊業務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功能又是最少的,根本無法實現復用,只能說減少了一點代碼量。
對此,筆者將整個相冊模塊抽離成了一個圖片展示組件:
這個組件依賴于圖片加載組件,也就是Picasso
之類的框架,實現了通用的圖片列表,圖片九宮圖,圖片添加刪除等展示功能。原來所有冗余的Activity全部棄用,只需實現一個承載特定業務邏輯的界面,并使用該組件進行展示即可。
MVP
對于MVP模式相信大家都不陌生,MVP模式是傳統安卓MVC模式的改良版,將原本承擔View和Controller功能的Activity改良為只承擔View這一功能,引入Presenter作為View與Model層的中介,架設特定的業務邏輯。
引用一張經典MVP的圖片來解釋:(原文)
對于MVP模式,網上包括Google官方都有提出幾種不同的實現方法,常見的有todo-mvp與mvp-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進行刷新。
三個層次之間的關系如下:
- View接收用戶的觸摸事件,傳遞action給Presenter
- Presenter接收到action,執行對應的業務邏輯。如果涉及到數據請求,那么發送相應的request給Model
- Model接收到request,進行本地或網絡數據請求。待請求結束,返回對應的response給Presenter
- Presenter接收到Response,執行對應的業務邏輯。如果需要更新界面元素,那么通知View進行處理
整個流程看起來十分清晰,實現也不難,需要注意的一點是要注意各個層次的調用關系。View層不能主動調用Model,Model亦不可調用View層。兩者需要通過Presener作為橋梁。
再思考
其實對于局部架構的重構,并沒有太多的技巧,更多的是一種思想。
無論是組件化還是MVP,都是力求實現代碼層次的解耦,實現更高程度的復用,是我們的代碼更加優雅。
縱然使用組件化和MVP都會增加我們的代碼量和研發周期(稍微,可接受)。然而,這樣的付出是值得的,但我們回觀整個架構,會感覺一切都是可控的,不管是控件的管理,還是View、Model的管理,都十分靈活。
最后再看一下結合模塊化重構完之后,整個項目的架構:
一目了然,整個App劃分為多個業務模塊,這些業務模塊以Module的形式管理。對于每個Module需要使用的通用功能,也就劃分為了組件,這些組件以Lib的形式管理,藉由Gradle進行依賴管理。
而不管是模塊亦或是組件,我們基本都采用了MVP的模式(部分簡單的模塊、組件除外)。這樣便使得不管是整體架構,亦或是局部架構,都十分靈活可管理。
這樣的重構,何樂而不為?
最后希望筆者分享的一點經驗能對大家提高代碼有些幫助,如有錯誤的地方,歡迎指正探討。