MVVM&Android实践(四):MVVM的前世今生

第四部分:MVVM


前面的主要内容,基本只是介绍了MVVM的基础DataBinding的语法和使用。但这远远不够,下面才是本文的重头戏。Android 的关于代码的组织方式(你也可以称之为设计模式),从MVC 到MVP 再到MVVM,经历了三次重要变化。

MVVM的前世今生

MVC

Android设计之初,就遵循的MVC模式,此时的模块划分是:

  • Model:所有和数据相关的类
  • View:布局文件
  • Controller:Activity、Fragment

但因为布局文件又不能完全承担自己的职责,我们被迫将一部分更新View的代码写在了Activity(也就是Controller)中,导致Controller中的代码混乱,不利于阅读和维护,既有Controller的逻辑,也有View层逻辑。

想象一下一个业务场景:在界面上点击一个Button,登录一个网站并将登录结果在界面上呈现。

这个需求,我们需要有一个界面,来放点击按钮,和登录结果的TextView,这就是View层。再新建一个LoginHelper类来处理网络数据,这就是Model层。剩下一个Controller就是Activity,在Activity中,通过button的OnClickListener触发Model层的网络请求后,将登录的结果返回给Activity,Activity可以直接将结果显示到界面的TextView上。

是不是很清晰,各司其职。但当你的需求中,包含需要动态改变界面控件隐藏和显示、界面颜色变化、动画控制,你也只能把这些View相关的代码写在Controller的Activity中。

造成这种混乱的根本原因,是作为View层的布局文件,本身控制能力太弱,View层需要动态改变的东西,不得不依赖Contoller提供控制。

MVP

为了解决Controller逻辑混乱的问题,于是有了MVP模式。MVP模块划分:

  • Model:所有和数据相关的类,和MVC中的Model职责一致,处理数据逻辑。
  • View:布局文件 + Activity or Fragment
  • Presenter:需要额外增加接口和类

MVC中的作为View层的xml不是控制力弱吗?直接把Activity和Fragment划分给View层,控制力是不是爆表!!!这就是MVP模式。

Presenter作为Controller的替代者,接替了它的工作。什么工作?当然是连接Model和View层

Presenter通常由两部分组成:

  1. 接口:让View层的Activity或者Fragment实现,用于通知View层更新界面。
  2. 具体的Presenter类,比如LoginPresenter,它持有Model层和View层的引用。用于处理业务逻辑。

MVP的工作原理是,通过View层(Activity或者Fragment)创建对应的Presenter,并相互持有彼此的引用。在Presenter中,创建Model实例,并获取Model处理后的数据,通过接口通知View层更新界面。

MVP的一大好处是,通过接口,让Model和View层解耦,可以实现面向接口编程的目的,这对于大型项目的开发,和各模块单独测试帮助很大。

那么,MVP有什么缺点呢?

MVP的缺点是,额外增加了一个接口,会增加额外的维护成本。真是的应用场景是,需求不断变化,接口也需要做对应调整。

该问题的解决方法是,在定义接口时,尽可能的斟酌接口,合理利用继承、实现,合理的使用设计模式。

MVVM

对于MVVM来说,它的模块分为:

  • Model:Model还是哪个Model,一直都没变。
  • View:依然是布局文件 + Activity + Fragment。
  • ViewModel:就是被绑定在View层对象所属的类。

本文的最终目的就是这个,以一个下载代码的例子说明。只贴核心代码,Demo源码见:

Model

public class Model {
    public interface OnDownloadListener {
        void onDownloadSuccess();
        void onDownloading(int progress);
        void onDownloadFailed();
    }

    public static void downloadFile(final String url, final String dir, final OnDownloadListener listener) {
		// ...
                           listener.onDownloading(progress);
		// ...
    }
}

为了显而易见,我将唯一属于Model模块的类直接命名为Model。该类使用Okhttp网络框架,用于下载文件。

  • OnDownloadListener接口用于通知ViewModel下载的结果,所以ViewModel需要实现该接口。
  • public static void downloadFile(final String url, final String dir, final OnDownloadListener listener):用于具体的下载逻辑,供外界调用。url为需要下载的地址,dir为下载文件存放的目录,listener为下载监听。
  • listener.onDownloading(progress);:下载过程中,调用监听器,通知View层更新下载进度。

