android中MVC,MVP和MVVM三種模式詳解析


我們都知道,Android本身就采用了MVC模式,model層數據源層我們就不說了,至於view層即通過xml來體現,而 controller層的角色一般是由activity來擔當的。雖然我們項目用到了MVP模式,但是現在人們並沒有總結出一種規范,所以MVP模式的寫法並不統一,而至於MVVM模式看網上的呼聲似乎也是贊同和拍磚的參半,所以對於這幾種模式我也不發表意見了,適合自己的才是最好的。下面是我看到的關於這幾種模式的幾篇文章,整合了一下分享給大家。


----------------------------------------------------------------轉載內容------------------------------------------------------------

相信大家對MVC,MVP和MVVM都不陌生,作為三個最耳熟能詳的Android框架,它們的應用可以是非常廣泛的,但是對於一些新手來說,可能對於區分它們三個都有困難,更別說在實際的項目中應用了,有些時候想用MVP的,代碼寫着寫着就變成了MVC,久而久之就對它們三個的選擇產生了恐懼感,如果你也是這樣的人群,那么這篇文章可能會對你有很大的幫助,希望大家看完都會有收獲吧!


文章重點:


(1)了解並區分MVC,MVP,MVVM。


(2)知道這三種模式在Android中如何使用。


(3)走出data binding的誤區。


(4)了解MVP+data binding的開發模式。


本篇文章的demo我將會上傳到我的github上。


水之積也不厚,則其負大舟也無力


正如庄子在逍遙游中說的,如果水不夠深,那就沒有能夠擔負大船的力量 。所以在真正開始涉及具體的代碼之前,我們要先對MVC,MVP和MVVM做一個初步的了解。如果各位同學對此已經有所了解了,可以選擇性跳過這一節。


MVC


MVC,Model View Controller,是軟件架構中最常見的一種框架,簡單來說就是通過controller的控制去操作model層的數據,並且返回給view層展示,具體見下圖



當用戶出發事件的時候,view層會發送指令到controller層,接着controller去通知model層更新數據,model層更新完數據以后直接顯示在view層上,這就是MVC的工作原理。


那具體到Android上是怎么樣一個情況呢?


大家都知道一個Android工程有什么對吧,有Java的class文件,有res文件夾,里面是各種資源,還有類似manifest文件等等。對於原生的Android項目來說,layout.xml里面的xml文件就對應於MVC的view層,里面都是一些view的布局代碼,而各種java bean,還有一些類似repository類就對應於model層,至於controller層嘛,當然就是各種activity咯。大家可以試着套用我上面說的MVC的工作原理是理解。比如你的界面有一個按鈕,按下這個按鈕去網絡上下載一個文件,這個按鈕是view層的,是使用xml來寫的,而那些和網絡連接相關的代碼寫在其他類里,比如你可以寫一個專門的networkHelper類,這個就是model層,那怎么連接這兩層呢?是通過button.setOnClickListener()這個函數,這個函數就寫在了activity中,對應於controller層。是不是很清晰。


大家想過這樣會有什么問題嗎?顯然是有的,不然為什么會有MVP和MVVM的誕生呢,是吧。問題就在於xml作為view層,控制能力實在太弱了,你想去動態的改變一個頁面的背景,或者動態的隱藏/顯示一個按鈕,這些都沒辦法在xml中做,只能把代碼寫在activity中,造成了activity既是controller層,又是view層的這樣一個窘境。大家回想一下自己寫的代碼,如果是一個邏輯很復雜的頁面,activity或者fragment是不是動輒上千行呢?這樣不僅寫起來麻煩,維護起來更是噩夢。(當然看過Android源碼的同學其實會發現上千行的代碼不算啥,一個RecyclerView.class的代碼都快上萬行了呢。。)


MVC還有一個重要的缺陷,大家看上面那幅圖,view層和model層是相互可知的,這意味着兩層之間存在耦合,耦合對於一個大型程序來說是非常致命的,因為這表示開發,測試,維護都需要花大量的精力。


正因為MVC有這樣那樣的缺點,所以才演化出了MVP和MVVM這兩種框架。


MVP


MVP作為MVC的演化,解決了MVC不少的缺點,對於Android來說,MVP的model層相對於MVC是一樣的,而activity和fragment不再是controller層,而是純粹的view層,所有關於用戶事件的轉發全部交由presenter層處理。下面還是讓我們看圖



