自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何構(gòu)建Android MVVM應(yīng)用程序

移動開發(fā) Android
Databinding 是一種框架,MVVM是一種模式,兩者的概念是不一樣的。我的理解DataBinding是一個實現(xiàn)數(shù)據(jù)和UI綁定的框架,只是一個實現(xiàn)MVVM模式的工具。ViewModel和View可以通過DataBinding來實現(xiàn)單向綁定和雙向綁定,這套UI和數(shù)據(jù)之間的動態(tài)監(jiān)聽和動態(tài)更新的框架Google已經(jīng)幫我們做好了。在MVVM模式中ViewModel和View是用綁定關(guān)系來實現(xiàn)的,所以有了DataBinding 使我們構(gòu)建Android MVVM 應(yīng)用程序成為可能。

1、概述

Databinding 是一種框架,MVVM是一種模式,兩者的概念是不一樣的。我的理解DataBinding是一個實現(xiàn)數(shù)據(jù)和UI綁定的框架,只是一個實現(xiàn)MVVM模式的工具。ViewModel和View可以通過DataBinding來實現(xiàn)單向綁定和雙向綁定,這套UI和數(shù)據(jù)之間的動態(tài)監(jiān)聽和動態(tài)更新的框架Google已經(jīng)幫我們做好了。在MVVM模式中ViewModel和View是用綁定關(guān)系來實現(xiàn)的,所以有了DataBinding 使我們構(gòu)建Android MVVM 應(yīng)用程序成為可能。

之前看了很多關(guān)于DataBinding的博客和相關(guān)的一些Demo,大多數(shù)就是往xml布局文件傳入一些數(shù)據(jù),然后把這些數(shù)據(jù)綁定到控件上( 如TextView binding:text=“@{user.name} ),接著在這些控件上(如Button binding:setOnClickListener=”@{user.listener}”) 設(shè)置一些事件到控件上,基本講述都是DataBinding的基本用法。但是并沒有人告訴你把一個onClickListener 寫到一個類并把這個listener綁定到xml里面上是不是不太好,也沒有人告訴你這個和xml布局綁定的ViewModel類應(yīng)該放哪些數(shù)據(jù),應(yīng)該做什么事?應(yīng)該如何設(shè)計?更是很少有博文來告訴你在Android 中如何通過Data Binding 去構(gòu)建MVVM 的應(yīng)用框架。這也就是是本篇文章的重點。接下來,我們先來看看什么是MVVM,然后在一步一步來設(shè)計整個應(yīng)用程序框架。

源碼地址 https://github.com/Kelin-Hong/MVVMLight

2、MVC、MVP、MVVM

首先,我們先大致了解Android開發(fā)中常見的模式,以便我們更深入了解MVVM 模式。

  • MVC

View:對應(yīng)于xml布局文件

Model:實體模型

Controllor:對應(yīng)于Activity業(yè)務(wù)邏輯,數(shù)據(jù)處理和UI處理

從上面看起來各個組件的職責視乎還挺耦合MVC的,但是打開Android的一個Activity文件,一看一言難盡, Android中經(jīng)常會出現(xiàn)數(shù)千行的Activity代碼,究其原因,Android中純粹作為View的各個XML視圖功能太弱,Activity基本上都是View和Controller的合體,既要負責視圖的顯示又要加入控制邏輯,承擔的功能過多,代碼量大也就不足為奇。所有更貼切的目前常規(guī)的開發(fā)說應(yīng)該是View-Model 模式,大部分都是通過Activity的協(xié)調(diào),連接,和處理邏輯的。

  • MVP

View: 對應(yīng)于Activity和xml,負責View的繪制以及與用戶交互

Model: 依然是實體模型

Presenter: 負責完成View于Model間的交互和業(yè)務(wù)邏輯

在Android開發(fā)中MVP的設(shè)計思想用得比較多,利用MVP的設(shè)計模型可以把部分的邏輯的代碼從Fragment和Activity業(yè)務(wù)的邏輯移出來,在Presenter中持有View(Activity或者Fragment)的引用,然后在Presenter調(diào)用View暴露的接口對視圖進行操作,這樣有利于把視圖操作和業(yè)務(wù)邏輯分開來。MVP能夠讓Activity成為真正的View而不是View和Control的合體,Activity只做UI相關(guān)的事。但是這個模式還是存在一些不好的地方,比較如說:

  • Activity需要實現(xiàn)各種跟UI相關(guān)的接口,同時要在Activity中編寫大量的事件,然后在事件處理中調(diào)用presenter的業(yè)務(wù)處理方法,View和Presenter只是互相持有引用并互相做回調(diào),代碼不美觀。
  • 這種模式中,程序的主角是UI,通過UI事件的觸發(fā)對數(shù)據(jù)進行處理,更新UI就有考慮線程的問題。而且UI改變后牽扯的邏輯耦合度太高,一旦控件更改(比較TextView 替換 EditText等)牽扯的更新UI的接口就必須得換。

