Android MVVM pattern

Android MVVM pattern

concept

The MVVM pattern refers to _Model-View-ViewModel_. I believe that readers who have read the article about MVP will also find that no matter how abstract it is, it is inevitable to deal with some logic in our View layer. The View in the MVVM mode completely abstracts the state and behavior of the View, and completely hands over the control of the logic and interface to the ViewModel.

Android_MVVM mode_content1.png

MVVM consists of the following three core components:

  1. Model: Data layer, including data entities and operations on data entities
  2. View: Interface layer, corresponding to Activity, XML, View, responsible for data display and user interaction
  3. ViewModel: The association layer, which binds the Model and the View, and refreshes each other in real time when the Model or View changes.

important point

  1. View only does UI-related work, does not involve any business logic, does not involve operating data, and does not process data. UI and data are strictly separated
  2. ViewModel only does business logic-related work, does not involve any UI-related operations, does not hold control references, and does not update UI

------## DataBinding

What is DataBinding

DataBinding is a data binder officially launched by Google. The function of this binder is to bind the data to the View, and then the View will be automatically refreshed when the data changes. This DataBinding is the key to our realization of the MVVM pattern.

Introduce DataBinding

The way to introduce DataBinding is very simple, we only need to add the following code in the build.gradle of App

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

复制代码

Using DataBinding

The layout file using DataBinding is a bit different from the ordinary layout file. The root tag of the DataBinding layout file is the layout tag, and there is a data element and a View element in the layout. This View element is the layout file when we do not use DataBinding. Take a look at the example code below:

<?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 pattern

MVVM在不同的平台实现方式是有一定差异性的。在Google IO 2017 ,Google发布了一个官方应用架构库Architecture Components,这个架构库便是Google对Android应用架构的建议,也被称之为Android官方应用架构指南。Android Architecture Components在Google中国开发者网站中能找到。和Data Binding Library一样官方还没翻译为中文。

下图是Architecture的应用架构图。结合Android程序特点,整体上与微软的MVVM类似,但是做了更细致的模块划分。

安卓_MVVM模式_内容2.png

层次 说明
View 显而易见Activity/Fragment便是MVVM中的View,当收到ViewModel传递过来的数据时,Activity/Fragment负责将数据以你喜欢的方式显示出来。View还包括ViewDataBinding,上面中并没有体现。
ViewModel ViewModel作为Activity/Fragment与其他组件的连接器。负责转换和聚合Model中返回的数据,使这些数据易于展示,并把这些数据改变即时通知给Actvity/Fragment。
ViewModel是具有生命周期意识的,当Activity/Fragment销毁时ViewModel的onClear方法会被回调,你可以在这里做一些清理工作。LiveData是具有生命周期意识的一个可观察的数据持有者,ViewModel中的数据有LiveData持有,并且只有当Activity/Fragment处于活动时才会通知UI数据的改变,避免无用的刷新UI。
Model Repository及其下方就是model了。Repository负责提取和处理数据。数据来源可以是本地数据库,也可以来自网络,这些数据统一有Repository处理,对应隐藏数据来源以及获取方式。
Binder绑定器 Android中的数据绑定技术由DataBinding和LiveData共同实现。当Activity/Fragment接收到来自ViewModel中的新数据时(由LiveData自动通知数据的改变),将这些数据通过DataBinding绑定到ViewDataBinding中,UI将会自动刷新。

MVVM的实现

Model层

import java.util.List;
 
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;

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>

复制代码

Activity文件

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();
    }
}

复制代码

实际效果

安卓_MVVM模式_内容3.gif

在listview 等列表的adapter中使用数据绑定,可以使用如下方式:

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

复制代码

MVVM的特点

MVVM的优势

  1. 双向绑定技术,当Model变化时,View-Model会自动更新,View也会自动变化。很好做到数据的一致性,不用担心,在模块的这一块数据是这个值,在另一块就是另一个值了。所以 MVVM模式有些时候又被称作:model-view-binder模式。
  2. View的功能进一步的强化,具有控制的部分功能,若想无限增强它的功能,甚至控制器的全部功几乎都可以迁移到各个View上(不过这样不可取,那样View干了不属于它职责范围的事情)。View可以像控制器一样具有自己的View-Model
  3. 由于控制器的功能大都移动到View上处理,大大的对控制器进行了瘦身。不用再为看到庞大的控制器逻辑而发愁了。
  4. 可以对View或ViewController的数据处理部分抽象出来一个函数处理model。这样它们专职页面布局和页面跳转,它们必然更一步的简化。

MVVM的劣势

  1. 数据绑定使得 Bug 很难被调试。你看到界面异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题。数据绑定使得一个位置的 Bug 被快速传递到别的位置,要定位原始出问题的地方就变得不那么容易了。

  2. 一个大的模块中,model也会很大,虽然使用方便了也很容易保证了数据的一致性,当时长期持有,不释放内存,就造成了花费更多的内存。

  3. 数据双向绑定不利于代码重用。客户端开发最常用的重用是View,但是数据双向绑定技术,让你在一个View都绑定了一个model,不同模块的model都不同。那就不能简单重用View了。

Guess you like

Origin juejin.im/post/7085269726032560159