转载请标明出处:
https://blog.csdn.net/xuehuayous/article/details/82777022;
本文出自:【Kevin.zhou的博客】

前言:去年对项目的架构进行了调整,迁移到了MVVM架构,还好之前的代码写的还算清晰,在调整的过程中也没有遇到太多的问题。改造的过程中也查找了大量资料,不管是架构相关的还是针对MVVM的代码或文章,发现在Android中对于MVVM大家还没有达成共识。或许每一种方式在固定的业务场景中都是有益的,并没有谁对谁错。之前在技术群里也听到有很多人想了解MVVM,正好我也总结下希望对想了解的朋友能起到帮助作用;如果有不同的意见,也许是我功力尚浅,希望能够留下评论,我们共同探讨,相互学习。

关于什么是MVC/MVP/MVVM以及他们之间的区别,这里就不赘述了,如果想了解网上的资料很多,而且很容易陷入他们的对比,最终搞的云里雾里。

一句话了解MVVM

  1. View 只处理用户的即时交互;
  2. ViewModel 只处理业务逻辑;
  3. Model 只处理数据存储与获取。

View

View层,不再是我们之前理解的是一个TextView、LinearLayout等View控件,只要是可以和用户进行交互的都可以归属到View层,比如:Activity、Fragment、Dialog、PopupWindow、XML布局、布局Adapter、系统及自定义View控件等等,只要是用户看到的、摸到的都归View层管。也就是说禁止在这里做业务逻辑、数据操作等和View无直接相关的事情。

Model

model层,与我们之前把定义的实体bean对象称为model不同的是,这里的model被赋予了数据管理的职责。数据的管理包括数据存储与数据获取,这里存储的位置不仅限于本地(SharedPreferences、SQLite),而且也会是网络上的任何存储方式。

ViewModel

ViewModel层,说是只处理业务逻辑,更准确的说法是Model层和View层的粘合剂,从Model中获取数据整合之后提供给View层进行显示,响应View层的事件调用Model层进行响应的落地。

它们的关系

通过以上大致了解了View-ViewModel-Model是什么、主要干什么,通过这三个层次的定义把视图、业务、数据进行了切割,这样职责与分层清楚了,那他们之间怎么传递数据呢?

View层包含两大类,Activity、Fragment、Dialog、PopupWindow等代码类和XML布局类,代码类与布局类交互的方式是DataBinding,通过DataBinding的双向绑定技术可以轻松实现View数据的双向通知。

Model层数据的获取与保存绝大部分应该是在服务端的,网络访问是数据交互的重头戏,这部分必须在异步中完成,异步处理方面大家都弃Handler而去投奔了Rx的怀抱,所有在Model层的网络部分与ViewModel层交互通过Rx的观察者模式Observable。

ViewModel层从Model层获取数据后,需求将数据通知到View层并显示出来,采用Android架构组件中的LiveData。

Demo

通过以上说明,大家应该是对MVVM架构有了大致的了解,并且具体怎么操作是云里雾里。下面就以一个示例的方式进行说明。思考再三我决定用Java而不是Kotlin来进行编写,尽管Kotlin用起来很舒服,但是照顾到可能一部分朋友不太了解。

需求一

需求内容

编写字符串,保存到本地,再读取出来修改下并保存。

撸码

XML布局

布局很简单,上面一个EditText,下面是两个Button。

