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