從圖中就可以看出,最明顯的差別就是view層和model層不再相互可知,完全的解耦,取而代之的presenter層充當了橋梁的作用,用於操作view層發出的事件傳遞到presenter層中,presenter層去操作model層,並且將數據返回給view層,整個過程中view層和model層完全沒有聯系。看到這里大家可能會問,雖然view層和model層解耦了,但是view層和presenter層不是耦合在一起了嗎?其實不是的,對於view層和presenter層的通信,我們是可以通過接口實現的,具體的意思就是說我們的activity,fragment可以去實現實現定義好的接口,而在對應的presenter中通過接口調用方法。不僅如此,我們還可以編寫測試用的View,模擬用戶的各種操作,從而實現對Presenter的測試。這就解決了MVC模式中測試,維護難的問題。


當然,其實最好的方式是使用fragment作為view層,而activity則是用於創建view層(fragment)和presenter層(presenter)的一個控制器。


MVVM


MVVM最早是由微軟提出的



這里要感謝泡在網上的日子,因為前面看到的三張圖我都是從它的博客中摘取的,如果有人知道不允許這樣做的話請告訴我,我會從我的博客中刪除的,謝謝。


從圖中看出,它和MVP的區別貌似不大,只不過是presenter層換成了viewmodel層,還有一點就是view層和viewmodel層是相互綁定的關系,這意味着當你更新viewmodel層的數據的時候,view層會相應的變動ui。


我們很難去說MVP和MVVM這兩個MVC的變種孰優孰劣,還是要具體情況具體分析。


紙上得來終覺淺,絕知此事要躬行


對於程序員來說,空談是最沒效率的一種方式,相信大家看了我上面對於三種模式的分析,或多或少都會有點雲里霧里,下面讓我們結合代碼來看看。


讓我們試想一下下面這個情景,用戶點擊一個按鈕A,獲取github上對應公司對應倉庫中貢獻排行第一的任的名字,然后我們還會有一個按鈕B,用戶點擊按鈕B,界面上排行第一的那個人的名字就會換成自己的。


MVC


MVC實現是最簡單的。


首先看對應view層的xml文件

[html] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. <?xmlversionxmlversion="1.0"encoding="utf-8"?>  
  2.   
  3. <LinearLayoutxmlns:androidLinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"  
  4.   
  5.     xmlns:tools="http://schemas.android.com/tools"  
  6.   
  7.     android:layout_width="match_parent"  
  8.   
  9.     android:layout_height="match_parent"  
  10.   
  11.     android:id="@+id/container"  
  12.   
  13.     android:orientation="vertical"  
  14.   
  15.     tools:context=".ui.view.MainActivity"  
  16.   
  17.     android:fitsSystemWindows="true">  
  18.   
  19.    
  20.   
  21.     <Button  
  22.   
  23.         android:text="get"  
  24.   
  25.         android:layout_width="match_parent"  
  26.   
  27.         android:layout_height="wrap_content"  
  28.   
  29.         android:onClick="get"/>  
  30.   
  31.    
  32.   
  33.     <Button  
  34.   
  35.         android:text="change"  
  36.   
  37.         android:layout_width="match_parent"  
  38.   
  39.         android:layout_height="wrap_content"  
  40.   
  41.         android:onClick="change"/>  
  42.   
  43.    
  44.   
  45.     <TextView  
  46.   
  47.         android:id="@+id/top_contributor"  
  48.   
  49.         android:layout_width="match_parent"  
  50.   
  51.         android:layout_height="match_parent"  
  52.   
  53.         android:gravity="center"  
  54.   
  55.         android:textSize="30sp"/>  
  56.   
  57.    
  58.   
  59. </LinearLayout>  


很簡單,兩個Button一個TextView


接着看對應controller層的activity