也许有朋友对这种布局的编写有些疑惑,请移步上一篇博客《认识Android中的双向绑定》 ,对Android中的databinding进行初识。
其中android:text="@={view.content}"是做数据双向绑定用的;
android:onClick="@{view::onSaveClick}"即可调用view中的onSaveClick(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">    <data class="com.kevin.mvvmdemo.MainActivityBinding">        <variable            name="view"            type="com.kevin.mvvmdemo.MainActivity" />    data>    <android.support.constraint.ConstraintLayout        android:layout_width="match_parent"        android:layout_height="match_parent"        android:padding="10dp">        <EditText            android:id="@+id/et_content"            android:layout_width="0dp"            android:layout_height="125dp"            android:background="#EEEEEE"            android:hint="请输入内容"            android:inputType="textMultiLine"            android:padding="8dp"            android:text="@={view.content}"            android:textSize="16sp"            app:layout_constraintLeft_toLeftOf="parent"            app:layout_constraintRight_toRightOf="parent"            app:layout_constraintTop_toTopOf="parent" />        <Button            android:id="@+id/btn_save"            android:layout_width="0dp"            android:layout_height="wrap_content"            android:layout_margin="8dp"            android:text="保存"            android:onClick="@{view::onSaveClick}"            app:layout_constraintLeft_toLeftOf="parent"            app:layout_constraintRight_toLeftOf="@+id/btn_load"            app:layout_constraintTop_toBottomOf="@+id/et_content"            app:layout_constraintVertical_chainStyle="spread" />        <Button            android:id="@+id/btn_load"            android:layout_width="0dp"            android:layout_height="wrap_content"            android:layout_margin="8dp"            android:text="加载"            android:onClick="@{view::onLoadClick}"            app:layout_constraintLeft_toRightOf="@+id/btn_save"            app:layout_constraintRight_toRightOf="parent"            app:layout_constraintTop_toBottomOf="@+id/et_content"            app:layout_constraintVertical_chainStyle="spread" />    android.support.constraint.ConstraintLayout>layout>

编写MainActivity

创建MainActivity,Activity中为设置databinding及两个回调方法,onSaveClick(view)onLoadClick(view)这两个方法是在XML布局中调用的。

public class MainActivity extends AppCompatActivity {    private MainActivityBinding mBinding;    public MutableLiveData<String> content = new MutableLiveData<>();    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        mBinding = MainActivityBinding.inflate(getLayoutInflater());        mBinding.setLifecycleOwner(this);        setContentView(mBinding.getRoot());        mBinding.setView(this);    }    public void onSaveClick(View view) {        Toast.makeText(this, "点击了 保存 按钮", Toast.LENGTH_SHORT).show();    }    public void onLoadClick(View view) {        Toast.makeText(this, "点击了 加载 按钮", Toast.LENGTH_SHORT).show();    }}

目前看到的效果是这样的,Activity中响应XML布局的的点击。

注意
这里需要注意的是,在《认识Android中的双向绑定》中可观察对象使用的是:

public ObservableField<String> content = new ObservableField<>();

这里使用的是:

public MutableLiveData<String> content = new MutableLiveData<>();

其中ObservableField是databinding中提供的,MutableLiveData是android arch 中提供的,二者都可以,Google推荐是MutableLiveData,Android架构会管理它的生命周期,在使用MutableLiveData,需要添加如下:

mBinding.setLifecycleOwner(this);

编写MainRepository

创建MainRepository,Repository主要做的就是数据的获取与保存,这里简单的保存在SharedPreferences,提供了保存和读取的两个方法。

public class MainRepository {    private Application mApplication;    public MainRepository(Application application) {        this.mApplication = application;    }    public void saveData(String content) {        SharedPreferences sp = mApplication.getSharedPreferences("demo", Context.MODE_PRIVATE);        sp.edit().putString("content", content).commit();    }    public String loadData() {        SharedPreferences sp = mApplication.getSharedPreferences("demo", Context.MODE_PRIVATE);        return sp.getString("content", "");    }}

编写MainViewModel

创建MainViewModel,继承自Android架构组件中的ViewModel或者AndroidViewModel,其中AndroidViewModel运行传递Application对象。
作为业务逻辑的处理者以及Model层与View层的粘结剂,这里没有需要处理的物业逻辑,仅仅是对数据层封装了一下,提供给View层使用。

public class MainViewModel extends AndroidViewModel {    private MainRepository mRepository;    public MainViewModel(@NonNull Application application) {        super(application);        mRepository = new MainRepository(application);    }    public void setData(String content) {        mRepository.saveData(content);    }    public String getData() {        return mRepository.loadData();    }}

修改MainActiviy

添加依赖库

实例化ViewModel需要用到Android架构组件中的另外一个库,在build.gradle中添加依赖:

