Android框架MVC、MVP和MVVM探究(圖解+案例+附源碼)

1.MVC、MVP、MVVM這些模式是爲了解決開發過程中的實際問題而提出來的,目前作爲主流的幾種架構模式而被廣泛使用。本文代碼

2、瞭解並區分MVC,MVP,MVVM

2.1 MVC

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


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

Android中的MVC:

對於原生的Android項目來說,layout.xml裏面的xml文件就對應於MVC的view層,裏面都是一些view的佈局代碼,而各種java bean,還有一些類似repository類就對應於model層,至於controller層嘛,當然就是各種activity咯。比如你的界面有一個按鈕,按下這個按鈕去網絡上下載一個文件,這個按鈕是view層的,是使用xml來寫的,而那些和網絡連接相關的代碼寫在其他類裏,比如你可以寫一個專門的networkHelper類,這個就是model層,那怎麼連接這兩層呢?是通過 button.setOnClickListener()這個函數,這個函數就寫在了activity中,對應於controller層。是不是很清晰。

存在的問題:

  • 問題就在於xml作爲view層,控制能力實在太弱了,你想去動態的改變一個頁面的背景,或者動態的隱藏/顯示一個按鈕,這些都沒辦法在xml中做,只能把代碼寫在activity中,造成了activity既是controller層,又是view層的這樣一個窘境。大家回想一下自己寫的代碼,如果是一個邏輯很複雜的頁面,activity或者fragment是不是動輒上千行呢?這樣不僅寫起來麻煩,維護起來更是噩夢。
  • view層和model層是相互可知的,這意味着兩層之間存在耦合,耦合對於一個大型程序來說是非常致命的,因爲這表示開發,測試,維護都需要花大量的精力。

正因爲MVC存在上述的問題,所以引入後面要介紹的兩個框架-MVP和MVVM。

2.2 MVP

MVP,(Model View Presenter)作爲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)的一個控制器。

2.3 MVVM

MVVM,(Model View ViewModel)如果說MVP是對MVC的進一步改進,那麼MVVM則是思想的完全變革。它是將“數據模型數據雙向綁定”的思想作爲核心,因此在View和Model之間沒有聯繫,通過ViewModel進行交互,而且Model和ViewModel之間的交互是雙向的,因此視圖的數據的變化會同時修改數據源,而數據源數據的變化也會立即反應到View上。結構如下圖所示:

3 案例

案列背景介紹:

用戶點擊一個按鈕A,獲取github上對應公司對應倉庫中貢獻排行第一的任的名字,然後我們還會有一個按鈕B,
用戶點擊按鈕B,界面上排行第一的那個人的名字就會換成自己的。效果如圖所示:

3.1 MVC-案列

首先看對應view層的xml文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/container"
    android:orientation="vertical"
    tools:context=".MainActivity"
    android:fitsSystemWindows="true">
    <Button
        android:text="get"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="get"/>
    <Button
        android:text="change"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="change"/>
    <TextView
        android:id="@+id/top_contributor"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:textSize="30sp"/>
</LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

接着看對應controller層的activity:

public class MainActivity extends AppCompatActivity {

    private ProcessDialog dialog;
    private Contributor contributor = new Contributor();

    private TextView topContributor;

    private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {

        @Override
        public void onStart() {
            showProgress();
        }

        @Override
        public void onCompleted() {

        }

        @Override
        public void onError(Throwable e) {

        }

        @Override
        public void onNext(Contributor contributor) {
            MainActivity.this.contributor = contributor;
            topContributor.setText(contributor.login);
            dismissProgress();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        topContributor = (TextView)findViewById(R.id.top_contributor);
    }

    public void get(View view){
        getTopContributor("square", "retrofit");//進行網絡請求
    }

    public void change(View view){
        contributor.login = "GITHUB";
        topContributor.setText(contributor.login);
    }

    public void getTopContributor(String owner,String repo){
        GitHubApi.getContributors(owner, repo)
                .take(1)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.newThread())
                .map(new Func1<List<Contributor>, Contributor>() {
                    @Override
                    public Contributor call(List<Contributor> contributors) {
                        return contributors.get(0);
                    }
                })
                .subscribe(contributorSub);
    }