復(fù)雜的業(yè)務(wù)同時會導(dǎo)致presenter層太大,代碼臃腫的問題。

  • MVVM

View: 對應(yīng)于Activity和xml,負責View的繪制以及與用戶交互

Model: 實體模型

ViewModel: 負責完成View于Model間的交互,負責業(yè)務(wù)邏輯

MVVM的目標和思想MVP類似,利用數(shù)據(jù)綁定(Data Binding)、依賴屬性(Dependency Property)、命令(Command)、路由事件(Routed Event)等新特性,打造了一個更加靈活高效的架構(gòu)。

  • 數(shù)據(jù)驅(qū)動

在MVVM中,以前開發(fā)模式中必須先處理業(yè)務(wù)數(shù)據(jù),然后根據(jù)的數(shù)據(jù)變化,去獲取UI的引用然后更新UI,通過也是通過UI來獲取用戶輸入,而在MVVM中,數(shù)據(jù)和業(yè)務(wù)邏輯處于一個獨立的View Model中,ViewModel只要關(guān)注數(shù)據(jù)和業(yè)務(wù)邏輯,不需要和UI或者控件打交道。由數(shù)據(jù)自動去驅(qū)動UI去自動更新UI,UI的改變又同時自動反饋到數(shù)據(jù),數(shù)據(jù)成為主導(dǎo)因素,這樣使得在業(yè)務(wù)邏輯處理只要關(guān)心數(shù)據(jù),方便而且簡單很多。

  • 低耦合度

MVVM模式中,數(shù)據(jù)是獨立于UI的,ViewModel只負責處理和提供數(shù)據(jù),UI想怎么處理數(shù)據(jù)都由UI自己決定,ViewModel 不涉及任何和UI相關(guān)的事也不持有UI控件的引用,即使控件改變(TextView 換成 EditText)ViewModel 幾乎不需要更改任何代碼,專注自己的數(shù)據(jù)處理就可以了,如果是MVP遇到UI更改,就可能需要改變獲取UI的方式,改變更新UI的接口,改變從UI上獲取輸入的代碼,可能還需要更改訪問UI對象的屬性代碼等等。

  • 更新 UI

在MVVM中,我們可以在工作線程中直接修改View Model的數(shù)據(jù)(只要數(shù)據(jù)是線程安全的),剩下的數(shù)據(jù)綁定框架幫你搞定,很多事情都不需要你去關(guān)心。

  • 團隊協(xié)作

MVVM的分工是非常明顯的,由于View和View Model之間是松散耦合的。一個是處理業(yè)務(wù)和數(shù)據(jù),一個是專門的UI處理。完全有兩個人分工來做,一個做UI(xml 和 Activity)一個寫ViewModel,效率更高。

  • 可復(fù)用性

一個View Model復(fù)用到多個View中,同樣的一份數(shù)據(jù),用不同的UI去做展示,對于版本迭代頻繁的UI改動,只要更換View層就行,對于如果想在UI上的做AbTest 更是方便的多。

  • 單元測試

View Model里面是數(shù)據(jù)和業(yè)務(wù)邏輯,View中關(guān)注的是UI,這樣的做測試是很方便的,完全沒有彼此的依賴,不管是UI的單元測試還是業(yè)務(wù)邏輯的單元測試,都是低耦合的。

通過上面對MVVM的簡述和其他兩種模式的對比,我們發(fā)現(xiàn)MVVM對比MVC和MVP來說還是存在比較大的優(yōu)勢,雖然目前Android開發(fā)中可能真正在使用MVVM的很少,但是是值得我們?nèi)プ鲆恍┨接懞驼{(diào)研。

3、如何構(gòu)建MVVM應(yīng)用程序

1). 如何分工

構(gòu)建MVVM框架首先要具體了解各個模塊的分工,接下來我們來講解View,ViewModel,Model 的它們各自的職責所在。

  • View

