MVC、MVP 和 MVVM 架构模式 & ViewModel

在学习viewModel之前,我们需要先了解MVC、MVP 和 MVVM 架构模式。

简单的了解可以查看MVC,MVP 和 MVVM 的图示

另超级好文:Android App的设计架构:MVC,MVP,MVVM与架构

MVC

图示:

MVC、MVP 和 MVVM 架构模式 & ViewModel

是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。

Android中界面部分也采用了当前比较流行的MVC框架,在Android中:

视图层(View) :一般采用XML文件进行界面的描述。

控制层(Controller): Android的控制层的重任通常落在了众多的Activity的肩上。这句话也就暗含了不要在Activity中写代码,要通过Activity交割Model业务逻辑层处理,这样做的另外一个原因是Android中的Actiivity的响应时间是5s,如果耗时的操作放在这里,程序就很容易被回收掉。

模型层(Model) :我们针对业务模型,建立的数据结构和相关的类,就可以理解为AndroidApp的Model,Model是与View无关,而与业务相关的。对数据库的操作、对网络等的操作都应该在Model里面处理,当然对业务计算等操作也是必须放在的该层的。

MVC的缺点: 在Android开发中,Activity并不是一个标准的MVC模式中的Controller,它的首要职责是加载应用的布局和初始化用户 界面,并接受并处理来自用户的操作请求,进而作出响应。随着界面及其逻辑的复杂度不断提升,Activity类的职责不断增加,以致变得庞大臃肿

MVP

在App开发过程中,经常出现的问题就是某一部分的代码量过大,虽然做了模块划分和接口隔离,但也很难完全避免。因为Activity本身需要担负与用户之间的操作交互,界面的展示,不是单纯的Controller或View。因此引入MVP框架。MVP图示如下:

MVC、MVP 和 MVVM 架构模式 & ViewModel

MVP从更早的MVC框架演变过来,与MVC有一定的相似性:Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。

View:负责绘制UI元素(一般包括Activity,Fragment,Adapter等直接和UI相关的类)、与用户进行交互(在Android中体现为Activity)

Model:负责存储、检索、操纵数据(有时也实现一个Model interface用来降低耦合)

Presenter:作为View与Model交互的中间纽带,处理与用户交互的负责逻辑。

View interface: 需要View实现的接口,View通过View interface与Presenter进行交互,降低耦合,方便进行单元测试

View interface的必要性:回想一下你在开发Android应用时是如何对代码逻辑进行单元测试的?是否每次都要将应用部署到Android模拟器或真机上,然后通过模拟用 户操作进行测试?然而由于Android平台的特性,每次部署都耗费了大量的时间,这直接导致开发效率的降低。而在MVP模式中,处理复杂逻辑的Presenter是通过interface与View(Activity)进行交互的,这说明我们可以通过自定义类实现这个interface来模拟Activity的行为对Presenter进行单元测试,省去了大量的部署及测试的时间。

当我们将Activity复杂的逻辑处理移至另外的一个类(Presenter)中时,Activity其实就是MVP模式中的View,它负责UI元素的初始化,建立UI元素与Presenter的关联(Listener之类),同时自己也会处理一些简单的逻辑(复杂的逻辑交由 Presenter处理)。

MVP的Presenter是框架的控制者,承担了大量的逻辑操作,而MVC的Controller更多时候承担一种转发的作用。因此在App中引入MVP的原因,是为了将此前在Activty中包含的大量逻辑操作放到控制层中,避免Activity的臃肿。

两种模式的主要区别:

(最主要区别)View与Model并不直接交互,而是通过与Presenter交互来与Model间接交互。而在MVC中View可以与Model直接交互
通常View与Presenter是一对一的,但复杂的View可能绑定多个Presenter来处理逻辑。而Controller是基于行为的,并且可以被多个View共享,Controller可以负责决定显示哪个View
Presenter与View的交互是通过接口来进行的,更有利于添加单元测试。

因此我们可以发现MVP的优点如下:

1、模型与视图完全分离,我们可以修改视图而不影响模型;

2、可以更高效地使用模型,因为所有的交互都发生在一个地方——Presenter内部;

3、我们可以将一个Presenter用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁;

4、如果我们把逻辑放在Presenter中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试)。

在MVP中,Activity的代码不臃肿;
在MVP中,Model(IUserModel的实现类)的改动不会影响Activity(View),两者也互不干涉,而在MVC中会;
在MVP中,IUserView这个接口可以实现方便地对Presenter的测试;
在MVP中,UserPresenter可以用于多个视图,但是在MVC中的Activity就不行。

MVVM

MVVM可以算是MVP的升级版,其中的VM是ViewModel的缩写,ViewModel可以理解成是View的数据模型和Presenter的合体,ViewModel和View之间的交互通过Data Binding完成,而Data Binding可以实现双向的交互,这就使得视图和控制层之间的耦合程度进一步降低,关注点分离更为彻底,同时减轻了Activity的压力。其图示如下:

MVC、MVP 和 MVVM 架构模式 & ViewModel

MVVM模式将Presener改名为View Model,基本上与MVP模式完全一致,唯一的区别是,它采用双向绑定(data-binding),View的变动,自动反映在 ViewModel,反之亦然。

所以这里也就出现了ViewModel。

ViewModel

在Android中的MVVM出现是Jetpack系列,主要使用LiveData、ViewModel和Data-binding结合实现MVVM。