[java] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. public class MainActivity extends AppCompatActivity {  
  2.    
  3.     private ProcessDialog dialog;  
  4.     private Contributor contributor = new Contributor();  
  5.    
  6.     private TextView topContributor;  
  7.    
  8.     private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {  
  9.    
  10.         @Override  
  11.         public void onStart() {  
  12.             showProgress();  
  13.         }  
  14.    
  15.         @Override  
  16.         public void onCompleted() {  
  17.    
  18.         }  
  19.    
  20.         @Override  
  21.         public void onError(Throwable e) {  
  22.    
  23.         }  
  24.    
  25.         @Override  
  26.         public void onNext(Contributor contributor) {  
  27.             MainActivity.this.contributor = contributor;  
  28.    
  29.             topContributor.setText(contributor.login);  
  30.    
  31.             dismissProgress();  
  32.         }  
  33.     };  
  34.    
  35.     @Override  
  36.     protected void onCreate(Bundle savedInstanceState) {  
  37.         super.onCreate(savedInstanceState);  
  38.         setContentView(R.layout.activity_main);  
  39.         topContributor = (TextView)findViewById(R.id.top_contributor);  
  40.     }  
  41.    
  42.     public void get(View view){  
  43.         getTopContributor("square", "retrofit");  
  44.     }  
  45.    
  46.     public void change(View view){  
  47.         contributor.login = "zjutkz";  
  48.    
  49.         topContributor.setText(contributor.login);  
  50.     }  
  51.    
  52.     public void getTopContributor(String owner,String repo){  
  53.         GitHubApi.getContributors(owner, repo)  
  54.                 .take(1)  
  55.                 .observeOn(AndroidSchedulers.mainThread())  
  56.                 .subscribeOn(Schedulers.newThread())  
  57.                 .map(new Func1<List<Contributor>, Contributor>() {  
  58.    
  59.                     @Override  
  60.                     public Contributor call(List<Contributor> contributors) {  
  61.                         return contributors.get(0);  
  62.                     }  
  63.                 })  
  64.                 .subscribe(contributorSub);  
  65.     }  
  66.    
  67.     public void showProgress(){  
  68.         if(dialog == null){  
  69.             dialog = new ProcessDialog(this);  
  70.         }  
  71.    
  72.         dialog.showMessage("正在加載...");  
  73.     }  
  74.    
  75.    public void dismissProgress(){  
  76.         if(dialog == null){  
  77.             dialog = new ProcessDialog(this);  
  78.         }  
  79.    
  80.         dialog.dismiss();  
  81.     }  
  82. }  


我們看一下get()方法中調用的getTopContributor方法

[java] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. public void getTopContributor(String owner,String repo){  
  2.     GitHubApi.getContributors(owner, repo)  
  3.             .take(1)  
  4.             .observeOn(AndroidSchedulers.mainThread())  
  5.             .subscribeOn(Schedulers.newThread())  
  6.             .map(new Func1<List<Contributor>, Contributor>() {  
  7.    
  8.                 @Override  
  9.                 public Contributor call(List<Contributor> contributors) {  
  10.                     return contributors.get(0);  
  11.                 }  
  12.             })  
  13.             .subscribe(contributorSub);  
  14. }  




熟悉rxjava和retrofit的同學應該都明白這是啥意思,如果對這兩個開源庫不熟悉也沒事,可以參考給 Android 開發者的 RxJava 詳解和用 Retrofit 2 簡化 HTTP 請求這兩篇文章。


對於這里大家只要知道這段代碼的意思就是去獲取github上owner公司中的repo倉庫里貢獻排名第一的那個人。貢獻者是通過Contributor這個java bean存儲的。

[java] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. public class Contributor {  
  2.    
  3.     public String login;  
  4.     public int contributions;  
  5.    
  6.     
  7.   @Override  
  8.     public String toString() {  
  9.         return login + ", " + contributions;  
  10.     }  
  11. }  



很簡單,login表示貢獻者的名字,contributor表示貢獻的次數。


然后通過rxjava的subscriber中的onNext()函數得到這個數據。


[java] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {  
  2.    
  3.     @Override  
  4.     public void onStart() {  
  5.         showProgress();  
  6.     }  
  7.    
  8.     @Override  
  9.     public void onCompleted() {  
  10.    
  11.     }  
  12.    
  13.     @Override  
  14.     public void onError(Throwable e) {  
  15.    
  16.     }  
  17.    
  18.     @Override  
  19.     public void onNext(Contributor contributor) {  
  20.         MainActivity.this.contributor = contributor;  
  21.    
  22.         topContributor.setText(contributor.login);  
  23.    
  24.         dismissProgress();  
  25.     }  
  26. };  


至於另外那個change按鈕的工作大家應該都看得懂,這里不重復了。


好了,我們來回顧一遍整個流程。


首先在xml中寫好布局代碼。


其次,activity作為一個controller,里面的邏輯是監聽用戶點擊按鈕並作出相應的操作。比如針對get按鈕,做的工作就是調用GithubApi的方法去獲取數據。


GithubApi,Contributor等類則表示MVC中的model層,里面是數據和一些具體的邏輯操作。