dependencies { // ... ...    implementation 'android.arch.lifecycle:extensions:latest.release'}
实例化ViewModel

在onCreate()方法中初始化ViewModel:

mViewModel = ViewModelProviders.of(this).get(MainViewModel.class);
调用ViewModel方法

之后就可以在点击的监听中进行响应的操作啦,代码如下:

    public void onSaveClick(View view) {        mViewModel.setData(content.getValue());    }    public void onLoadClick(View view) {        String data = mViewModel.getData();        content.setValue(data);    }
完整代码
public class MainActivity extends AppCompatActivity {    public MutableLiveData<String> content = new MutableLiveData<>();    private MainActivityBinding mBinding;    private MainViewModel mViewModel;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        mBinding = MainActivityBinding.inflate(getLayoutInflater());        mBinding.setLifecycleOwner(this);        setContentView(mBinding.getRoot());        mBinding.setView(this);        mViewModel = ViewModelProviders.of(this).get(MainViewModel.class);    }    public void onSaveClick(View view) {        mViewModel.setData(content.getValue());    }    public void onLoadClick(View view) {        String data = mViewModel.getData();        content.setValue(data);    }}

效果

通过以上,就完成了一个最简单的MVVM,效果如下:

大家可能觉得这么简单的东西,我在Activity中简单的几行就能搞定,你却来这么一堆调用,不是很麻烦。那么我们做一下修改呢?

需求二

需求内容

由于输入的字数不能够无线长,那我们限制最长为100个字,太少了也不行,最少10个字,如果不合法就提示。

撸码

修改布局

增加两个文案

<TextView    android:id="@+id/tv_max_count"    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:layout_marginBottom="4dp"    android:layout_marginRight="8dp"    android:text="/100"    android:textColor="@color/gray"    android:textSize="12sp"    app:layout_constraintBottom_toBottomOf="@+id/et_content"    app:layout_constraintRight_toRightOf="@+id/et_content" /><TextView    android:id="@+id/tv_count"    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:layout_marginBottom="4dp"    android:textSize="12sp"    app:layout_constraintBottom_toBottomOf="@+id/et_content"    app:layout_constraintRight_toLeftOf="@+id/tv_max_count"    tools:text="0" />

大家先想一下,如果用原生的怎么搞?给EditText控件设置内容更改监听,当变化后将获取内容长度设置给TextView。不复杂,就是一堆模板代码。
但是如果使用databinding,也许你不信,两行代码就搞定。

<TextView    android:id="@+id/tv_count"    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:layout_marginBottom="4dp"    android:text="@{String.valueOf(view.content.length())}"    android:textColor="@{(view.content.length() > 9 && view.content.length() < 101) ? @color/gray : @color/red, [email protected]/red}"    android:textSize="12sp"    app:layout_constraintBottom_toBottomOf="@+id/et_content"    app:layout_constraintRight_toLeftOf="@+id/tv_max_count"    tools:text="0" />

一行是动态绑定text,一行是动态设置颜色。
其中设置文案的比较好理解,就是获取可观察对象(输入内容)的长度,设置给TextView显示。

android:text="@{String.valueOf(view.content.length())}"

下面设置颜色的,可观察对象(输入内容)的长度大于9 并且 小于 101 则显示灰色,其他情况显示红色。

android:textColor="@{(view.content.length() > 9 && view.content.length() < 101) ? @color/gray : @color/red, [email protected]/red}"

通过以上演示大家已经看到了View层databinding的好处。

如果输入不合法的时候不允许提交怎么做呢?想必大家都想到了在XML中处理。

<Button    android:id="@+id/btn_save"    android:layout_width="0dp"    android:layout_height="wrap_content"    android:layout_margin="8dp"    android:enabled="@{view.content.length() > 9 && view.content.length() < 101, default=false}"    android:onClick="@{view::onSaveClick}"    android:text="保存"    app:layout_constraintLeft_toLeftOf="parent"    app:layout_constraintRight_toLeftOf="@+id/btn_load"    app:layout_constraintTop_toBottomOf="@+id/et_content"    app:layout_constraintVertical_chainStyle="spread" />

也是一行代码就可以搞定:

android:enabled="@{view.content.length() > 9 && view.content.length() < 101, default=false}"

需求三

需求内容

如果我想保存到网络呢?

撸码