我们知道类似旋转屏幕等配置项改变会导致我们的 Activity 被销毁并重建,此时 Activity 持有的数据就会跟随着丢失,而ViewModel 则并不会被销毁,从而能够帮助我们在这个过程中保存数据,而不是在 Activity 重建后重新去获取。并且 ViewModel 能够让我们不必去担心潜在的内存泄露问题,同时 ViewModel 相比于用onSaveInstanceState() 方法更有优势,比如存储相对大的数据,并且不需要序列化以及反序列化。

ViewModel的生命周期图为:

MVC、MVP 和 MVVM 架构模式 & ViewModel

从上图中可以看到 ViewModel 在 Activity 的重建时依然存活。为什么?

使用ViewModel主要的业务场景为:

我们旋转屏幕而没有其他处理的时候,因为屏幕旋转activity被销毁重建,存放在activity的数据自然就会丢失了。而如果使用ViewModel就不会出现这种情况。

比如新建一个MyViewModel.java,内容如下:

public class MyViewModel extends ViewModel {
    public int number = 0;
}

在主Activity中的代码如下:

public class MainActivity extends AppCompatActivity {
    Button button;
    MyViewModel myViewModel;
    TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = findViewById(R.id.button);
        textView = findViewById(R.id.textView);
        myViewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(MyViewModel.class);
        textView.setText(String.valueOf(myViewModel.number));
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myViewModel.number++;
                textView.setText(String.valueOf(myViewModel.number));
            }
        });
    }
}

就可以实现在屏幕翻转的时候,数据不会丢失。

那么ViewModel是如何实现的?

ViewModel内部实现

and ViewModel怎么更新数据的。

myViewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(MyViewModel.class);

通过ViewModelProvider实例的get方法来得到自己所需要的ViewModel实例对象。

那么myViewModel.number++;操作的时候,是如何完成更新和保存的?不妨先看下我们的ViewModel对象是如何得到的:

public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    String canonicalName = modelClass.getCanonicalName();
    if (canonicalName == null) {
        throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
    }
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);

    if (modelClass.isInstance(viewModel)) {
        //noinspection unchecked
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }

    viewModel = mFactory.create(modelClass);
    mViewModelStore.put(key, viewModel);
    //noinspection unchecked
    return (T) viewModel;
}

在viewModel实例化后,使用ViewModelStore来进行存储。当首次创建的时候使用NewInstanceFactory,如下:

public static class NewInstanceFactory implements Factory {

    @SuppressWarnings("ClassNewInstance")
    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        //noinspection TryWithIdenticalCatches
        try {
            return modelClass.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        }
    }
}

可以看到这里是反射来得到的实例对象。然后通过mViewModelStore.put(key, viewModel);将该对象进行存储,put实现如下:

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }
}

在ViewModelStore类中,使用HashMap进行存储,key为前面提到的DEFAULT_KEY + ":" + canonicalName,也就是DEFAULT_KEY + ":"加全限定类名。

到此,我们知道了ViewModel是如何得到实例对象,以及如何存储的了。但是还是不知道该对象是如何进行更新数据的。所以我们需要更加仔细些。

我们重新观察:

new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(MyViewModel.class);

前面就只剩下ViewModelProvider类的这个构造方法没有了解,那么不妨看下:

public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
    this(owner.getViewModelStore(), factory);
}

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
    mFactory = factory;
    this.mViewModelStore = store;
}

也就是说关键在于ViewModelStore是如何得到的。也即是说ViewModelStoreOwner类是如何书写的。注意到:

public interface ViewModelStoreOwner {
    ViewModelStore getViewModelStore();
}

ViewModelStoreOwner是一个接口,且传入的是this对象,那么在Activity的某个父类中必然做了相应的实现。很容易可以追踪到在FragmentActivity中实现了这个接口:

public class FragmentActivity extends SupportActivity implements ViewModelStoreOwner

那么我们可以找下这个接口对应的getViewModelStore方法的实现:

public ViewModelStore getViewModelStore() {
    if (this.getApplication() == null) {
        throw new IllegalStateException("Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call.");
    } else {
        if (this.mViewModelStore == null) {
            FragmentActivity.NonConfigurationInstances nc = (FragmentActivity.NonConfigurationInstances)this.getLastNonConfigurationInstance();
            if (nc != null) {
                this.mViewModelStore = nc.viewModelStore;
            }

            if (this.mViewModelStore == null) {
                this.mViewModelStore = new ViewModelStore();
            }
        }

        return this.mViewModelStore;
    }
}

不存在就使用ViewModelStore的无参数构造进行实例化。

一件比较有趣的事情就是,我在追踪viewModel类的时候,发现了这里给了一个使用案例,和我使用的并不一样:

public class MyFragment extends Fragment {
    public void onStart() {
    UserModel userModel = 	ViewModelProviders.of(getActivity()).get(UserModel.class);
    }
}

使用的是ViewModelProviders,而不是ViewModelProvider。然后我在MainActivity.java文件中做了相应的替换:

//        myViewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(MyViewModel.class);
myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);

发现结果居然一样。

注意到我这里的版本为:implementation 'android.arch.lifecycle:extensions:1.1.1',原来在2.2.0中ViewModelProviders方法被弃用了。

但是,至于ViewModel怎么更新数据的?这个我确实没有看见。这个问题就以后再思考。

上一篇:WPF轻量级MVVM框架入门2.1.2


下一篇:浅析MVC,MVP,MVVM三种模式