Android之MVVM開發模式


MVVM 模式簡介

MVVM模式是指Model-View-ViewModel。相信看過筆者關於MVP的文章的讀者也會發現,無論如何抽象化,在我們的View層中是無法避免的要處理一部分邏輯的。而MVVM模式中的View是將View的狀態和行為完全抽象化,把邏輯與界面的控制完全交給ViewModel處理。
MVVM由下面三個核心組件組成:

    • Model: 用於獲取業務數據模型
    • View: 定義了界面中的布局和外觀
    • ViewModel: 邏輯控制層,負責處理數據和處理View層中的業務邏輯

什么是DataBinding

DataBinding是Google官方推出的數據綁定器,這個綁定器的作用是把數據和View綁定起來,然后數據改變的時候View會自動刷新,這個DataBinding就是我們實現MVVM模式的關鍵。


引入DataBinding

引入DataBinding的方式很簡單,我們只需要在Module的build.gradle添加如下代碼即可。

android{
  ....
  dataBinding{
    enabled true
  }
}

使用DataBinding

使用DataBinding的布局文件和普通的布局文件有點不同,DataBinding布局文件的根標簽是layout標簽,layout里面有一個data元素和View元素,這個View元素就是我們沒使用DataBinding時候的布局文件。下面看看例子代碼:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools"
    tools:context="com.loaderman.modedemo.MainActivity">

    <data>

        <variable
            name="user"
            type="com.loaderman.modedemo.User" />

        <variable
            name="myHandlers"
            type="com.loaderman.modedemo.myHandlers" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:orientation="vertical"
        android:layout_height="match_parent">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:onClick="@{myHandlers.onClickName}"
            android:text="@{user.name}"

            />
        <Button
            android:id="@+id/btn"
            android:layout_width="match_parent"
            android:text="更新數據"
            android:layout_height="wrap_content" />

    </LinearLayout>

</layout>

 


Android MVVM 模式

MVVM在不同的平台實現方式是有一定差異性的。在Google IO 2017 ,Google發布了一個官方應用架構庫Architecture Components,這個架構庫便是Google對Android應用架構的建議,也被稱之為Android官方應用架構指南。Android Architecture Components在Google中國開發者網站中能找到。和Data Binding Library一樣官方還沒翻譯為中文。

下圖是Architecture的應用架構圖。結合Android程序特點,整體上與微軟的MVVM類似,但是做了更細致的模塊划分。


View
顯而易見 Activity/Fragment 便是MVVM中的View,當收到ViewModel傳遞來的數據時,Activity/Fragment負責將數據以你喜歡的方式顯示出來。實際是View成還包括ViewDataBinding(根據xml自動生成),上面中並沒有體現。
ViewModel
ViewModel作為Activity/Fragment與其他組件的連接器。負責轉換和聚合Model中返回的數據,使這些數據易於顯示,並把這些數據改變及時的通知給Activity/Fragment。
ViewModel是具有生命周期意識的,當Activity/Fragment銷毀時ViewModel的onClear方法會被回調,你可以在這里做一些清理工作。
LiveData是具有生命周期意識的一個可觀察的的數據持有者,ViewModel中的數據由LiveData持有,並且只有當Activity/Fragment處於活動時才會通知UI數據的改變,避免無用的刷新UI;
Model
Repository及其下方就是Model了。Repository負責提取和處理數據。數據可以來自本地數據庫(Room),也可以來自網絡,這些數據統一有Repository處理,對應隱藏數據來源及獲取方式
Binder 綁定器
上圖中並沒有標出綁定器在哪里,其實在任何MVVM的實現中,數據綁定技術都是必須的。而上圖僅僅是應用架構圖。
Android中的數據綁定技術由 DataBinding和LiveData共同實現。當Activity/Fragment接收到來自ViewModel中的新數據時(由LiveData自動通知數據的改變),將這些數據通過DataBinding綁定到ViewDataBinding中,UI將會自動刷新,而不用書寫類似setText的方法。