本想着在我的服务器上搭一个简单的服务,提供保存和读取的接口。思来想去,我们的重点不在这里,于是改为本地模拟的形式。

网络实体

由于网络交互中有共同的状态、信息、数据等部分,这里进行抽取下。不太了解的同学可以看下我之前的一篇博客《一步步封装Retrofit + RxJava2》,这里有MVVM架构中Model层是怎样一步步生成的。

public class HttpResult<T> {    public int status;    public String msg;    public T data;}

模拟网络交互

仍然是两个方法,保存数据和获取数据,获取数据返回的是具体数据,
在保存数据中,返回的是数据本身。并且每个交互都做了延迟2秒的操作。

public class NetWork {    private Application mApplication;    public NetWork(Application application) {        this.mApplication = application;    }    public Observable<HttpResult<String>> saveData(final String content) {        return Observable.create(                new ObservableOnSubscribe<HttpResult<String>>() {                    @Override                    public void subscribe(ObservableEmitter<HttpResult<String>> e) {                        SharedPreferences sp = mApplication.getSharedPreferences("demo", Context.MODE_PRIVATE);                        boolean success = sp.edit().putString("content", content).commit();                        HttpResult httpResult = new HttpResult();                        if (success) {                            httpResult.status = 200;                            httpResult.msg = "保存成功";                        } else {                            httpResult.status = 5;                            httpResult.msg = "保存失败";                        }                        httpResult.data = content;                        e.onNext(httpResult);                        e.onComplete();                    }                }        )                .delay(2, TimeUnit.SECONDS)                .subscribeOn(Schedulers.io())                .observeOn(AndroidSchedulers.mainThread());    }    public Observable<HttpResult<String>> loadData() {        return Observable.create(                new ObservableOnSubscribe<HttpResult<String>>() {                    @Override                    public void subscribe(ObservableEmitter<HttpResult<String>> e) {                        SharedPreferences sp = mApplication.getSharedPreferences("demo", Context.MODE_PRIVATE);                        HttpResult httpResult = new HttpResult();                        httpResult.data = sp.getString("content", "");                        httpResult.status = 200;                        httpResult.msg = "获取成功";                        e.onNext(httpResult);                        e.onComplete();                    }                }        )                .delay(2, TimeUnit.SECONDS)                .subscribeOn(Schedulers.io())                .observeOn(AndroidSchedulers.mainThread());    }}

MainRepository修改

之前在MainRepository中进行了SharedPreferences的操作,如果把MainRepository定义为数据仓库是明显不合理的,他不能涉及具体的操作,应该做在哪(本地、网络)获取、存储数据,以及缓存策略等等。让它回归本质,调用我们模拟的网络层。

public class MainRepository {    private NetWork mNetWork;    public MainRepository(Application application) {        this.mNetWork = new NetWork(application);    }    public Observable<String> saveData(String content) {        return mNetWork.saveData(content)                .map(new Function<HttpResult<String>, String>() {                    @Override                    public String apply(HttpResult<String> result) {                        return result.data;                    }                });    }    public Observable<String> loadData() {        return mNetWork.loadData()                .map(new Function<HttpResult<String>, String>() {                    @Override                    public String apply(HttpResult<String> result) {                        return result.data;                    }                });    }}

由于网络层向上暴露的返回是带有网络层信息的Observable>,那这里要把HttpResult转换掉,通过Rx的map操作符可以轻松完成。

MainViewModel修改

为了使用方便,将之前定义在MainActivity中的可观察对象移动到ViewModel层,调用MainRepository进行对应操作。

public class MainViewModel extends AndroidViewModel {    private MainRepository mRepository;    public MutableLiveData<String> content = new MutableLiveData<>();    public MainViewModel(@NonNull Application application) {        super(application);        mRepository = new MainRepository(application);    }    public void setData() {        mRepository.saveData(content.getValue()).subscribe();    }    public void getData() {        mRepository.loadData().subscribe(new Consumer<String>() {            @Override            public void accept(String s) {                content.setValue(s);            }        });    }}

效果

需求四

需求内容

给网络访问加上等待圈

撸码