    public void showProgress(){
        if(dialog == null){
            dialog = new ProcessDialog(this);
        }
        dialog.showMessage("正在加載...");
    }

    public void dismissProgress(){
        if(dialog == null){
            dialog = new ProcessDialog(this);
        }
        dialog.dismiss();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76

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

    public void getTopContributor(String owner,String repo){  
        GitHubApi.getContributors(owner, repo)  
                .take(1)  
                .observeOn(AndroidSchedulers.mainThread())  
                .subscribeOn(Schedulers.newThread())  
                .map(new Func1<List<Contributor>, Contributor>() {  

                    @Override  
                    public Contributor call(List<Contributor> contributors) {  
                        return contributors.get(0);  
                    }  
                })  
                .subscribe(contributorSub);  
    }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

熟悉rxjava和retrofit的同學應該都明白這是啥意思,如果對這兩個開源庫不熟悉也沒事,可以參考rxjavaretrofit 對於這裏大家只要知道這段代碼的意思就是去獲取github上owner公司中的repo倉庫裏貢獻排名第一的那個人。貢獻者是通過Contributor這個java bean存儲的。

  public class Contributor {  

        public String login;  
        public int contributions;  

        @Override  
        public String toString() {  
            return login + ", " + contributions;  
        }  
    }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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

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

  private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {  

        @Override  
        public void onStart() {  
            showProgress();  
        }  

        @Override  
        public void onCompleted() {  

        }  

        @Override  
        public void onError(Throwable e) {  

        }  

        @Override  
        public void onNext(Contributor contributor) {  
            MainActivity.this.contributor = contributor;  
            topContributor.setText(contributor.login);  
            dismissProgress();  
        }  
    };  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

對於另外一個change按鈕的功能相信大家都能看懂了。

流程小結:

1、在xml中寫好佈局代碼;
2、activity作爲一個controller,裏面的邏輯是監聽用戶點擊按鈕並作出相應的操作。比如針對get按鈕,做的工作就是調用GithubApi的方法去獲取數據。
3、GithubApi,Contributor等類則表示MVC中的model層,裏面是數據和一些具體的邏輯操作。

3.2 MVP-案列

通過具體的代碼大家知道了MVC在Android上是如何工作的,也知道了它的缺點,那MVP是如何修正的呢?這裏先向大家推薦github上的一個第三方庫,通過這個庫大家可以很輕鬆的實現MVP。好了,還是看代碼吧。

首先還是xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/container"
    android:orientation="vertical"
    tools:context=".ui.view.MainActivity"
    android:fitsSystemWindows="true">
    <Button
        android:text="get"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="get"/>
    <Button
        android:text="change"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="change"/>
    <TextView
        android:id="@+id/top_contributor"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:textSize="30sp"/>
</LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

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

接下去,我們看一個接口

public interface ContributorView extends MvpView {
    void onLoadContributorStart();
    void onLoadContributorComplete(Contributor topContributor);
    void onChangeContributorName(String name);
}
  • 1
  • 2
  • 3
  • 4
  • 5

這個接口起什麼作用呢?還記得我之前說的嗎?MVP模式中,view層和presenter層靠的就是接口進行連接,而具體的就是上面的這個了,裏面定義的三個方法,

  • 第一個是開始獲取數據;
  • 第二個是獲取數據成功;
  • 第三個是改名。

我們的view層(activity)只要實現這個接口就可以了。

下面看activity的代碼

public class MainActivity extends MvpActivity<ContributorView,ContributorPresenter> implements ContributorView {

    private ProcessDialog dialog;
    private TextView topContributor;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        topContributor = (TextView)findViewById(R.id.top_contributor);
    }

    @NonNull
    @Override
    public ContributorPresenter createPresenter() {
        return new ContributorPresenter();
    }

    public void get(View view){
        getPresenter().get("square", "retrofit");
    }

    public void change(View view){
        getPresenter().change();
    }

    @Override
    public void onLoadContributorStart() {
        showProgress();
    }

    @Override
    public void onLoadContributorComplete(Contributor contributor) {
        topContributor.setText(contributor.toString());
        dismissProgress();
    }

    @Override
    public void onChangeContributorName(String name) {
        topContributor.setText(name);
    }

    public void showProgress(){
        if(dialog == null){
            dialog = new ProcessDialog(this);
        }
        dialog.showMessage("正在加載...");
    }

    public void dismissProgress(){
        if(dialog == null){
            dialog = new ProcessDialog(this);
        }
        dialog.dismiss();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

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

    public void get(View view){  
        getPresenter().get("square", "retrofit");  
    }  

    public void change(View view){  
        getPresenter().change();  
    }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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

public class ContributorPresenter extends MvpBasePresenter<ContributorView> {

    private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {

        @Override
        public void onStart() {
            ContributorView view = getView();
            if(view != null){
                view.onLoadContributorStart();
            }
        }

        @Override
        public void onCompleted() {
        }

        @Override
        public void onError(Throwable e) {
        }

        @Override
        public void onNext(Contributor topContributor) {
            ContributorView view = getView();
            if(view != null){
                view.onLoadContributorComplete(topContributor);
            }
        }
    };

    public void get(String owner,String repo){
        GitHubApi.getContributors(owner, repo)
                .take(1)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.newThread())
                .map(new Func1<List<Contributor>, Contributor>() {

                    @Override
                    public Contributor call(List<Contributor> contributors) {
                        return contributors.get(0);
                    }
                })
                .subscribe(contributorSub);
    }

    public void change(){
        ContributorView view = getView();
        if(view != null){
            view.onChangeContributorName("GITHUB");
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

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

流程小結:

1、在xml中寫好佈局代碼,將activity作爲了view層,通過代碼也可以看到,整個activity沒有任何和model層相關的邏輯代碼,
2、presenter獲取了model層的數據之後,通過接口的形式將view層需要的數據返回給View層了。
3、GithubApi,Contributor等類則表示MVP中的model層,裏面是數據和一些具體的邏輯操作。

好處:

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

3.3 MVVM-案列

首先在看這段內容之前,你需要保證你對data binding框架有基礎的瞭解。不瞭解的同學可以去看下這篇文章。在接下去讓我們開始探索MVVM,MVVM最近在Android上可謂十分之火,最主要的原因就是谷歌推出了data binding這個框架,可以輕鬆的實現MVVM。

直接上代碼:

首先看對應view層的xml文件

    <layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="contributor" type="zjutkz.com.mvvm.viewmodel.Contributor"/>
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/container"
        android:orientation="vertical"
        android:fitsSystemWindows="true">
        <Button
            android:id="@+id/get"
            android:text="get"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="get"/>
        <Button
            android:id="@+id/change"
            android:text="change"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="change"/>
        <TextView
            android:id="@+id/top_contributor"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:textSize="30sp"
            android:text="@{contributor.login}"/>
    </LinearLayout>
</layout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

通過上面的代碼可以看出,有些地方和MVC、MVP不同:新增 data標籤,TextView 數據由變量引入。

MainActivity代碼:

public class MainActivity extends AppCompatActivity {

    private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {

        @Override
        public void onStart() {
            showProgress();
        }

        @Override
        public void onCompleted() {

        }

        @Override
        public void onError(Throwable e) {

        }

        @Override
        public void onNext(Contributor contributor) {
            binding.setContributor(contributor);

            dismissProgress();
        }
    };

    private ProcessDialog dialog;

    private MvvmActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.mvvm_activity_main);
    }

    public void get(View view){
        getContributors("square", "retrofit");
    }

    public void change(View view){
        if(binding.getContributor() != null){
            binding.getContributor().setLogin("zjutkz");
        }
    }

    public void showProgress(){
        if(dialog == null){
            dialog = new ProcessDialog(this);
        }

        dialog.showMessage("正在加載...");
    }

    public void dismissProgress(){
        if(dialog == null){
            dialog = new ProcessDialog(this);
        }

        dialog.dismiss();
    }

    public void getContributors(String owner,String repo){
        GitHubApi.getContributors(owner, repo)
                .take(1)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.newThread())
                .map(new Func1<List<Contributor>, Contributor>() {

                    @Override
                    public Contributor call(List<Contributor> contributors) {
                        return contributors.get(0);
                    }
                })
                .subscribe(contributorSub);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78

如果你對data binding框架是有了解的,上面的代碼你能輕鬆的看懂。 那model層又是什麼呢?當然就是那些和數據相關的類,GithubApi等等。
重點來了,viewmodel層呢?好吧,viewmodel層就是是Contributor類!大家不要驚訝,我慢慢的來說。

public class Contributor extends BaseObservable{

    private String login;
    private int contributions;

    @Bindable
    public String getLogin(){
        return login;
    }

    @Bindable
    public int getContributions(){
        return contributions;
    }

    public void setLogin(String login){
        this.login = login;
        notifyPropertyChanged(BR.login);
    }

    public void setContributions(int contributions){
        this.contributions = contributions;
        notifyPropertyChanged(BR.contributions);
    }

    @Override
    public String toString() {
        return login + ", " + contributions;
    }


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

我們可以看到,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類的工作是什麼?

    binding = DataBindingUtil.setContentView(this, R.layout.mvvm_activity_main);  
    binding.setContributor(contributor);  
  • 1
  • 2

首先,binding要通過DataBindingUtil.setContentView()方法將xml,也就是view層設定。接着,通過setXXX()方法將viewmodel層注入進去。
由於這兩個工作,view層(xml的各個組件)和viewmodel層(contributor)綁定在了一起。好了,大家知道了嗎,binding類,其實就是上圖中view和viewmodel中間的那根線啊!!

4 拓展

前面討論了MVC,MVP和MVVM具體的實現方案,大家肯定都瞭解了它們三者的關係和使用方式。但是,這裏我想說,不要把一個框架看作萬能的,其實MVP和MVVM都是有自己的缺陷的!

MVP:
MVP的問題在於,由於我們使用了接口的方式去連接view層和presenter層,這樣就導致了一個問題,如果你有一個邏輯很複雜的頁面,你的接口會有很多,十幾二十個都不足爲奇。想象一個app中有很多個這樣複雜的頁面,維護接口的成本就會非常的大。
這個問題的解決方案就是你得根據自己的業務邏輯去斟酌着寫接口。你可以定義一些基類接口,把一些公共的邏輯,比如網絡請求成功失敗,toast等等放在裏面,之後你再定義新的接口的時候可以繼承自那些基類,這樣會好不少。

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

public class MainActivity extends AppCompatActivity {  

    private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {  

        @Override  
        public void onStart() {  
            showProgress();  
        }  

        @Override  
        public void onCompleted() {  

        }  

        @Override  
        public void onError(Throwable e) {  

        }  

        @Override  
        public void onNext(Contributor contributor) {  
            binding.setContributor(contributor);  

            dismissProgress();  
        }  
    };  

    private ProcessDialog dialog;  

    private MvvmActivityMainBinding binding;  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        binding = DataBindingUtil.setContentView(this, R.layout.mvvm_activity_main);  
    }  

    public void get(View view){  
        getContributors("square", "retrofit");  
    }  

    public void change(View view){  
        if(binding.getContributor() != null){  
            binding.getContributor().setLogin("zjutkz");  
        }  
    }  

    public void showProgress(){  
        if(dialog == null){  
            dialog = new ProcessDialog(this);  
        }  

        dialog.showMessage("正在加載...");  
    }  

    public void dismissProgress(){  
        if(dialog == null){  
            dialog = new ProcessDialog(this);  
        }  

        dialog.dismiss();  
    }  

    public void getContributors(String owner,String repo){  
        GitHubApi.getContributors(owner, repo)  
                .take(1)  
                .observeOn(AndroidSchedulers.mainThread())  
                .subscribeOn(Schedulers.newThread())  
                .map(new Func1<List<Contributor>, Contributor>() {  

                    @Override  
                    public Contributor call(List<Contributor> contributors) {  
                        return contributors.get(0);  
                    }  
                })  
                .subscribe(contributorSub);  
    }  
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78

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

4.1 MVP + Data Binding

其實,真正的最佳實踐都是人想出來的,我們爲何不結合一下MVP和MVVM的特點呢?其實谷歌已經做了這樣的事,大家可以看下這個。沒錯,就是MVP+data binding,我們可以使用presenter去做和model層的通信,並且使用data binding去輕鬆的bind data。還是讓我們看代碼吧。

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="contributor" type="zjutkz.com.mvpdatabinding.viewmodel.Contributor"/>
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/container"
        android:orientation="vertical"
        android:fitsSystemWindows="true">
        <Button
            android:id="@+id/get"
            android:text="get"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="get"/>
        <Button
            android:id="@+id/change"
            android:text="change"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="change"/>
        <TextView
            android:id="@+id/top_contributor"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:textSize="30sp"
            android:text="@{contributor.login}"/>
    </LinearLayout>
</layout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
public class MainActivity extends MvpActivity<ContributorView,ContributorPresenter> implements ContributorView {

    private ProcessDialog dialog;

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    }

    @NonNull
    @Override
    public ContributorPresenter createPresenter() {
        return new ContributorPresenter();
    }

    public void get(View view){
        getPresenter().get("square", "retrofit");
    }

    public void change(View view){
        if(binding.getContributor() != null){
            binding.getContributor().setLogin("zjutkz");
        }
    }

    @Override
    public void onLoadContributorStart() {
        showProgress();
    }

    @Override
    public void onLoadContributorComplete(Contributor contributor) {
        binding.setContributor(contributor);
        dismissProgress();
    }

    public void showProgress(){
        if(dialog == null){
            dialog = new ProcessDialog(this);
        }

        dialog.showMessage("正在加載...");
    }

    public void dismissProgress(){
        if(dialog == null){
            dialog = new ProcessDialog(this);
        }

        dialog.dismiss();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

然後是presenter層

public class ContributorPresenter extends MvpBasePresenter<ContributorView> {

    private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {

        @Override
        public void onStart() {
            ContributorView view = getView();
            if(view != null){
                view.onLoadContributorStart();
            }
        }

        @Override
        public void onCompleted() {

        }

        @Override
        public void onError(Throwable e) {

        }

        @Override
        public void onNext(Contributor topContributor) {
            ContributorView view = getView();
            if(view != null){
                view.onLoadContributorComplete(topContributor);
            }
        }
    };

    public void get(String owner,String repo){
        GitHubApi.getContributors(owner, repo)
                .take(1)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.newThread())
                .map(new Func1<List<Contributor>, Contributor>() {

                    @Override
                    public Contributor call(List<Contributor> contributors) {
                        return contributors.get(0);
                    }
                })
                .subscribe(contributorSub);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

我們使用了data binding框架去節省了類似findViewById和數據綁定的時間,又使用了presenter去將業務邏輯和view層分離。當然這也不是固定的,你大可以在viewmodel中實現相應的接口,presenter層的數據直接發送到viewmodel中,在viewmodel裏更新,因爲view和viewmodel是綁定的,這樣view也會相應的作出反應。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章