model層

import java.util.List;

/**
 * Author: loaderman

 */
public class TestEntity {


    private int code;
    private String message;
    private List<ResultBean> result;

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public List<ResultBean> getResult() {
        return result;
    }

    public void setResult(List<ResultBean> result) {
        this.result = result;
    }

    @Override
    public String toString() {
        return "TestEntity{" +
                "code=" + code +
                ", message='" + message + '\'' +
                ", result=" + result +
                '}';
    }

    public static class ResultBean {
        /**
         * data : {"subTitle":null,"dataType":"TextCard","actionUrl":null,"id":0,"text":"今日社區精選","type":"header5","follow":null,"adTrack":null}
         * adIndex : -1
         * tag : null
         * id : 0
         * type : textCard
         */

        private DataBean data;
        private int adIndex;
        private Object tag;
        private int id;
        private String type;

        public DataBean getData() {
            return data;
        }

        public void setData(DataBean data) {
            this.data = data;
        }

        public int getAdIndex() {
            return adIndex;
        }

        public void setAdIndex(int adIndex) {
            this.adIndex = adIndex;
        }

        public Object getTag() {
            return tag;
        }

        public void setTag(Object tag) {
            this.tag = tag;
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        @Override
        public String toString() {
            return "ResultBean{" +
                    "data=" + data +
                    ", adIndex=" + adIndex +
                    ", tag=" + tag +
                    ", id=" + id +
                    ", type='" + type + '\'' +
                    '}';
        }

        public static class DataBean {
            /**
             * subTitle : null
             * dataType : TextCard
             * actionUrl : null
             * id : 0
             * text : 今日社區精選
             * type : header5
             * follow : null
             * adTrack : null
             */

            private Object subTitle;
            private String dataType;
            private Object actionUrl;
            private int id;
            private String text;
            private String type;
            private Object follow;
            private Object adTrack;

            public Object getSubTitle() {
                return subTitle;
            }

            public void setSubTitle(Object subTitle) {
                this.subTitle = subTitle;
            }

            public String getDataType() {
                return dataType;
            }

            public void setDataType(String dataType) {
                this.dataType = dataType;
            }

            public Object getActionUrl() {
                return actionUrl;
            }

            public void setActionUrl(Object actionUrl) {
                this.actionUrl = actionUrl;
            }

            public int getId() {
                return id;
            }

            public void setId(int id) {
                this.id = id;
            }

            public String getText() {
                return text;
            }

            public void setText(String text) {
                this.text = text;
            }

            public String getType() {
                return type;
            }

            public void setType(String type) {
                this.type = type;
            }

            public Object getFollow() {
                return follow;
            }

            public void setFollow(Object follow) {
                this.follow = follow;
            }

            public Object getAdTrack() {
                return adTrack;
            }

            public void setAdTrack(Object adTrack) {
                this.adTrack = adTrack;
            }

            @Override
            public String toString() {
                return "DataBean{" +
                        "subTitle=" + subTitle +
                        ", dataType='" + dataType + '\'' +
                        ", actionUrl=" + actionUrl +
                        ", id=" + id +
                        ", text='" + text + '\'' +
                        ", type='" + type + '\'' +
                        ", follow=" + follow +
                        ", adTrack=" + adTrack +
                        '}';
            }
        }
    }
}

Respository用戶從服務器獲取數據,這里使用的網絡框架是retrofit+rxjava+okhttp:

public class TestRespository {
    public void getData(final MutableLiveData<TestEntity> liveData) {
        HttpServiceDataProvder.getInstence().loadTestData(new HttpCallBack<TestEntity>() {
            @Override
            public void onSuccess(TestEntity testEntity) {
                liveData.setValue(testEntity);
            }

            @Override
            public void onError(Throwable e) {

            }
        });
    }

}

ViewModel層

import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

import com.loaderman.frameappdemo.mvvm.model.TestEntity;
import com.loaderman.frameappdemo.mvvm.repository.TestRespository;

/**
 * Author: loaderman
 */
public class TestViewModel extends ViewModel {