View層做的就是和UI相關(guān)的工作,我們只在XML和Activity或Fragment寫View層的代碼,View層不做和業(yè)務(wù)相關(guān)的事,也就是我們的Activity 不寫和業(yè)務(wù)邏輯相關(guān)代碼,也不寫需要根據(jù)業(yè)務(wù)邏輯來更新UI的代碼,因為更新UI通過Binding實現(xiàn),更新UI在ViewModel里面做(更新綁定的數(shù)據(jù)源即可),Activity 要做的事就是初始化一些控件(如控件的顏色,添加 RecyclerView 的分割線),Activity可以更新UI,但是更新的UI必須和業(yè)務(wù)邏輯和數(shù)據(jù)是沒有關(guān)系的,只是單純的根據(jù)點擊或者滑動等事件更新UI(如 根據(jù)滑動顏色漸變、根據(jù)點擊隱藏等單純UI邏輯),Activity(View層)是可以處理UI事件,但是處理的只是處理UI自己的事情,View層只處理View層的事。簡單的說:View層不做任何業(yè)務(wù)邏輯、不涉及操作數(shù)據(jù)、不處理數(shù)據(jù)、UI和數(shù)據(jù)嚴格的分開。

  • ViewModel

ViewModel層做的事情剛好和View層相反,ViewModel 只做和業(yè)務(wù)邏輯和業(yè)務(wù)數(shù)據(jù)相關(guān)的事,不做任何和UI、控件相關(guān)的事,ViewModel 層不會持有任何控件的引用,更不會在ViewModel中通過UI控件的引用去做更新UI的事情。ViewModel就是專注于業(yè)務(wù)的邏輯處理,操作的也都是對數(shù)據(jù)進行操作,這些個數(shù)據(jù)源綁定在相應(yīng)的控件上會自動去更改UI,開發(fā)者不需要關(guān)心更新UI的事情。DataBinding 框架已經(jīng)支持雙向綁定,這使得我們在可以通過雙向綁定獲取View層反饋給ViewModel層的數(shù)據(jù),并進行操作。關(guān)于對UI控件事件的處理,我們也希望能把這些事件處理綁定到控件上,并把這些事件統(tǒng)一化,方便ViewModel對事件的處理和代碼的美觀。為此我們通過BindingAdapter 對一些常用的事件做了封裝,把一個個事件封裝成一個個Command,對于每個事件我們用一個ReplyCommand<T>去處理就行了,ReplyCommand<T>會把可能你需要的數(shù)據(jù)帶給你,這使得我們處理事件的時候也只關(guān)心處理數(shù)據(jù)就行了,具體見MVVM Light Toolkit 使用指南的 Command 部分。再強調(diào)一遍ViewModel 不做和UI相關(guān)的事。

  • Model

Model 的職責很簡單,基本就是實體模型(Bean)同時包括Retrofit 的Service ,ViewModel 可以根據(jù)Model 獲取一個Bean的Observable<Bean>( RxJava ),然后做一些數(shù)據(jù)轉(zhuǎn)換操作和映射到ViewModel 中的一些字段,最后把這些字段綁定到View層上。

2). 如何協(xié)作

關(guān)于協(xié)作,我們先來看下面的一張圖: 

 

 

 

圖 1

上圖反應(yīng)了MVVM框架中各個模塊的聯(lián)系和數(shù)據(jù)流的走向,由上圖可知View和Model 直接是解耦的,是沒有直接聯(lián)系的,也就是我之前說到的View 不做任何和業(yè)務(wù)邏輯和數(shù)據(jù)處理相關(guān)的事。我們從每個模塊一一拆分來看。那么我們重點就是下面的三個協(xié)作。

  • ViewModel與View的協(xié)作
  • ViewModel與Model的協(xié)作
  • ViewModel與ViewModel的協(xié)作

ViewModel與View的協(xié)作 

 

 

 

圖 2

圖 2 中ViewModel 和View 是通過綁定的方式連接在一起的,綁定的一種是數(shù)據(jù)綁定,一種是命令綁定。數(shù)據(jù)的綁定 DataBinding 已經(jīng)提供好了,簡單的定義一些ObservableField就能把數(shù)據(jù)和控件綁定在一起了(如TextView的text屬性),但是DataBinding框架提供的不夠全面,比如說如何讓一個URL綁定到一個ImageView讓這個ImageView能自動去加載url指定的圖片,如何把數(shù)據(jù)源和布局模板綁定到一個ListView,讓ListView可以不需要去寫Adapter和ViewHolder 相關(guān)的東西,而只是通過簡單的綁定的方式把ViewModel的數(shù)據(jù)源綁定到Xml的控件里面就能快速的展示列表呢?這些就需要我們做一些工作和簡單的封裝。MVVM Light Toolkit 已經(jīng)幫我們做了一部分的工作,詳情可以查看MVVM Light Toolkit 使用指南。關(guān)于事件綁定也是一樣,MVVM Light Toolkit 做了簡單的封裝,對于每個事件我們用一個ReplyCommand<T>去處理就行了,ReplyCommand<T>會把可能你需要的數(shù)據(jù)帶給你,這使得我們處理事件的時候也只關(guān)心處理數(shù)據(jù)就行了。

