美文网首页
安卓MVVM模式

安卓MVVM模式

作者: 蜗牛是不是牛 | 来源:发表于2022-04-11 22:21 被阅读0次

    安卓MVVM模式

    概念

    MVVM模式是指_Model-View-ViewModel_。相信看过关于MVP文章的读者也会发现,无论如何抽象化,在我们的View层中是无法避免的要处理一部分逻辑的。而MVVM模式中的View是将View的状态和行为完全抽象化,把逻辑与界面的控制完全交给ViewModel处理。

    安卓_MVVM模式_内容1.png

    MVVM由下面三个核心组件组成:

    1. Model: 数据层,包含数据实体和对数据实体的操作
    2. View: 界面层,对应于Activity,XML,View,负责数据显示以及用户交互
    3. ViewModel: 关联层,将Model和View进行绑定,Model或者View更改时,实时刷新对方

    注意点

    1. View只做和UI相关的工作,不涉及任何业务逻辑,不涉及操作数据,不处理数据。UI和数据严格的分开
    2. ViewModel只做和业务逻辑相关的工作,不涉及任何和UI相关的操作,不持有控件引用,不更新UI

    ------## DataBinding

    什么是DataBinding

    DataBinding是Google官方推出的数据绑定器,这个绑定器的作用是把数据和View绑定起来,然后数据改变的时候View会自动刷新,这个DataBinding就是我们实现MVVM模式的关键。

    引入DataBinding

    引入DataBinding的方式很简单,我们只需要在App的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类似,但是做了更细致的模块划分。

    安卓_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了。

    本文转自 https://juejin.cn/post/7085269726032560159,如有侵权,请联系删除。

    相关文章

      网友评论

          本文标题:安卓MVVM模式

          本文链接:https://www.haomeiwen.com/subject/nxwwsrtx.html