    private final TestRespository testRespository;
    private MutableLiveData<TestEntity> liveData;

    public MutableLiveData<TestEntity> getTestEntityLiveData() {
        if (liveData == null) {
            liveData = new MutableLiveData<>();
        }
        return liveData;
    }

    public TestViewModel() {
        testRespository = new TestRespository();
    }


    public void getDataFromNet() {
        testRespository.getData(liveData);
    }

}

view層

<?xml version="1.0" encoding="utf-8"?>
<layout

    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="test"
            type="com.loaderman.frameappdemo.mvvm.model.TestEntity" />
    </data>

    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:id="@+id/refresh"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".mvvm.view.MVVMActivity">

            <TextView
                android:id="@+id/tv"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:text="@{test.result.toString()}"></TextView>
        </LinearLayout>
    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

</layout>
import com.loaderman.frameappdemo.BR;
import com.loaderman.frameappdemo.R;
import com.loaderman.frameappdemo.databinding.ActivityMvvmBinding;
import com.loaderman.frameappdemo.mvvm.model.TestEntity;
import com.loaderman.frameappdemo.mvvm.viewmodel.TestViewModel;

public class MVVMActivity extends AppCompatActivity {

    private ActivityMvvmBinding viewDataBinding;
    private TestViewModel testViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mvvm);
        viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
        testViewModel = new ViewModelProvider(this).get(TestViewModel.class);
        MutableLiveData<TestEntity> testViewModelData = testViewModel.getTestEntityLiveData();
        initData();
        testViewModelData.observe(this, new Observer<TestEntity>() {
            @Override
            public void onChanged(TestEntity testEntity) {
                viewDataBinding.refresh.setRefreshing(false);

                viewDataBinding.setVariable(BR.test, testEntity);
            }
        });

        viewDataBinding.refresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                initData();
            }
        });

    }

    private void initData() {
        viewDataBinding.refresh.setRefreshing(true);
        testViewModel.getDataFromNet();
    }
}

效果:

 如果你對jetpack不了解,歡迎關注微信公眾號【碼上加油站】學習~

示例demo下載

在listview 等列表的adapter中使用數據綁定,可以使用如下方式

     LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        DataBindingUtil.inflate(inflater,R.layout.activity_main, viewgroup,false);

MVVM設計模式的優點
1.雙向綁定技術,當Model變化時,View-Model會自動更新,View也會自動變化。很好做到數據的一致性,不用擔心,在模塊的這一塊數據是這個值,在另一塊就是另一個值了。所以 MVVM模式有些時候又被稱作:model-view-binder模式。
2.View的功能進一步的強化,具有控制的部分功能,若想無限增強它的功能,甚至控制器的全部功幾乎都可以遷移到各個View上(不過這樣不可取,那樣View干了不屬於它職責范圍的事情)。View可以像控制器一樣具有自己的View-Model.
3.由於控制器的功能大都移動到View上處理,大大的對控制器進行了瘦身。不用再為看到龐大的控制器邏輯而發愁了。
4.可以對View或ViewController的數據處理部分抽象出來一個函數處理model。這樣它們專職頁面布局和頁面跳轉,它們必然更一步的簡化。


MVVM設計模式的缺點
第一點:數據綁定使得 Bug 很難被調試。你看到界面異常了,有可能是你 View 的代碼有 Bug,也可能是 Model 的代碼有問題。數據綁定使得一個位置的 Bug 被快速傳遞到別的位置,要定位原始出問題的地方就變得不那么容易了。
第二點:一個大的模塊中,model也會很大,雖然使用方便了也很容易保證了數據的一致性,當時長期持有,不釋放內存,就造成了花費更多的內存。
第三點:數據雙向綁定不利於代碼重用。客戶端開發最常用的重用是View,但是數據雙向綁定技術,讓你在一個View都綁定了一個model,不同模塊的model都不同。那就不能簡單重用View了。



免責聲明!

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



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