說完了流程再來看看問題,還記得我們前面說的嗎,MVC在Android上的應用,一個具體的問題就是activity的責任過重,既是controller又是view。這里是怎么體現的呢?看了代碼大家發現其中有一個progressDialog,在加載數據的時候顯示,加載完了以后取消,邏輯其實是view層的邏輯,但是這個我們沒辦法寫到xml里面啊,包括TextView.setTextView(),這個也一樣。我們只能把這些邏輯寫到activity中,這就造成了activity的臃腫,這個例子可能還好,如果是一個復雜的頁面呢?大家自己想象一下。


MVP


通過具體的代碼大家知道了MVC在Android上是如何工作的,也知道了它的缺點,那MVP是如何修正的呢?


這里先向大家推薦github上的一個第三方庫,通過這個庫大家可以很輕松的實現MVP。好了,還是看代碼吧。


首先還是xml

[html] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     xmlns:tools="http://schemas.android.com/tools"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent"  
  6.     android:id="@+id/container"  
  7.     android:orientation="vertical"  
  8.     tools:context=".ui.view.MainActivity"  
  9.     android:fitsSystemWindows="true">  
  10.    
  11.     <Button  
  12.         android:text="get"  
  13.         android:layout_width="match_parent"  
  14.         android:layout_height="wrap_content"  
  15.         android:onClick="get"/>  
  16.    
  17.     <Button  
  18.         android:text="change"  
  19.         android:layout_width="match_parent"  
  20.         android:layout_height="wrap_content"  
  21.         android:onClick="change"/>  
  22.    
  23.     <TextView  
  24.         android:id="@+id/top_contributor"  
  25.         android:layout_width="match_parent"  
  26.         android:layout_height="match_parent"  
  27.         android:gravity="center"  
  28.         android:textSize="30sp"/>  
  29.    
  30. </LinearLayout>  



這個和MVC是一樣的,畢竟界面的形式是一樣的嘛。


接下去,我們看一個接口。


[java] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. public interface ContributorView extends MvpView {  
  2.    
  3.     void onLoadContributorStart();  
  4.    
  5.     void onLoadContributorComplete(Contributor topContributor);  
  6.    
  7.     void onChangeContributorName(String name);  
  8. }  

這個接口起什么作用呢?還記得我之前說的嗎?MVP模式中,view層和presenter層靠的就是接口進行連接,而具體的就是上面的這個了,里面定義的三個方法,第一個是開始獲取數據,第二個是獲取數據成功,第三個是改名。我們的view層(activity)只要實現這個接口就可以了。


下面看activity的代碼

[java] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. public class MainActivity extends MvpActivity<ContributorView,ContributorPresenter> implements ContributorView {  
  2.    
  3.     private ProcessDialog dialog;  
  4.    
  5.     private TextView topContributor;  
  6.    
  7.     @Override  
  8.     protected void onCreate(Bundle savedInstanceState) {  
  9.         super.onCreate(savedInstanceState);  
  10.         setContentView(R.layout.activity_main);  
  11.         topContributor = (TextView)findViewById(R.id.top_contributor);  
  12.     }  
  13.    
  14.     @NonNull  
  15.     @Override  
  16.     public ContributorPresenter createPresenter() {  
  17.         return new ContributorPresenter();  
  18.     }  
  19.    
  20.     public void get(View view){  
  21.         getPresenter().get("square", "retrofit");  
  22.     }  
  23.    
  24.     public void change(View view){  
  25.         getPresenter().change();  
  26.     }  
  27.    
  28.     @Override  
  29.     public void onLoadContributorStart() {  
  30.         showProgress();  
  31.     }  
  32.    
  33.     @Override  
  34.     public void onLoadContributorComplete(Contributor contributor) {  
  35.    
  36.         topContributor.setText(contributor.toString());  
  37.    
  38.         dismissProgress();  
  39.     }  
  40.    
  41.     @Override  
  42.     public void onChangeContributorName(String name) {  
  43.         topContributor.setText(name);  
  44.     }  
  45.    
  46.     public void showProgress(){  
  47.         if(dialog == null){  
  48.             dialog = new ProcessDialog(this);  
  49.         }  
  50.    
  51.         dialog.showMessage("正在加載...");  
  52.     }  
  53.    
  54.     public void dismissProgress(){  
  55.         if(dialog == null){  
  56.             dialog = new ProcessDialog(this);  
  57.         }  
  58.    
  59.         dialog.dismiss();  
  60.     }  
  61. }  



它繼承自MvpActivity,實現了剛才的ContributorView接口。繼承的那個MvpActivity大家這里不用太關心主要是做了一些初始化和生命周期的封裝。我們只要關心這個activity作為view層,到底是怎么工作的。

