文:https://www.jianshu.com/p/ce26e7960926java
最近App項目(MVC架構)越作越大,協同開發效率較低,維護困難,因此產生了調整架構的想法,在 簡書、csdn、知乎上看了很多文章,感受知乎用戶 0x8421bcd 對於「Android項目開發如何設計總體架構?」的回答頗爲精彩,在此引用,鞠躬感謝!android
想要設計App的總體框架,首先要清楚咱們作的是什麼。通常咱們與網絡交互數據的方式有兩種:主動請求(http)和長鏈接推送。
結合網絡交互數據的方式來講一下咱們開發的App的類型和特色:git
通常咱們作的App都是類型1,簡要來講這類app的主要工做就是把服務端的數據拉下來給用戶展現,把用戶在客戶端修改的數據上傳給服務端處理,因此這類App的網絡調用至關頻繁,並且須要考慮到網絡差、沒網絡等狀況下App可以正常運行。github
成熟的商業應用的網絡調用通常是以下流程:UI發起請求 -> 檢查緩存 -> 調用網絡模塊 -> 解析返回JSON / 統一處理異常 -> JSON對象映射爲Java對象 -> 緩存 -> UI獲取數據並展現,這之中能夠看到很明顯職責劃分,即:數據獲取;數據管理;數據展現。肯定了職責,就能夠進入正題了。數據庫
Android最原生也是最基礎的架構,能夠理解爲MVC(Model-View-Controller),Controller便是Activity和Fragment,可是這二者掌握了Android系統中絕大多數的資源,而且在內部直接控制View,所以MVC架構通常是以Activity和Fragment爲核心,將網絡模塊,數據庫管理模塊,文件管理模塊,經常使用工具類等分離成若干工具類包,供Activity和Fragment使用。json
這是比較基礎的Android項目架構,市面上大部分App都是這種造型。後端
若是仔細看本身的項目,能夠發現絕大多數數據處理的代碼是不須要使用Activity和Fragment持有的資源的(好比Context),而不少時候咱們須要多個頁面共用一套數據和請求邏輯,很經典的例子是應用中的User對象,通常來講都是全局單例。這些全局的數據源寫多了,很容易就能想到將數據處理統一抽出來造成一層,向上層提供數據接口,而上層並不關心數據的來源(內存,緩存,網絡),由於不用從Activity和Fragment拿資源並且主要工做是數據處理,因此這一層是UI無關的,大幅提高了複用性,我把這一層稱爲DataManager層。
這是我一個項目的包結構: 緩存
{
"code":0, "message":"success", "data":{ "page":1, "totalPage":10, "pageSize":20, "total":200, "list":[......] } }
在傳統的寫法中,通常在Activity/Fragment中緩存page,totalPage,pageSize去進行分頁請求,根據請求結果刷新數據並判斷是否還有更多;每個分頁接口都要寫一遍,假如把這段邏輯放到DataManager會怎麼樣?我是這麼寫的:網絡
//定義回調接口 public interface ActionCallback<T> { void onSuccess(T data); void onFailure(String message, Throwable e); }
分頁加載DataManager實現架構
public class PageLoadDataManager extends BaseDataManager { private static final int PAGE_COUNT = 20; private List<Data> mDataList = new ArrayList<>(); private int currentPage = 0; private int totalPage = 0; public PageLoadDataManager() { // init something...... } public void loadData(final boolean refresh, ActionListener<Boolean> listener) { if (refresh) { currentPage = 0; } currentPage++; RequestParams params = new RequestParams(); params.put("page", currentPage); Request request = new Request(url, params); request.request(new RequestCallback(){ @Override public void onSuccess(JSONObject data) { if (refresh) { mDataList.clear(); } totalPage = response.optInt("total_page"); // 返回數據添加到 mDataList ...... if (listener != null) { boolean hasMore = currentPage <= totalPage listener.onSuccess(hasMore); } } @Override public void onFailure(String message, Throwable e) { if (listener != null) { listener.onFailure(message, e); } } }); } public List<Data> getDataList() { return mDataList; } }
Activity/Fragment初始化DataManager以後,只須要將數據源綁定到Adapter,loadData設置的回調告訴上層還有沒有更多數據,UI層調用adapter.notifyDataSetChanged( );至於數據從哪來,分頁邏輯,根本不須要UI層管理。UI層只須要經過loadData(refresh),告訴DataManager是否須要從新加載分頁,與下拉刷新的邏輯完美契合。
固然,在此基礎上實現數據庫緩存讀寫,也毫無壓力。DataManager也很容易實現對某一數據的多個接口的統一管理,經過單例模式或者其餘管理方法,將數據配發給多個頁面。
其實到了這一步,已經能知足大多數幾萬行代碼規模中小App的框架需求了,並且分層架構統一處理數據以及代碼複用度高的特色,使得項目中按照框架思路實現業務成爲最快速可靠的開發方法。我認爲一個優秀的框架,很重要的特性就是方便業務開發而不是給開發找麻煩,好比在分層設計事後,就算開發時間再緊張,依託分層框架依然是最快最保險的開發方法,假如某個接口直接在UI中寫了,就意味着數據管理層提供的一切便利都沒法直接使用,並且假如其餘UI用到這個接口,還得再複製粘貼一遍改來改去,相反,依託框架,網絡調用只實現一遍,上層便可重複使用這一業務接口(比較典型的:關注、收藏等),即使如此,項目規模進一步往上以後,DataManager,Activity/Fragment的壓力仍然會增大,更高的測試需求,要求進一步分離Activity/Fragment的代碼。這時候就能夠看看MVP和MVVM了。
MVC的C是即持有具體Model,又持有具體View,因此C很臃腫,分層架構就算抽出了DataManager,實質上仍然是一個MVC架構,而MVP和MVVM則是C持有具體View這個問題作了點文章,其中MVP就是將大量的View <-> Model 交互剝離出來交由Presenter,Presenter持有抽象的View。
在去年寫這個回答的時候,我曾經寫過這麼一段:看上去很美好,可是網上不少博客的那種Demo寫法我在嘗試應用中發現並不實用,就是抽象出不少View接口,而後創建Presenter類來做爲Presenter,這樣作寫些簡單的列表獲取,登陸之類看起來很漂亮,好像作到了代碼分離,可是業務場景一複雜就有點蛋疼
那個時候我還僅僅只是嘗試,不實用是一個很感性的認識,也沒有多說,那時候是在作一個商城應用,使用MVP編寫諸如購物車之類複雜場景的時候遇到了很大的困難,以致於讓我懷疑我是否是在用MVP給本身找麻煩,寫登陸這些還好,寫到購物車的時候我就開始懷疑人生了。一個ICartView,我要寫多少接口?購物車查刪改、優惠券滿減查、湊單、價格計算、運費……二十個接口少不了吧?那麼這個抽象的View除了給CartActivity用,還有其它什麼卵用嗎?假如我寫成ICartView,IBonusView,IXXXView……但是有的界面並不須要刪改購物車列表啊,難道我還要再細分?而後讓Activity實現一堆接口?搞成這個樣子,假如哪天需求變了怎麼辦……Presenter聽起來很吊,主導者啊,可是沒有Activity和Fragment的資源啊,我要怎麼才能讓它主導?須要獲取系統的一些信息(須要Context)的時候怎麼辦?不持有Context難道再開接口嗎?寫這麼多接口,接口實現,Presenter,多寫了幾百行代碼n個類,就爲了把1~200行代碼從Activity移出去?仍是放棄吧……
後來Google出了TODO-MVP,可是發現跟上面那種Demo寫法同樣很麻煩,我也沒有實際運用。後來反編譯了某個大型App,發現其正好是MVP架構,因而仔細看了一下代碼,就如同我最開始的想法,一個IXXXView有多少功能就寫多少接口。再看看Presenter的實現,我突然就明白我爲何會感受不實用了:
任何想要構建一個其餘什麼東西取代Activity/Fragment地位的嘗試都是自找麻煩
MVP正是一個典型。既然MVP把Activity/Fragment抽象爲View,那麼就意味着當它做爲一個抽象View去使用的時候,生命週期,Context這些極其重要的資源Presenter是看不到的,可是這些東西是不可能不使用的。爲了能讓Presenter使用到這些,Presenter就必須持有Context,綁定Activity、Fragment的生命週期,就算如此,在一些須要肯定使用Activity、Fragment的場合,仍須要使用強制轉型。正由於Presenter這個「主導」,致使Presenter和Activity/Fragment高度綁定,Presenter和IXXXView,沒有什麼複用性。這是我對目前Android MVP的一點見解,若是有小夥伴有比較好的實踐經驗,能夠在評論告訴我。
在我研究MVP的時間點,MVVM也是一個很火的概念,基於data-binding框架的demo也不少,可是我看過以後馬上否決了這個方案,大部分應用在從接口獲取數據後都會進行數據變換,哪怕拿到一個圖片URL都會在Java層添加後綴獲取縮略圖,有的要根據數據源控制View大小,顯隱,XML能作的事情太少了,若是將Model綁定到XML,大規模應用將會面臨多少坑……
MVVM相比於MVP,最重要的一個概念就是「數據綁定」!Presenter還持有抽象的View,ViewModel連這個都不須要,View經過ViewModel訂閱其所需的數據源,ViewModel向View提供改變數據的接口,當View的操做引發數據改變或者數據源發生改變時,ViewModel經過訂閱告知View,View進行視圖更新。這就是MVVM吸引人的地方,ViewModel只提供數據訂閱和數據接口,作到了與UI分離,ViewModel體量比Presenter小,複用性要比Presenter強太多,並且基於分層架構能夠作到小幅修改就能實現。惟一的痛點在於:如何實現數據綁定?
以前提到的data-binding,並非那麼如意,而此次Google I/O 2017放出的android-architecture-components則很好的解決了這個問題。
ViewModel的引入可以很好應對Activity銷燬重建時大規模數據的恢復問題,以及多個界面依賴一個接口返回數據的場景,在這兩個組件的規範下實現MVVM架構會十分容易,並且十分有意義。
因爲我已經在項目中大規模使用了RxJava,所以數據綁定我是採用RxJava方案實現的。
關於使用 android-architecture-components 組件實現MVVM的方案能夠參考:
googlesamples/android-architecture-components
關於 新型MVVM結構的思路,推薦這三篇文章
Android官方架構組件指南
Android官方架構組件介紹之ViewModel
Android官方架構組件介紹之LiveData
這兩年來這兩個概念很火,但須要注意的一點是,這兩個概念和上面的東西並非一個層級的,組件化和插件化是比上面說的那一堆亂七八糟更上層的東西,是針對整個大工程下的若干小模塊來講的,而這些小模塊怎樣搭建,則仍是上面那些內容:)
通常來講咱們作App,好比小外包,實際上是用不到MVP,MVVM這樣的架構的,一個分層架構就足以讓咱們快速高效的開發出App,選用什麼框架,不只要看你的應用類型,也要看你的應用規模,在分層架構的基礎上,只要接口實現的足夠好,代碼夠規範,切換到MVVM這樣的架構也不是什麼很難的事情。
若是你有現成成熟的框架那無需多言,但若是你的應用只有幾千行代碼,爲了追求MVVM,寫了十幾個類,踩了若干坑,只爲了把一個Activity中的幾十行代碼抽到ViewModel裏面,豈不是南轅北轍?
最後分享一個我本身的代碼庫和基礎框架工程,有沒集成RxJava的基礎分支、集成了RxJava的分層框架分支,還有一個使用android-arch-components的mvvm-rx分支,目前網絡調用這塊還不怎麼完善,後面會逐步完善演示示例,但願能幫助到你們。
最後鳴謝:知乎 0x8421bcd