添加加载圈,肯定是在View层进行添加,但是数据获取真正执行在Model层,调用在ViewModel层,所以调用的时候才知道什么时候该显示、什么时候隐藏。但是我们也知道View层持有ViewModel层的引用,ViewModel层是不可能去调用View层方法的,那么该怎么办呢?
可以在View层添加对ViewModel层的数据监听,这样显示与隐藏改变的时候View层即可做相应的操作。

XML布局修改

在布局中添加ProgressBar

<ProgressBar    android:id="@+id/loading"    android:layout_width="38dp"    android:layout_height="38dp"    android:layout_gravity="center_horizontal"    android:indeterminateBehavior="repeat"    android:indeterminateDrawable="@drawable/circular"    app:layout_constraintBottom_toBottomOf="parent"    app:layout_constraintLeft_toLeftOf="parent"    app:layout_constraintRight_toRightOf="parent"    app:layout_constraintTop_toTopOf="parent"    android:visibility="gone"/>

ViewModel修改

添加可观察对象,并且在方法执行前设置显示,在方法执行后设置隐藏。

public class MainViewModel extends AndroidViewModel {    private MainRepository mRepository;    public MutableLiveData<String> content = new MutableLiveData<>();    public MutableLiveData<Boolean> showLoading = new MutableLiveData(); // 是否显示加载圈    public MainViewModel(@NonNull Application application) {        super(application);        mRepository = new MainRepository(application);    }    public void setData() {        showLoading.setValue(true);        mRepository.saveData(content.getValue()).subscribe(new Consumer<String>() {            @Override            public void accept(String s) {                showLoading.setValue(false);            }        });    }    public void getData() {        showLoading.setValue(true);        mRepository.loadData().subscribe(new Consumer<String>() {            @Override            public void accept(String s) {                content.setValue(s);                showLoading.setValue(false);            }        });    }}

MainActivity修改

onCreate()方法中添加对viewModel的可观察对象的监听,进而对ProgressBar设置显示与隐藏。

mViewModel.showLoading.observe(this, new Observer<Boolean>() {     @Override     public void onChanged(@Nullable Boolean showLoading) {         mBinding.loading.setVisibility(showLoading ? View.VISIBLE : View.GONE);     } });

效果

到目前为止,已经完成了MVVM的小Demo,尽管还有很多小细节要处理,看下效果吧。

这样如果之后觉得这个加载圈不好看,也只需要更改View层的就可以了,ViewModel层的内容不必更改,ViewModel层不关心显示的样式,它只是给关心该数据(注册了数据监听)的进行通知,至于怎么显示它不会理会。

总结

通过以上大致了解了View-ViewModel-Model是什么,通过这三个层次的定义把视图、业务、数据进行了切割,了解了它们之间如何传递数据。

View 只处理用户的即时交互,ViewModel 只处理业务逻辑,Model 只处理数据存储与获取。View 持有 ViewModel ,直接调用ViewMode中的方法,ViewModel持有Model,View层响应用户事件通过ViewModel层调用Model层数据,数据通过可观察对象的形式上传到View层进行显示。

源码下载 Android-MVVM-demo


马上中秋了,预祝大家中秋节快乐!!!

更多相关文章

  1. android在代码中为new出的控件设置ID及setId()异常
  2. android activity与fragment的生命周期详细研究
  3. Android(安卓)布局文件的性能--使用include标签重用Layout---转
  4. Android(安卓)高级控件-ListView的扩展
  5. Android中的一个简单的List应用
  6. Android(安卓)四大组件——Service 生命周期
  7. Android(安卓)总结:ContentProvider 的使用
  8. Android(安卓)MVP设计模式实例详解
  9. Android(安卓)播放SD卡上指定的一首歌(初级版)

随机推荐

  1. Android(安卓)Gallery3D效果 教程 案例
  2. android中string.xml文件的使用
  3. Android嵌入式底层开发技术(应试)
  4. Android SDK 2.1 下载与安装教程
  5. 【Android】Android的快速开发框架Afinal
  6. Android导航栏资源总结,单纯防丢!
  7. 学习Android 必备 实例大集合
  8. 【Android游戏开发二十一】Android os设
  9. android 网络 post get
  10. Android消息处理系统