由 圖 1 中ViewModel的模塊中我們可以看出ViewModel類下面一般包含下面5個部分:

  • Context (上下文)
  • Model (數(shù)據(jù)模型Bean)
  • Data Field (數(shù)據(jù)綁定)
  • Command (命令綁定)
  • Child ViewModel (子ViewModel)

我們先來看下示例代碼,然后在一一講解5個部分是干嘛用的:

  1. //context 
  2.  
  3. private Activity context; 
  4.  
  5.   
  6.  
  7. //model(數(shù)據(jù)模型Bean) 
  8.  
  9. private NewsService.News news; 
  10.  
  11. private TopNewsService.News topNews; 
  12.  
  13.   
  14.  
  15. //數(shù)據(jù)綁定(data field) 
  16.  
  17. public final ObservableField<String> imageUrl = new ObservableField<>(); 
  18.  
  19. public final ObservableField<String> html = new ObservableField<>(); 
  20.  
  21. public final ObservableField<String> title = new ObservableField<>(); 
  22.  
  23. // 一個變量包含了所有關(guān)于View Style 相關(guān)的字段 
  24.  
  25. public final ViewStyle viewStyle = new ViewStyle(); 
  26.  
  27.   
  28.  
  29.   
  30.  
  31. //命令綁定(command) 
  32.  
  33. public final ReplyCommand onRefreshCommand = new ReplyCommand<>(() -> {     
  34.  
  35.   
  36.  
  37. }) 
  38.  
  39. public final ReplyCommand<Integer> onLoadMoreCommand = new ReplyCommand<>((p) -> { 
  40.  
  41.   
  42.  
  43. }); 
  44.  
  45.   
  46.  
  47.   
  48.  
  49. //Child ViewModel 
  50.  
  51. public final ObservableList<NewItemViewModel> itemViewModel = new ObservableArrayList<>(); 
  52.  
  53.   
  54.  
  55. /** * ViewStyle 關(guān)于控件的一些屬性和業(yè)務(wù)數(shù)據(jù)無關(guān)的Style 可以做一個包裹,這樣代碼比較美觀, 
  56.  
  57. ViewModel 頁面也不會有太多的字段。 **/ 
  58.  
  59. public static class ViewStyle {     
  60.  
  61.    public final ObservableBoolean isRefreshing = new ObservableBoolean(true);     
  62.  
  63.    public final ObservableBoolean progressRefreshing = new ObservableBoolean(true); 
  64.  
  65.  

Context

Context 是干嘛用的呢,為什么每個ViewModel都最好需要持了一個Context的引用呢?ViewModel 不做和UI相關(guān)的事,不操作控件,也不更新UI,那為什么要有Context呢?原因主要有以下兩點,當然也有其他用處,調(diào)用工具類、幫助類可能需要context參數(shù)等:

通過圖1中,我們發(fā)現(xiàn)ViewModel 通過傳參給Model 然后得到一個Observable<Bean>,其實這就是網(wǎng)絡(luò)請求部分,做網(wǎng)絡(luò)請求我們必須把Retrofit Service返回的Observable<Bean>綁定到Context的生命周期上,防止在請求回來時Activity已經(jīng)銷毀等異常,其實這個Context的目的就是把網(wǎng)絡(luò)請求綁定到當前頁面的生命周期中。

在圖1中,我們可以看到兩個ViewModel 之間的聯(lián)系是通過Messenger來做,這個Messenger 是需要用到Context,這個我們后續(xù)會講解。

Model

Model 是什么呢,其實就是數(shù)據(jù)原型,也就是我們用Json轉(zhuǎn)過來的Java Bean,我們可能都知道,ViewModel要把數(shù)據(jù)映射到View中可能需要大量對Model的數(shù)據(jù)拷貝,拿Model 的字段去生成對應(yīng)的ObservableField(我們不會直接拿Model的數(shù)據(jù)去做展示),這里其實是有必要在一個ViewModel 保留原始的Model引用,這對于我們是非常有用的,因為可能用戶的某些操作和輸入需要我們?nèi)ジ淖償?shù)據(jù)源,可能我們需要把一個Bean 從列表頁點擊后傳給詳情頁,可能我們需要把這個model 當做表單提交到服務(wù)器。這些都需要我們的ViewModel 持有相應(yīng)的model。