[java] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. public void get(View view){  
  2.     getPresenter().get("square", "retrofit");  
  3. }  
  4.    
  5. public void change(View view){  
  6.     getPresenter().change();  
  7. }  


get()和change()這兩個方法是我們點擊按鈕以后執行的,可以看到,里面完完全全沒有任何和model層邏輯相關的東西,只是簡單的委托給了presenter,那我們再看看presenter層做了什么

[java] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. public class ContributorPresenter extends MvpBasePresenter<ContributorView> {  
  2.    
  3.     private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {  
  4.    
  5.         @Override  
  6.         public void onStart() {  
  7.             ContributorView view = getView();  
  8.             if(view != null){  
  9.                 view.onLoadContributorStart();  
  10.             }  
  11.         }  
  12.    
  13.         @Override  
  14.         public void onCompleted() {  
  15.    
  16.         }  
  17.    
  18.         @Override  
  19.         public void onError(Throwable e) {  
  20.    
  21.         }  
  22.    
  23.         @Override  
  24.         public void onNext(Contributor topContributor) {  
  25.             ContributorView view = getView();  
  26.             if(view != null){  
  27.                 view.onLoadContributorComplete(topContributor);  
  28.             }  
  29.         }  
  30.     };  
  31.    
  32.     public void get(String owner,String repo){  
  33.         GitHubApi.getContributors(owner, repo)  
  34.                 .take(1)  
  35.                 .observeOn(AndroidSchedulers.mainThread())  
  36.                 .subscribeOn(Schedulers.newThread())  
  37.                 .map(new Func1<List<Contributor>, Contributor>() {  
  38.    
  39.                     @Override  
  40.                     public Contributor call(List<Contributor> contributors) {  
  41.                         return contributors.get(0);  
  42.                     }  
  43.                 })  
  44.                 .subscribe(contributorSub);  
  45.     }  
  46.    
  47.     public void change(){  
  48.         ContributorView view = getView();  
  49.         if(view != null){  
  50.             view.onChangeContributorName("zjutkz");  
  51.         }  
  52.     }  
  53. }  


其實就是把剛才MVC中activity的那部分和model層相關的邏輯抽取了出來,並且在相應的時機調用ContributorView接口對應的方法,而我們的activity是實現了這個接口的,自然會走到對應的方法中。好了,我們來捋一捋。

首先,和MVC最大的不同,MVP把activity作為了view層,通過代碼也可以看到,整個activity沒有任何和model層相關的邏輯代碼,取而代之的是把代碼放到了presenter層中,presenter獲取了model層的數據之后,通過接口的形式將view層需要的數據返回給它就OK了。

這樣的好處是什么呢?首先,activity的代碼邏輯減少了,其次,view層和model層完全解耦,具體來說,如果你需要測試一個http請求是否順利,你不需要寫一個activity,只需要寫一個java類,實現對應的接口,presenter獲取了數據自然會調用相應的方法,相應的,你也可以自己在presenter中mock數據,分發給view層,用來測試布局是否正確。


它竟然說data binding的viewmodel層是binding類,其實不止是這篇文章,其他有一些開發者寫的關於data binding的文章里都犯了一樣的錯誤。大家如果也有這樣的概念,請務必糾正過來!!說完了錯誤的概念,那data binding中真正的viewmodel是什么呢?我們還是以之前MVC,MVP的那個例子做引導。首先是view層,這沒啥好說的,和MVP一樣,只不過多了數據綁定。view層就是xml和activity。

[html] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. <layout xmlns:android="http://schemas.android.com/apk/res/android">  
  2.     <data>  
  3.         <variable name="contributor" type="zjutkz.com.mvvm.viewmodel.Contributor"/>  
  4.     </data>  
  5.     <LinearLayout  
  6.         android:layout_width="match_parent"  
  7.         android:layout_height="match_parent"  
  8.         android:id="@+id/container"  
  9.         android:orientation="vertical"  
  10.         android:fitsSystemWindows="true">  
  11.    
  12.         <Button  
  13.             android:id="@+id/get"  
  14.             android:text="get"  
  15.             android:layout_width="match_parent"  
  16.             android:layout_height="wrap_content"  
  17.             android:onClick="get"/>  
  18.    
  19.         <Button  
  20.             android:id="@+id/change"  
  21.             android:text="change"  
  22.             android:layout_width="match_parent"  
  23.             android:layout_height="wrap_content"  
  24.             android:onClick="change"/>  
  25.           
  26.         <TextView  
  27.             android:id="@+id/top_contributor"  
  28.             android:layout_width="match_parent"  
  29.             android:layout_height="match_parent"  
  30.             android:gravity="center"  
  31.             android:textSize="30sp"  
  32.             android:text="@{contributor.login}"/>  
  33.     </LinearLayout>  
  34.    
  35. </layout>  