在该例中,数据只有来自网络的数据流,而Model类负责下载和存储数据,作为Model层存在。

ViewModel

public class ViewModel implements Model.OnDownloadListener {
    private static final String TAG = "ViewModel";

    private static final String URL = "http://172.0.6.55/test_for_live.mp4";

    public ObservableInt mDownloadProgress = new ObservableInt(0);

    public void onClick(View view) { // 点击事件处理函数
        Log.i(TAG, "onClick");
        Model.downloadFile(URL, "/mvvm/", this); // 连接Model
    }

    @Override
    public void onDownloadSuccess() {
        Log.i(TAG, "onDownloadSuccess");
    }

    @Override
    public void onDownloading(int progress) {
        mDownloadProgress.set(progress); // 进度更新变量
    }

    @Override
    public void onDownloadFailed() {
        Log.i(TAG, "onDownloadFailed");
    }
}

本例中,ViewModel类作为ViewMode层的为一类,方便起见也起名叫ViewModel,希望不要搞混了。

ViewMode层的主要工作是将View层和Model层连接起来,主要靠如下关键调用实现:

  1. public void onClick(View view):该函数定义后,将会被绑定在View层的Dowload按钮上,点击DowLoad按钮,就会执行Model层的下载逻辑。

  2. Model.downloadFile(URL, "/mvvm/", this);:DownLoad按钮点击后,立马执行下载,由该函数调用可以看到Model是如何与ViewModel连接的。函数调用后,下载进度,将会通过OnDownloadListener的回调接口通知到ViewModel层。

    当然,这里是静态调用。使用类实例的方式调用应该更加常见一些。

  3. mDownloadProgress.set(progress);:定义了一个ObservableInt对象mDownloadProgress,该对象将和View层绑定,当下载进度更新时,mDownloadProgress值变化将直接触发View层的刷新。

View

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">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <ProgressBar
            android:id="@+id/pb"
            style="@android:style/Widget.ProgressBar.Horizontal"
            android:layout_width="0dp"
            android:layout_height="10dp"
            android:max="100"
            android:progress="@{viewModel.mDownloadProgress}"
            android:progressDrawable="@drawable/progressbar_preview"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintBottom_toTopOf="@id/bt_download"
            app:layout_constraintWidth_percent="0.875" />

        <Button
            android:id="@+id/bt_download"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{viewModel::onClick}"
            android:text="DownLoad"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>

    <data class="MvvmBinding">

        <variable
            name="viewModel"
            type="com.superli.demo.ViewModel" />
    </data>
</layout>

View层的Activity:

public class MVVMActivity extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
        binding.setViewModel(new ViewModel());
    }
}

View层的工作主要就是将ViewModel和自己绑定,同时通过ViewMode间接和Model中的数据绑定,这种绑定关系并不直观,但显然是存在的。

以上就是MVVM代码原因各层的划分方式了。

MVVM的不足

MVC的问题是View层和Controller层在Activity中融合到一起了,原因是xml布局文件的能力太弱,无法控制一些动态的内容。在MVVM中,data binding通过数据绑定的方式解决了这个问题,使View层和Model可以各司其职。但当你需要处理一些特殊的逻辑时,还是会让View层出现不必要的代码。

比如在本例中,我现在想控制ProgressBar,做一个移动或者随便什么样式的动画,我将不得不在Activity中做控制,但控制的逻辑原则上应该交给ViewModel啊。类似的情况还有很多。

关于data binding的使用,Google官方探索除了一套MVP + data binding的道路,原理大概是通过data binding解决数据绑定的问题,使用Presenter和Model交互。相关sample源码见:

有兴趣可以去研究一下。

术语表

文中术语 含义
事件 即Event
发布了79 篇原创文章 · 获赞 30 · 访问量 3万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章