Data Field (數(shù)據(jù)綁定)

Data Field 就是需要綁定到控件上的ObservableField字段, 無可厚非這是ViewModel的必須品。這個沒有什么好說,但是這邊有一個建議:

這些字段是可以稍微做一下分類和包裹的,比如說可能一些字段綁定到控件的一些Style屬性上(如果說:長度,顏色,大小)這些根據(jù)業(yè)務(wù)邏輯的變化而動態(tài)去更改的,對于著一類針對View Style的的字段可以聲明一個ViewStyle類包裹起來,這樣整個代碼邏輯會更清晰一些,不然ViewModel里面可能字段泛濫,不易管理和閱讀性較差。而對于其他一些字段,比如說title,imageUrl,name這些屬于數(shù)據(jù)源類型的字段,這些字段也叫數(shù)據(jù)字段,是和業(yè)務(wù)邏輯息息相關(guān)的,這些字段可以放在一塊。

Command (命令綁定)

Command (命令綁定)說白了就是對事件的處理(下拉刷新,加載更多,點擊,滑動等事件處理),我們之前處理事件是拿到UI控件的引用,然后設(shè)置Listener,這些Listener 其實就是Command,但是考慮到在一個ViewModel 寫各種Listener 并不美觀,可能實現(xiàn)一個Listener就需要實現(xiàn)多個方法,但是我們可能只想要其中一個有用的方法實現(xiàn)就好了。同時實現(xiàn)Listener 會拿到UI的引用,可能會去做一些和UI相關(guān)的事情,這和我們之前說的ViewModel 不持有控件的引用,ViewModel不更改UI 有相悖。更重要一點是實現(xiàn)一個Listener 可能需要寫一些UI邏輯才能最終獲取我們想要的,簡單一點的比如說,你想要監(jiān)聽ListView滑到最底部然后觸發(fā)加載更多的事件,這時候你就要在ViewModel里面寫一個OnScrollListener,然后在里面的onScroll方法中做計算,計算什么時候ListView滑動底部了,其實ViewModel的工作并不想去處理這些事件,它專注做的應(yīng)該是業(yè)務(wù)邏輯和數(shù)據(jù)處理,如果有一個東西它不需要你自己去計算是否滑到底部,而是在滑動底部自動觸發(fā)一個Command,同時把當前列表的總共的item數(shù)量返回給你,方便你通過 page=itemCount/LIMIT+1去計算出應(yīng)該請求服務(wù)器哪一頁的數(shù)據(jù)那該多好啊。MVVM Light Toolkit 幫你實現(xiàn)了這一點:

  1. public final ReplyCommand<Integer> onLoadMoreCommand = new ReplyCommand<>((itemCount) -> { 
  2.  
  3. int page=itemCount/LIMIT+1; 
  4.  
  5. loadData(page.LIMIT) 
  6.  
  7. });  

接著在XML 布局文件中通過bind:onLoadMoreCommand綁定上去就行了

  1. <android.support.v7.widget.RecyclerView 
  2.  
  3. android:layout_width="match_parent" 
  4.  
  5. android:layout_height="match_parent" 
  6.  
  7. bind:onLoadMoreCommand="@{viewModel.loadMoreCommand}"/>  

具體想了解更多請查看 MVVM Light Toolkit 使用指南,里面有比較詳細的講解Command的使用。當然Command并不是必須的,你完全可以依照你的習慣和喜好在ViewModel 寫Listener,不過使用Command 可以使你的ViewModel 更簡潔易讀,你也可以自己定義更多的Command,自己定義其他功能Command,那么ViewModel的事件處理都是托管ReplyCommand<T>來處理,這樣的代碼看起來會特別美觀和清晰。

Child ViewModel (子ViewModel)

子ViewModel 的概念就是在ViewModel 里面嵌套其他的ViewModel,這種場景還是很常見的。比如說你一個Activity里面有兩個Fragment,ViewModel 是以業(yè)務(wù)劃分的,兩個Fragment做的業(yè)務(wù)不一樣,自然是由兩個ViewModel來處理,Activity 本身可能就有個ViewModel 來做它自己的業(yè)務(wù),這時候Activity的這個ViewModel里面可能包含了兩個Fragment分別的ViewModel。這就是嵌套的子ViewModel。還有另外一種就是對于AdapterView 如ListView RecyclerView,ViewPager等。

  1. //Child ViewModelpublic final 
  2.  
  3. ObservableList<ItemViewModel> itemViewModel = new ObservableArrayList<>();  