[java] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. public class MainActivity extends AppCompatActivity {  
  2.     private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {  
  3.    
  4.         @Override  
  5.         public void onStart() {  
  6.             showProgress();  
  7.         }  
  8.    
  9.         @Override  
  10.         public void onCompleted() {  
  11.    
  12.         }  
  13.    
  14.         @Override  
  15.         public void onError(Throwable e) {  
  16.    
  17.         }  
  18.    
  19.         @Override  
  20.         public void onNext(Contributor contributor) {  
  21.             binding.setContributor(contributor);  
  22.    
  23.             dismissProgress();  
  24.         }  
  25.     };  
  26.    
  27.     private ProcessDialog dialog;  
  28.    
  29.     private MvvmActivityMainBinding binding;  
  30.    
  31.     @Override  
  32.     protected void onCreate(Bundle savedInstanceState) {  
  33.         super.onCreate(savedInstanceState);  
  34.         binding = DataBindingUtil.setContentView(this, R.layout.mvvm_activity_main);  
  35.     }  
  36.    
  37.     public void get(View view){  
  38.         getContributors("square", "retrofit");  
  39.     }  
  40.    
  41.     public void change(View view){  
  42.         if(binding.getContributor() != null){  
  43.             binding.getContributor().setLogin("zjutkz");  
  44.         }  
  45.     }  
  46.    
  47.     public void showProgress(){  
  48.         if(dialog == null){  
  49.             dialog = new ProcessDialog(this);  
  50.         }  
  51.    
  52.         dialog.showMessage("正在加載...");  
  53.     }  
  54.    
  55.     public void dismissProgress(){  
  56.         if(dialog == null){  
  57.             dialog = new ProcessDialog(this);  
  58.         }  
  59.    
  60.         dialog.dismiss();  
  61.     }  
  62.    
  63.     public void getContributors(String owner,String repo){  
  64.         GitHubApi.getContributors(owner, repo)  
  65.                 .take(1)  
  66.                 .observeOn(AndroidSchedulers.mainThread())  
  67.                 .subscribeOn(Schedulers.newThread())  
  68.                 .map(new Func1<List<Contributor>, Contributor>() {  
  69.    
  70.                     @Override  
  71.                     public Contributor call(List<Contributor> contributors) {  
  72.                         return contributors.get(0);  
  73.                     }  
  74.                 })  
  75.                 .subscribe(contributorSub);  
  76.     }  
  77. }  

如果你對data binding框架是有了解的,上面的代碼你能輕松的看懂。

那model層又是什么呢?當然就是那些和數據相關的類,GithubApi等等。

重點來了,viewmodel層呢?好吧,viewmodel層就是是Contributor類!大家不要驚訝,我慢慢的來說。

[java] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. public class Contributor extends BaseObservable{  
  2.    
  3.     private String login;  
  4.     private int contributions;  
  5.    
  6.     @Bindable  
  7.     public String getLogin(){  
  8.         return login;  
  9.     }  
  10.    
  11.     @Bindable  
  12.     public int getContributions(){  
  13.         return contributions;  
  14.     }  
  15.    
  16.     public void setLogin(String login){  
  17.         this.login = login;  
  18.         notifyPropertyChanged(BR.login);  
  19.     }  
  20.    
  21.     public void setContributions(int contributions){  
  22.         this.contributions = contributions;  
  23.         notifyPropertyChanged(BR.contributions);  
  24.     }  
  25.    
  26.     @Override  
  27.     public String toString() {  
  28.         return login + ", " + contributions;  
  29.     }  
  30. }  


我們可以看到,Contributor和MVP相比,繼承自了BaseObservable,有基礎的同學都知道這是為了當Contributor內部的variable改變的時候ui可以同步的作出響應。

我為什么說Contributor是一個viewmodel呢。大家還記得viewmodel的概念嗎?view和viewmodel相互綁定在一起,viewmodel的改變會同步到view層,從而view層作出響應。這不就是Contributor和xml中那些組件元素的關系嗎?所以,大家不要被binding類迷惑了,data binding框架中的viewmodel是自己定義的那些看似是model類的東西!比如這里的Contributor!


