編輯:關於Android編程
去年5月左右的時候,筆者在逛GitHub的時候,看到了一個MVP的項目,叫做mosby,仔細看了源碼和作者介紹的文章後,發現確實有點意思,雖然會需要多寫幾個類和方法,但是解決了activity/fragment過重的問題,通過V/P分離能夠幫助提高可維護性。時至去年年底,今年年初,MVP才逐漸被大家所知,也不時看到些文章介紹其概念和實踐。
再說說MVVM (Model-View-ViewModel),在Android上對應data binding。即ViewModel到View的映射,不需要再去自己找到view,然後更新字段,而是在映射建立後直接更新ViewModel然後反映到View上。
值得一提的是,MVP和MVVM都是微軟提出的理念,最早都是在WPF裡面被應用的,只是時至今日才在Android上被真正用起來。本文不是來介紹這兩個的,所以不再贅述,講講正題。
在本系列首篇中我曾經提到過在我設計的新應用中,采用了MVP+MVVM的混合(當初也考慮過Flux,但感覺並不合適Android),後來有一次CJJ同學和我探討這個架構的時候,問到了我有沒有什麼正式的名字,我就有楞,因為這個組合和應用是我自己設計的,所以還真沒想過這個問題,Google一搜,還真有這麼個東西(見參考資料,文章寫得很棒,建議英文不錯的同學讀一讀)!
這就是本文我要介紹的東西,MVPVM (Model-View-Presenter-ViewModel)。
以下所有Model,並不單單指的是Bean,而是Model層,更像是repository或者business logic。
MVC: View持有Controller,傳遞事件給Controller,Controller由此去觸發Model層事件,Model更新完數據(如從網絡或者數據庫獲得數據後)觸發View的更新事件。
乍一看,MVP似乎是MVC的變種,即C的位置被P取代了,但如果我們再看一看下圖:
其實MVP是MVC的一個wrap,C層仍然可以在那裡,代替View處理點擊事件、數據綁定、扮演ListView的觀察者,從而View可以專注於處理純視覺的一些東西。而Presenter則避免了Model直接去觸發View的更新,View徹底成為了一個被動的東西,只有Presenter告知其更新視覺,它才會去更新,比如showLoading(), showEmpty()。
MVVM通過View和ViewModel的雙向綁定,讓我們可以
- 直接更新ViewModel,View會進行對應刷新
- View的事件直接傳遞到ViewModel,ViewModel去對Model進行操作並接受更新。
如果你仔細讀過Clean architecture的源碼,會發現其中已經有了ViewModel這一層。如果你熟悉DO(Domain Object),PO(Persistent Object),VO(View Object),或許了解visibility這個概念,各層只需要知道其應該知道的。這些Object代表了完全獨立不同的概念。
ViewModel層的必要性,簡單舉個例子,服務器傳來一個Date String,但我們需要顯示的是該Date到現在的相對時間描述,比如1分鐘前,2天前,為了避免在view中綁定數據時去做這個邏輯,ViewModel會代替來進行這個的轉換。
MVVM盡管確實省去了綁定數據到View的boilerplate,但
- ViewModel引用了View,從而導致ViewModel無法重用於其他View。
- 並沒有解決View層過重的問題,僅僅去掉了數據綁定,尤其對一些復雜業務邏輯的頁面。
模式的引入都是為了通過可拔插化以提高可復用性,松耦合和盡量小的接口可以給予最大的可復用性,使得組件能重組使用。
所以有了MVPVM:
在我的個人實踐中:
- Model: data和domain模塊組成,包含了Interactor(UseCase)、Repository、Datastore、Retrofit、Realm、DO、部分PO等。
- View: Activity/Fragment。
- Presenter:Presenter,包含了Subscriber,並通過Dagger2注入UseCase從而減輕耦合。
- ViewModel:由Model轉換而成,繼承BaseObservable或SZ喎?/kf/ware/vc/" target="_blank" class="keylink">vcnRlZExpc3SjrLTzsr+31taxvdN3cmFwwcttb2RlbKOstNO2+MiltfTBy21hcHBlcrXEYm9pbGVycGxhdGWho82ouf1EYXRhIEJpbmRpbmew87aotb14bWyhozwvcD4NCjxwPrTTUHJlc2VudGVytcRTdWJzY3JpYmVyzfnPwra8ysdSeEphdmG1xMH3ysC956Osc3RyZWFtIGluIHN0cmVhbSBvdXSho8jnufvE49StwLS+zdOm08PBy01WULvy1d9DbGVhbiBBcmNoaXRlY3R1cmWjrMTHu+G3os/W1Nm808nPVmlld01vZGVsvPLWscyrvPK1pcHLo6zNrMqxyMO0+sLrv+K4/NCho6zC37ytuPzH5c76oaM8L3A+DQo8cD6909fFv7S/tLj3uPbX6bz+1NpNVlBWTdbQtcRzdGFuZGluZ6GjPC9wPg0KPGgyIGlkPQ=="mvpvm-model">MVPVM: Model
實際對應的是Repository層,即第一篇文章中提到的data/domain module。具體的Model理論上應該是PO,但我們大部分場景並不需要PO,所以也可以是domain層的DO。
View對ViewModel不需要了解太多,這樣才能保持兩者的解耦,兩者之間的協議只需要:
- ViewModel支持View需要展示的properties。
- View實現了ViewModel的觀察者模式接口(如Listener)。
所以這裡ViewModel到View是一條虛線,而不是MVVM中的雙向實線。
和在MVP一樣,Presenter站在View和Model層之間。這裡值得一提的是Presenter到ViewModel是有耦合的,因為Presenter需要把model更新到ViewModel中,也就是map行為,然後調用View的對應接口進行binding。
Presenter是MVPVM中唯一不需要解耦的,它緊緊地與View、ViewModel、Model層耦合。如果你的Presenter被多個View重用了,那你可能需要考慮它是不是更應該作為一個module,比如(第三方)登陸。
MVPVM讓ViewModel可以重用,因為它再也不是直接和特定View綁定,而僅僅作為數據到View的一個綁定用展示。ViewModel因為用戶操作而觸發的事件不再直接對Model進行操作,而由View去負責任務流。ViewModel本身基本沒有field,而是通過暴露get方法來讓data binding找到對應要顯示的property,get方法中直接調用持有的model的對應屬性get方法。
理想化的架構是通過一個mapper類進行轉換,但我想大部分的程序員面對這個工作都會抓狂,畢竟很多字段其實就是一個復制,而且對性能也有一些影響(遍歷list,new對象,一個個字段轉換,添加到新的list)。所以折中地,讓ViewModel持有Model,在get方法中直接返回對應model的具體字段,在一些特殊的field如相對時間、添加一些描述性字符的地方再去進行拼接和特殊處理。
啊,對了,說到ViewModel,Data Binding現在支持雙向綁定了哦,見https://halfthought.wordpress.com/2016/03/23/2-way-data-binding-on-android/,語法如:
不同於單向綁定的@{},使用了@={},畢竟雙向綁定這個東西還是慎用,一方面早成數據流混亂不好理解,另一方面容易出現死循環。
在MVP中,我們有時候碰到的問題是,Presenter真的有必要存在嗎,尤其是一些較為靜態,沒什麼業務邏輯,只需要純展示的頁面,結果就是為了MVP而特意去創建一個Presenter。
所以Presenter不應該被強求,正如MVP中,V和C其實被並在了一起,在某些情況下(確實就是個純展示,或者很少的業務邏輯),應該允許去Presenter,並讓View承擔其任務。比如注冊頁面,我真的就只是想把用戶的輸入發到服務器驗證一下,何必非得去搞一個presenter套著呢?
我們不能永遠理想化地去選擇所謂最好的設計,在現實的必要情況下,我們要敢於捨棄,最合適的設計才是最好的設計。為此,Presenter不是強制的;為此,ViewModel並不一定通過mapper生成,而可以返回持有的DO對象對應字段。
本篇講了講MVPVM及其在Android的實踐,因為時間原因來不及寫個demo來說說具體實現,歡迎大家提出意見和建議。有空的話我最近會在GitHub上寫一下demo,你如果有興趣可以follow一下等等更新: markzhai。
Dagger匕首,比ButterKnife黃油刀鋒利得多。Square為什麼這麼有自信地給它取了這個名字,Google又為什麼會拿去做了Dagger2呢?筆者看了很多國內Dagger2的文章,但發現它們都保留在介紹API和官網翻譯的層面,無法讓讀者能明白究竟為什麼用Dagger2,又如何用好Dagger2。希望能在下一次為大家講清楚。
手機短信收發有三種方式:Block方式、Text方式和PDU方式,前兩種在國內很少使用,PDU格式則普遍支持。內容總長度140個字節(1120位),支持采用
前一篇文章講述了Android拍照、截圖、保存並顯示在ImageView控件中,該篇文章繼續講述Android圖像處理技術,主要操作包括:通過打開相冊裡的圖片,使用Mat
小米手環怎麼設置來電震動?小米手環上市開始就受到廣大用戶的青睐,不僅功能使用,價格還很公道,下面小編為大家帶來小米手環來電震動設置方法。小米手環設置來電震動
在viewgroup執行: public void snapToScreen(int whichScreen) { whichScreen = Mat