它們的每個Item 其實就對應(yīng)于一個ViewModel,然后在當前的ViewModel 通過ObservableList<ItemViewModel>持有引用(如上述代碼),這也是很常見的嵌套的子ViewModel。我們其實還建議,如果一個頁面業(yè)務(wù)非常復(fù)雜,不要把所有邏輯都寫在一個ViewModel,可以把頁面做業(yè)務(wù)劃分,把不同的業(yè)務(wù)放到不同的ViewModel,然后整合到一個總的ViewModel,這樣做起來可以使我們的代碼業(yè)務(wù)清晰,簡短意賅,也方便后人的維護。

總得來說ViewModel 和View 之前僅僅只有綁定的關(guān)系,View層需要的屬性和事件處理都是在xml里面綁定好了,ViewModel層不會去操作UI,只會操作數(shù)據(jù),ViewModel只是根據(jù)業(yè)務(wù)要求處理數(shù)據(jù),這些數(shù)據(jù)自動映射到View層控件的屬性上。關(guān)于ViewModel類中包含哪些模塊和字段,這個需要開發(fā)者自己去衡量,這邊建議ViewModel 不要引入太多的成員變量,成員變量最好只有上面的提到的5種(context、model、…),能不進入其他類型的變量就盡量不要引進來,太多的成員變量對于整個代碼結(jié)構(gòu)破壞很大,后面維護的人要時刻關(guān)心成員變量什么時候被初始化,什么時候被清掉,什么時候被賦值或者改變,一個細節(jié)不小心可能就出現(xiàn)潛在的Bug。太多不清晰定義的成員變量又沒有注釋的代碼是很難維護的。

ViewModel與Model的協(xié)作