話說到這里,那binding類又是什么呢?其實具體對應到之前MVVM的那張圖就很好理解了,我們想一下,binding類的工作是什么?

[java] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. binding = DataBindingUtil.setContentView(this,R.layout.mvvm_activity_main);  
  2. binding.setContributor(contributor);  



首先,binding要通過DataBindingUtil.setContentView()方法將xml,也就是view層設定。

接着,通過setXXX()方法將viewmodel層注入進去。

由於這兩個工作,view層(xml的各個組件)和viewmodel層(contributor)綁定在了一起。

好了,大家知道了嗎,binding類,其實就是上圖中view和viewmodel中間的那根線啊!!


 

MVVM

MVVM的問題呢,其實和MVC有一點像。data binding框架解決了數據綁定的問題,但是view層還是會過重,大家可以看我上面那個MVVM模式下的activity

[java] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. public class MainActivity extends AppCompatActivity {  
  2.    
  3.     private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {  
  4.    
  5.         @Override  
  6.         public void onStart() {  
  7.             showProgress();  
  8.         }  
  9.    
  10.         @Override  
  11.         public void onCompleted() {  
  12.    
  13.         }  
  14.    
  15.         @Override  
  16.         public void onError(Throwable e) {  
  17.    
  18.         }  
  19.    
  20.         @Override  
  21.         public void onNext(Contributor contributor) {  
  22.             binding.setContributor(contributor);  
  23.    
  24.             dismissProgress();  
  25.         }  
  26.     };  
  27.    
  28.     private ProcessDialog dialog;  
  29.    
  30.     private MvvmActivityMainBinding binding;  
  31.    
  32.     @Override  
  33.     protected void onCreate(Bundle savedInstanceState) {  
  34.         super.onCreate(savedInstanceState);  
  35.         binding = DataBindingUtil.setContentView(this, R.layout.mvvm_activity_main);  
  36.     }  
  37.    
  38.     public void get(View view){  
  39.         getContributors("square", "retrofit");  
  40.     }  
  41.    
  42.     public void change(View view){  
  43.         if(binding.getContributor() != null){  
  44.             binding.getContributor().setLogin("zjutkz");  
  45.         }  
  46.     }  
  47.    
  48.     public void showProgress(){  
  49.         if(dialog == null){  
  50.             dialog = new ProcessDialog(this);  
  51.         }  
  52.    
  53.         dialog.showMessage("正在加載...");  
  54.     }  
  55.    
  56.     public void dismissProgress(){  
  57.         if(dialog == null){  
  58.             dialog = new ProcessDialog(this);  
  59.         }  
  60.    
  61.         dialog.dismiss();  
  62.     }  
  63.    
  64.     public void getContributors(String owner,String repo){  
  65.         GitHubApi.getContributors(owner, repo)  
  66.                 .take(1)  
  67.                 .observeOn(AndroidSchedulers.mainThread())  
  68.                 .subscribeOn(Schedulers.newThread())  
  69.                 .map(new Func1<List<Contributor>, Contributor>() {  
  70.    
  71.                     @Override  
  72.                     public Contributor call(List<Contributor> contributors) {  
  73.                         return contributors.get(0);  
  74.                     }  
  75.                 })  
  76.                 .subscribe(contributorSub);  
  77.     }  
  78. }  


大家有沒有發現,activity在MVVM中應該是view層的,但是里面卻和MVC一樣寫了對model的處理。有人會說你可以把對model的處理放到viewmodel層中,這樣不是更符合MVVM的設計理念嗎?這樣確實可以,但是progressDialog的show和dismiss呢?你怎么在viewmodel層中控制?這是view層的東西啊,而且在xml中也沒有,我相信會有解決的方案,但是我們有沒有一種更加便捷的方式呢?


 

首先還是view層。


[html] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. <layout xmlns:android="http://schemas.android.com/apk/res/android">  
  2.     <data>  
  3.         <variable name="contributor" type="zjutkz.com.mvpdatabinding.viewmodel.Contributor"/>  
  4.     </data>  
  5.     <LinearLayout  
  6.         android:layout_width="match_parent"  
  7.         android:layout_height="match_parent"  
  8.         android:id="@+id/container"  
  9.         android:orientation="vertical"  
  10.         android:fitsSystemWindows="true">  
  11.    
  12.         <Button  
  13.             android:id="@+id/get"  
  14.             android:text="get"  
  15.             android:layout_width="match_parent"  
  16.             android:layout_height="wrap_content"  
  17.             android:onClick="get"/>  
  18.    
  19.         <Button  
  20.             android:id="@+id/change"  
  21.             android:text="change"  
  22.             android:layout_width="match_parent"  
  23.             android:layout_height="wrap_content"  
  24.             android:onClick="change"/>  
  25.    
  26.         <TextView  
  27.             android:id="@+id/top_contributor"  
  28.             android:layout_width="match_parent"  
  29.             android:layout_height="match_parent"  
  30.             android:gravity="center"  
  31.             android:textSize="30sp"  
  32.             android:text="@{contributor.login}"/>  
  33.     </LinearLayout>  
  34.    
  35. </layout>  


