在完成上一篇之后,斷斷續續的開始重構我的Android項目代碼,現在終于完成了。在重構期間又仔細閱讀了一些開源項目的源碼及文章,并詢問了一些大神思路,按照理解自己完成了MVP結構的重構,與google samples項目的大致一致,但沒有完全照搬。本文側重一些重構過程中思考的問題,,具體的代碼可以在Github查看,本文的源碼為branch1.1,重構前的是master,最好對比看看重構的區別。
對多重callback邏輯的思考
大量的文章都只介紹讀取一次網絡然后用一個監聽接口處理訪問狀態,這種是最常見的網絡訪問規則,如下圖所示:
但實際生產環境哪里僅僅是簡單的為列表獲取數據而已,要是完成業務邏輯需要多次網絡訪問呢?來看一次登錄過程,我將每一個步驟都截圖如下:
1.基于UMENG SDK訪問微信獲取token,成功執行下一步。
2.基于UMENG SDK獲取用戶資料(頭像、昵稱),成功則執行下一步。
3.將數據發送服務器進行登錄驗證,完成登錄業務。
說起來邏輯很簡單,但是從代碼實現角度就不夠優雅了。在OnSuccess中執行下一步動作,如果不寫在另一個方法函數中的話,那么看起來就是一層套一層的結構,可讀性很差,我考慮的有幾種技巧來盡量提高可讀性:
1.添加充足的注釋
2.用Handler(之前我是這么做的)
3.用EventBus
4.用RxJava
在這次重構中,我放棄了Handler的方式,如果注釋充足,Handler其實并沒有提高可讀性,而且從MVP架構的角度來思考,之前Handler的編寫并沒有將View邏輯與Presenter的邏輯分離,全部放在了Handler中,應該盡量避免。
EventBus在重構中也未使用,因為EventBus的訂閱模式更適合一個操作需要通知多個處理的情況(比如收到新消息),否則與監聽接口相比閱讀性。并未提升太大。
我傾向使用RxJava,雖然準備在下一次重構中才使用,但已經閱讀了不少的文章。在注釋充足的情況下,現在callback套callback的方式已經能夠閱讀,但不可忽略的線程安全和內存溢出問題,可以利用RxJava很好的解決(這也是放棄handler的一個原因,你真以為實際生產中new一個出來就可以不管了么..)感興趣的朋友可以等我的下一個branch,我會來說說我的感受。
MVP重構要一貫堅持到底么?
在google sample的todo例子中,要想從列表頁打開新建頁,需要點擊浮動按鈕,這樣一個簡單的在onClick中搞定的事情,被拆分為三部分,我認為實際有點過了。看看下面的代碼:
如果點擊消息列表的按鈕,需要首先清除通知badage,然后清除Preference的未讀數量,然后進行頁面跳轉。按照MVP架構思路,清除通知badage應該在View中實現(也就是在Activity里多一個方法);清除未讀數量應該在Model的Local中(也就是專門負責本地數據操作的類);跳轉應該在View中實現(Activity里面還要多寫一個方法)。
那么其實在OnClick中3行代碼搞定的時候,你要額外多謝那么多支撐框架的代碼,有必要么?再看看Model層的一個例子:
在google sample的todo例子中,網絡訪問與本地訪問分離,一條數據是從本地讀取還是從網絡讀取的邏輯判斷,是寫在Repository中的。如此毫無問題,思路非常清晰。但如果不存在本地化的需要,可以把代碼直接寫在Repository中么?(其實可以吧...反正也不影響閱讀)
todo例子中,網絡與本地的操作是保持一致的(基礎的增刪改查),用接口來定義了方法。在實際生產中(比如登錄過程),本地與網絡的操作差別比較大,因此應該根據實際判斷到底是否需要用接口定義方法。
todo例子中,用到了很多Ioc的思想,如果你不用DI庫如dagger2或者要做單元測試,可以考慮簡化一下,正如我實例化Presenter的代碼一樣。
重構之后,數據的成員變量該放哪去了?
詳情頁,需要一個描述商品數據的成員變量;其他用戶的介紹頁,需要一個描述用戶資料數據的成員變量;列表,需要一個數組來保存數據,一個int來保存現在是第幾頁了。那么MVP結構重構之后,這些變量是不應該仍然定義在Activity中的,那么這些數據應該放在MVP的那一層呢?
安裝todo例子中的介紹,包含2方面的重構。首先,數據變量應該寫在P層中;其次,對ListView這種包含adapter的控件,數組操作應該寫到Adapter中。
以我一個Presenter為例,由于不考慮注入,因此Repository就直接在構造器中自己實例化了。當前列表為第幾頁的mPage變量,是放在Presenter中了的,每次獲取數據后都直接將數據傳入View中,再在View中調用Adapter的自定義方法來更新列表(詳情見代碼)。
在Adapter中,自定義了remove、replace、addAll、getItem等幾種常用的數據操作函數,不夠再加就是了。自定義這些方法的時候,尤其要注意你是否自己實現了header-view的功能,如果實現了,那么就要計算一下實際的位置(其實特簡單,一句話的事)。
后話
其實本來想按照重構后MVP長什么樣的思路來寫這文章,但是在重構的過程中不斷的閱讀todo例子的代碼,覺得把握住幾個關鍵點,重構的思路就會非常清晰:
1.各種數據操作都要放到M層中去;
2.V層中只需要定義方法的時候把數據設為參數,不管數據從哪來,到哪去;
3.P層負責告訴M層要讀哪些數據(get)或者要向服務器發送什么數據(set),根據數據訪問情況(成功或失?。?,將數據傳給V層;
這一篇更多的是想把自己重構過程中的一些思考記下來和大家一起討論,本篇不是這次重構的終點,下一篇我會繼續聊如何從這一步繼續跨到RxJava函數式編程的范圍去。每一次重構是怎么變化的,代碼也給了,思路也給了,項目也是應用商店上線的產品(雖然沒有錢推廣用的人不多),這個系列應該比其他文章的玩具代碼參考價值更高吧。如有感興趣的大神指點一二,或者也在前行的朋友一起來討論下,那我也覺得深夜碼字沒有白費了。