從圖1 中,Model 是通過Retrofit 去獲取網(wǎng)絡(luò)數(shù)據(jù)的,返回的數(shù)據(jù)是一個Observable<Bean>( RxJava ),Model 層其實做的就是這些。那么ViewModel 做的就是通過傳參數(shù)到Model層獲取到網(wǎng)絡(luò)數(shù)據(jù)(數(shù)據(jù)庫同理)然后把Model的部分數(shù)據(jù)映射到ViewModel的一些字段(ObservableField),并在ViewModel 保留這個Model的引用,我們來看下這一塊的大致代碼(代碼涉及到簡單RxJava,如看不懂可以查閱入門一下):

  1. //Model 
  2.  
  3. private NewsDetail newsDetail; 
  4.  
  5. private void loadData(long id) { 
  6.  
  7. // Observable<Bean> 用來獲取網(wǎng)絡(luò)數(shù)據(jù) 
  8.  
  9. Observable<Notification<NewsDetailService.NewsDetail>> newsDetailOb = 
  10.  
  11. RetrofitProvider.getInstance() 
  12.  
  13. .create(NewsDetailService.class) 
  14.  
  15. .getNewsDetail(id) 
  16.  
  17. .subscribeOn(Schedulers.io()) 
  18.  
  19. .observeOn(AndroidSchedulers.mainThread()) 
  20.  
  21. // 將網(wǎng)絡(luò)請求綁定到Activity 的生命周期 
  22.  
  23. .compose(((ActivityLifecycleProvider) context).bindToLifecycle()) 
  24.  
  25. //變成 Notification<Bean> 使我們更方便處理數(shù)據(jù)和錯誤 
  26.  
  27. .materialize().share(); 
  28.  
  29. // 處理返回的數(shù)據(jù) 
  30.  
  31. newsDetailOb.filter(Notification::isOnNext) 
  32.  
  33. .map(n -> n.getValue()) 
  34.  
  35. // 給成員變量newsDetail 賦值,之前提到的5種變量類型中的一種(model類型) 
  36.  
  37. .doOnNext(m -> newsDetail = m) 
  38.  
  39. .subscribe(m -> initViewModelField(m)); 
  40.  
  41. // 網(wǎng)絡(luò)請求錯誤處理 
  42.  
  43. NewsListHelper.dealWithResponseError( 
  44.  
  45. newsDetailOb.filter(Notification::isOnError) 
  46.  
  47. .map(n -> n.getThrowable())); 
  48.  
  49.  
  50. //Model -->ViewModel 
  51.  
  52. private void initViewModelField(NewsDetail newsDetail) { 
  53.  
  54. viewStyle.isRefreshing.set(false); 
  55.  
  56. imageUrl.set(newsDetail.getImage()); 
  57.  
  58. Observable.just(newsDetail.getBody()) 
  59.  
  60. .map(s -> s + "<style type=\"text/css\">" + newsDetail.getCssStr()) 
  61.  
  62. .map(s -> s + "</style>"
  63.  
  64. .subscribe(s -> html.set(s)); 
  65.  
  66. title.set(newsDetail.getTitle()); 
  67.  
  68.  

以上代碼基本把注釋補全了,基本思路比較清晰,,Rxjava涉及的操作符都是比較基本的,如有不懂,可以稍微去入門,之后的源碼里面ViewModel數(shù)據(jù)邏輯處理都是用Rxjava做,所以需要提前學習一下方便你看懂源碼。

注:我們推薦使用MVVM 和 RxJava一塊使用,雖然兩者皆有觀察者模式的概念,但是我們RxJava不使用在針對View的監(jiān)聽,更多是業(yè)務(wù)數(shù)據(jù)流的轉(zhuǎn)換和處理。DataBinding框架其實是專用于View-ViewModel的動態(tài)綁定的,它使得我們的ViewModel 只需要關(guān)注數(shù)據(jù),而RxJava 提供的強大數(shù)據(jù)流轉(zhuǎn)換函數(shù)剛好可以用來處理ViewModel中的種種數(shù)據(jù),得到很好的用武之地,同時加上Lambda表達式結(jié)合的鏈式編程,使ViewModel 的代碼非常簡潔同時易讀易懂。

ViewModel與ViewModel的協(xié)作

在圖 1 中 我們看到兩個ViewModel 之間用一條虛線連接著,中間寫著Messenger,Messenger 可以理解是一個全局消息通道,引入messenger最主要的目的就實現(xiàn)ViewModel和ViewModel的通信,也可以用做View和ViewModel的通信,但是并不推薦這樣做。ViewModel主要是用來處理業(yè)務(wù)和數(shù)據(jù)的,每個ViewModel都有相應(yīng)的業(yè)務(wù)職責,但是在業(yè)務(wù)復(fù)雜的情況下,可能存在交叉業(yè)務(wù),這時候就需要ViewModel和ViewModel交換數(shù)據(jù)和通信,這時候一個全局的消息通道就很重要的。關(guān)于Messenger 的詳細使用方法可以參照 MVVM Light Toolkit 使用指南的 Messenger 部分,這邊給出一個簡單的例子僅供參考:

場景是這樣的,你的MainActivity對應(yīng)一個MainViewModel,MainActivity 里面除了自己的內(nèi)容還包含一個Fragment,這個Fragment 的業(yè)務(wù)處理對應(yīng)于一個FragmentViewModel,FragmentViewModel請求服務(wù)器并獲取數(shù)據(jù),剛好這個數(shù)據(jù)MainViewModel也需要用到,我們不可能在MainViewModel重新請求數(shù)據(jù),這樣不太合理,這時候就需要把數(shù)據(jù)傳給MainViewModel,那么應(yīng)該怎么傳,彼此沒有引用或者回調(diào)。那么只能通過全局的消息通道Messenger。FragmentViewModel 獲取消息后通知MainViewModel 并把數(shù)據(jù)傳給它:

  1. combineRequestOb.filter(Notification::isOnNext) 
  2.  
  3. .map(n -> n.getValue()) 
  4.  
  5. .map(p -> p.first
  6.  
  7. .filter(m -> !m.getTop_stories().isEmpty()) 
  8.  
  9. .doOnNext(m ->Observable.just(NewsListHelper.isTomorrow(date)).filter(b -> b).subscribe(b -> itemViewModel.clear())) 
  10.  
  11. // 上面的代碼可以不看,就是獲取網(wǎng)絡(luò)數(shù)據(jù) ,通過send把數(shù)據(jù)傳過去 
  12.  
  13. .subscribe(m -> Messenger.getDefault().send(m, TOKEN_TOP_NEWS_FINISH)); 
  14.  
  15. MainViewModel 接收消息并處理: 
  16.  
  17. Messenger.getDefault().register(activity, NewsViewModel.TOKEN_TOP_NEWS_FINISH, TopNewsService.News.class,(news) -> { 
  18.  
  19. // to something.... 
  20.  
  21.  

在MainActivity onDestroy 取消注冊就行了(不然導(dǎo)致內(nèi)存泄露)

  1. @Override 
  2.  
  3. protected void onDestroy() { 
  4.  
  5. super.onDestroy(); 
  6.  
  7. Messenger.getDefault().unregister(this); 
  8.  
  9.  

當然上面的例子也只是簡單的說明下,Messenger可以用在很多場景,通知,廣播都可以,不一定要傳數(shù)據(jù),在一定條件下也可以用在View層和ViewModel 上的通信和廣播。運用范圍特別廣,需要開發(fā)者結(jié)合實際的業(yè)務(wù)中去做更深層次的挖掘。

4、總結(jié)和源碼

本篇博文講解主要是一些個人開發(fā)過程中總結(jié)的Android MVVM構(gòu)建思想,更多是理論上各個模塊如何分工,代碼如何設(shè)計,雖然現(xiàn)在業(yè)界使用Android MVVM模式開發(fā)還比較少,但是隨著DataBinding 1.0 的發(fā)布,相信在Android MVVM 這塊領(lǐng)域會更多的人來嘗試,剛好最近用MVVM開發(fā)了一段時間,有點心得,寫出來僅供參考。

文中講解的過程代碼比較少,代碼用到了自己開發(fā)的一個MVVM Light Toolkit 庫,而且還是RxJava + Lambda 的代碼,估計很多人看著都暈菜了,這邊會把源碼公布出來。如果你還沒有嘗試過用RxJava+Retrofit+DataBinding 構(gòu)建Android MVVM 應(yīng)用程序,那么你可以試著看一下這邊的源碼并且做一下嘗試,說不定你會喜歡上這樣的開發(fā)框架。

關(guān)于MVVM Light Toolkit 只是一個工具庫,主要目的是更快捷方便的構(gòu)建Android MVVM應(yīng)用程序,在里面添加了一些控件額外屬性和做了一些事件的封裝,同時引進了全局消息通道Messenger,用起來確實非常方便,你可以嘗試一下,當然還有不少地方?jīng)]有完善和優(yōu)化,后續(xù)也會不斷更新和優(yōu)化,如果不能達到你的業(yè)務(wù)需求時,你也可以自己添加自己需要的屬性和事件。如果想更深入了解MVVM Light Toolkit 請看我這篇博文 MVVM Light Toolkit 使用指南

源碼地址 https://github.com/Kelin-Hong/MVVMLight

library —> library是MVVM Light Toolkit 的源碼,源碼很簡單,感興趣的同學可以看看,沒什么多少的技術(shù)難度,可以根據(jù)自己的需求,添加更多的控件的屬性和事件綁定。

sample —> 本文涉及的代碼均處出于這個項目,sample 一個知乎日報的App的簡單實現(xiàn),代碼包含了一大部分 MVVM Light Toolkit 的使用場景,(Data、Command、Messenger均有涉及),同時sample嚴格按照博文闡述的MVVM的設(shè)計思想開發(fā)的,對理解本文有很大的幫助,歡迎clone下來看看。

Sample 截圖 

 

 

 

源碼涉及 RxJava+Retrofit+Lambda 如有不懂或沒接觸過,花點時間入門一下,用到都是比較簡單的東西。希望這篇博客在如何構(gòu)建Android MVVM應(yīng)用程序?qū)δ阌兴鶐椭?,如有任何疑問,可以給我留言,歡迎大家共同探討,如果對MVVM Light Toolkit 有任何問題,也可以反饋給我。

責任編輯:龐桂玉 來源: 安卓開發(fā)精選
相關(guān)推薦

2017-02-24 10:02:04

AndroidMVVM應(yīng)用框架

2010-02-04 10:17:38

Android應(yīng)用程序

2017-12-10 14:13:14

云服務(wù)云原生應(yīng)用程序

2020-03-30 10:49:36

物聯(lián)網(wǎng)應(yīng)用程序IOT

2014-02-19 15:38:42

2010-02-05 18:21:24

Android應(yīng)用程序

2017-02-27 09:36:01

AndroidMVVM架構(gòu)

2011-09-01 10:01:35

PhoneGap應(yīng)用程序GoodDay

2022-10-08 00:53:12

HTTP物聯(lián)網(wǎng)應(yīng)用程序

2012-04-25 22:56:10

Android

2011-02-22 14:42:52

AndroidPad

2023-09-21 08:00:00

ChatGPT編程工具

2021-10-29 16:18:14

Streamlit Python

2010-06-13 09:22:37

jQuery

2012-04-19 09:34:21

ibmdw

2019-05-27 13:50:35

多云架構(gòu)企業(yè)多云集成云計算

2018-12-28 14:10:57

開發(fā)工具 移動應(yīng)用

2017-11-23 15:09:16

2024-03-07 09:15:57

2011-06-07 09:36:41

BlackBerry 應(yīng)用程序
點贊
收藏

51CTO技術(shù)棧公眾號