[java] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. public class MainActivity extends MvpActivity<ContributorView,ContributorPresenter> implements ContributorView {  
  2.    
  3.     private ProcessDialog dialog;  
  4.    
  5.     private ActivityMainBinding binding;  
  6.    
  7.     @Override  
  8.     protected void onCreate(Bundle savedInstanceState) {  
  9.         super.onCreate(savedInstanceState);  
  10.         binding = DataBindingUtil.setContentView(this, R.layout.activity_main);  
  11.     }  
  12.    
  13.     @NonNull  
  14.     @Override  
  15.     public ContributorPresenter createPresenter() {  
  16.         return new ContributorPresenter();  
  17.     }  
  18.    
  19.     public void get(View view){  
  20.         getPresenter().get("square", "retrofit");  
  21.     }  
  22.    
  23.     public void change(View view){  
  24.         if(binding.getContributor() != null){  
  25.             binding.getContributor().setLogin("zjutkz");  
  26.         }  
  27.     }  
  28.    
  29.     @Override  
  30.     public void onLoadContributorStart() {  
  31.         showProgress();  
  32.     }  
  33.    
  34.     @Override  
  35.     public void onLoadContributorComplete(Contributor contributor) {  
  36.         binding.setContributor(contributor);  
  37.         dismissProgress();  
  38.     }  
  39.    
  40.     public void showProgress(){  
  41.         if(dialog == null){  
  42.             dialog = new ProcessDialog(this);  
  43.         }  
  44.    
  45.         dialog.showMessage("正在加載...");  
  46.     }  
  47.    
  48.     public void dismissProgress(){  
  49.         if(dialog == null){  
  50.             dialog = new ProcessDialog(this);  
  51.         }  
  52.    
  53.         dialog.dismiss();  
  54.     }  
  55. }  


然后是presenter層

[java] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. public class ContributorPresenter extends MvpBasePresenter<ContributorView> {  
  2.    
  3.     private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {  
  4.    
  5.         @Override  
  6.         public void onStart() {  
  7.             ContributorView view = getView();  
  8.             if(view != null){  
  9.                 view.onLoadContributorStart();  
  10.             }  
  11.         }  
  12.    
  13.         @Override  
  14.         public void onCompleted() {  
  15.    
  16.         }  
  17.    
  18.         @Override  
  19.         public void onError(Throwable e) {  
  20.    
  21.         }  
  22.    
  23.         @Override  
  24.         public void onNext(Contributor topContributor) {  
  25.             ContributorView view = getView();  
  26.             if(view != null){  
  27.                 view.onLoadContributorComplete(topContributor);  
  28.             }  
  29.         }  
  30.     };  
  31.    
  32.     public void get(String owner,String repo){  
  33.         GitHubApi.getContributors(owner, repo)  
  34.                 .take(1)  
  35.                 .observeOn(AndroidSchedulers.mainThread())  
  36.                 .subscribeOn(Schedulers.newThread())  
  37.                 .map(new Func1<List<Contributor>, Contributor>() {  
  38.    
  39.                     @Override  
  40.                     public Contributor call(List<Contributor> contributors) {  
  41.                         return contributors.get(0);  
  42.                     }  
  43.                 })  
  44.                 .subscribe(contributorSub);  
  45.     }  
  46. }  


model層就是GithubApi等等。

我們使用了data binding框架去節省了類似findViewById和數據綁定的時間,又使用了presenter去將業務邏輯和view層分離。

當然這也不是固定的,你大可以在viewmodel中實現相應的接口,presenter層的數據直接發送到viewmodel中,在viewmodel里更新,因為view和viewmodel是綁定的,這樣view也會相應的作出反應。

說到這里,我還是想重復剛才的那句話,最佳實踐都是人想出來的,用這些框架根本的原因也是為了盡量低的耦合性和盡量高的可復用性。



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM