MVP 与 MVVM 优缺点总结

10,475 阅读8分钟

项目经验,如需转载,请注明作者:Yuloran (t.cn/EGU6c76)

前言

主要阐述 Android App 架构之 MVP 与 MVVM 的优点与痛点,不介绍具体的实现方式。因为 MVP 架构简单,无需介绍。而 MVVM 架构相对复杂,核心是 LifecycleOwner、LifecycleObserver、LifecycleRegistry 组件,在此之上,Google 还开发了 DataBinding、ViewModel、LiveData 以实现完整的 MVVM 架构。相关组件已收纳至 JetPack Architecture 中。具体实践可参考本人的开源项目 wanandroid_java,这是一个 MVVM 架构的最小化实践,便于理解以及探索 MVVM 架构背后的实现原理。

MVP

实际上按照代码在 App 中所处位置,描述为 VPM 更合适,即 View -> Presenter -> Model:

这个架构的核心就是 View 与 Presenter 都被抽象成了接口,是面向接口编程的一个实践。因为面向接口,所以实现了依赖隔离,即无需知道具体的 Class 类型。

优点

  • 只需定义好 View 与 Presenter 的接口,即可实现 UI 与业务逻辑独立开发,可由不同的开发团队分别实现
  • 业务逻辑只在 Presenter 中进行维护,遵循了单一职责类的设计原则,提升了代码的可维护性
  • 接口请求及缓存策略只在 Model 中进行维护,同 Presenter 一样,遵循了单一职责类的设计原则,提升了代码的可维护性
  • 避免了 MVC 架构中,Controller 类过于庞大的问题。MVP 架构中,View、Presenter、Model 类的体积都不会太大,提升了代码的可读性与可维护性

痛点

其实这并不是 MVP 架构的痛点,而是整个 Android App 开发的痛点,那就是对 UI 的操作必须在 Activity 与 Fragment 的生命周期之内,更细致一点,最好在 onStart() 之后 onPause()之前,否则极其容易出现各种异常。而 MVP 架构中,Presenter 对 Activity 与 Fragment 的生命周期是无感知的,所以我们需要手动添加相应的生命周期方法,并进行特殊处理,以避免出现异常或内存泄露。

MVVM

实际上按照代码在 App 中所处位置,描述为 VVMM 更合适,即 View -> ViewModel-> Model:

这个架构的核心就是 ViewModel 和 LiveData。ViewModel 的作用是保证当设备因配置改变而重新创建 FragmentActivity(目前 ViewModel 仅支持 FragmentActivity 和 Fragment) 时,数据也不会丢失。LiveData 的作用是保证只在 FragmentActivity 或 Fragment 的生命周期状态为 [onStarted, onResumed] 时,回调 onChanged(T data),所以我们可以在 onChanged() 中安全的更新 UI。下面简单介绍源码中是如何实现的:

ViewModel 不重建原理

  1. 保存 ViewModel 实例:
    /**
     * Retain all appropriate fragment state.  You can NOT
     * override this yourself!  Use {@link #onRetainCustomNonConfigurationInstance()}
     * if you want to retain your own state.
     */
    @Override
    public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();

        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

        if (fragments == null && mViewModelStore == null && custom == null) {
            return null;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = mViewModelStore;
        nci.fragments = fragments;
        return nci;
    }
  1. 恢复 ViewModel 实例:
    /**
     * Perform initialization of all fragments.
     */
    @SuppressWarnings("deprecation")
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        mFragments.attachHost(null /*parent*/);

        super.onCreate(savedInstanceState);

        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null && nc.viewModelStore != null && mViewModelStore == null) {
            mViewModelStore = nc.viewModelStore;
        }

        // omit the irrelevant codes
        ...
    }

由上述代码可见,在 onCreate() 中,如果 getLastNonConfigurationInstance() 不为 null,则将其中的 viewModelStore 恢复为成员变量 mViewModelStore,而 mViewModelStore 内部缓存了 ViewModel 的实例:

public class ViewModelStore {
    // key是固定前缀加ViewModel实现类的类名,value是实现类实例
    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();
    }
}

LiveData 安全更新原理

  1. 注册 observer 并监听 LifecycleOwner 生命周期改变。第一个参数是 LifecycleOwner (一个接口,表示一个类拥有生命周期),目前的实现类有 FragmentActivity 和 Fragment (注:文中 Fragment 皆指 support library 中的 Fragment,目前 Google 已经迁移至 androidx.* 包下,android.app.* 包下的 Fragment 已经被标记为 Deprecated,不推荐使用)
    @MainThread
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        assertMainThread("observe");
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // ignore
            return;
        }
        // 将入参 observer 封装为 LifecycleBoundObserver,这是很常见的写法,也是设计原则之一:多用复合、少用继承
        // 为便于区分,笔者将入参 observer 称为 inner observer
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
        if (existing != null && !existing.isAttachedTo(owner)) {
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        if (existing != null) {
            return;
        }
        // 注册到 Lifecycle 中,以监听生命周期变化
        owner.getLifecycle().addObserver(wrapper);
    }
  1. 能够监听生命周期改变的 LifecycleBoundObserver
    class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
        @NonNull
        final LifecycleOwner mOwner;

        LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
            super(observer);
            mOwner = owner;
        }

        /**
         * 只有 STARTED 或者 RESUMED 才会返回 true
         */
        @Override
        boolean shouldBeActive() {
            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
        }

        /**
         * 生命周期改变时,都会回调
         */
        @Override
        public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
            // destroy 时,移除 inner observer
            if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
                removeObserver(mObserver);
                return;
            }
            activeStateChanged(shouldBeActive());
        }

        @Override
        boolean isAttachedTo(LifecycleOwner owner) {
            return mOwner == owner;
        }

        @Override
        void detachObserver() {
            mOwner.getLifecycle().removeObserver(this);
        }
    }

父类:

    private abstract class ObserverWrapper {
        final Observer<? super T> mObserver;
        boolean mActive;
        int mLastVersion = START_VERSION;

        ObserverWrapper(Observer<? super T> observer) {
            mObserver = observer;
        }

        abstract boolean shouldBeActive();

        boolean isAttachedTo(LifecycleOwner owner) {
            return false;
        }

        void detachObserver() {
        }

        void activeStateChanged(boolean newActive) {
            if (newActive == mActive) {
                return;
            }
            // immediately set active state, so we'd never dispatch anything to inactive
            // owner
            mActive = newActive;
            boolean wasInactive = LiveData.this.mActiveCount == 0;
            LiveData.this.mActiveCount += mActive ? 1 : -1;
            if (wasInactive && mActive) {
                onActive();
            }
            if (LiveData.this.mActiveCount == 0 && !mActive) {
                onInactive();
            }
            if (mActive) {
                dispatchingValue(this);
            }
        }
    }

我们看到,每次生命周期改变时,都会回调 onStateChanged()。如果是 destroy 就移除 inner observer,否则回调 activeStateChanged(shouldBeActive()) 将 shouldBeActive() 的返回值赋给 mActive。而 shouldBeActive() 只有在生命周期为 STARTED 或者 RESUMED 时才返回 true,也只有 mActive 为 true时,才会回调 onChanged:

    private void considerNotify(ObserverWrapper observer) {
        if (!observer.mActive) {
            return;
        }
        // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
        //
        // we still first check observer.active to keep it as the entrance for events. So even if
        // the observer moved to an active state, if we've not received that event, we better not
        // notify for a more predictable notification order.
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        //noinspection unchecked
        observer.mObserver.onChanged((T) mData);
    }

因为 LiveData 支持非 UI 线程更新 mData ( postValue() 方法),所以引入了版本号以解决 ABA 问题。

优点

  • ViewModel:因设备配置改变导致 Activity 重建时,无需从 Model 中再次加载数据,减少了 IO 操作
  • LiveData:更新 UI 时,不用再关注生命周期问题
  • Data Binding: 可以有效减少模板代码的编写,而且目前已经支持双向绑定 (注意:不是所有的 UI 都需要使用 Data Binding,虽然通过 @BindingAdapter 我们真的可以“为所欲为”,最好还是只用于需要绑定 Bean 类的布局)

缺点

  1. 实际编写 App 过程中,在展示最终数据之前,需要展示过渡 UI,比如一个进度条、一个炫酷的动画、加载失败时展示异常提示等,这些可以称之为 ViewState。在 MVP 架构中,这些状态的更新被定义在 View 接口中。但是 MVVM 架构没有涉及。所以我们需要自行封装一个 ViewData 类,用于封装数据类和 ViewState,就像这样:
/**
 * [UI State: init->loading->loadSuccess|loadFailure]
 * <p>
 * Author: Yuloran
 * Date Added: 2018/12/20 20:59
 *
 * @param <T> UI需要的数据类型
 * @since 1.0.0
 */
public class BaseViewData<T>
{
    @NonNull
    private ViewState viewState;

    private T viewData;

    public BaseViewData(@NonNull ViewState viewState)
    {
        Objects.requireNonNull(viewState, "viewState is null!");
        this.viewState = viewState;
    }

    public BaseViewData(@NonNull T viewData)
    {
        this.viewState = ViewState.LOAD_SUCCESS;
        this.viewData = viewData;
    }

    @Nullable
    public T getViewData()
    {
        return viewData;
    }

    public void setViewData(@NonNull T data)
    {
        this.viewData = data;
    }

    @NonNull
    public ViewState getViewState()
    {
        return viewState;
    }

    public void setViewState(ViewState viewState)
    {
        this.viewState = viewState;
    }
}

这样,便可以通过 LiveData 方便的更新 UI了。

  1. 由于 LiveData 使用的是观察者模式,所以要避免产生循环调用。比如接口请求失败时,通过 LiveData 回调请求失败,在回调中发起新的接口请求。如果接口请求一直失败,又没有做特殊处理的话,就产生了循环调用,这会占用大量的 CPU 资源,降低 App 的性能。

  2. LiveData 在每次收到生命周期改变的回调时,只要 inner observer 的 mActive 为 true,并且 mLastVersion != mVersion,都会回调 onChanged(),这在某些场景下会有问题,比如在 onChanged() 中弹出对话框,或者跳转至其它页面。想象一下这个场景:Activity 因设备配置改变而重建了,那么当 Activity 走到 onStart() 时,LiveData 就会回调 onChanged(),这是因为 Activity 重建后,虽然 ViewModel 没有重建,但是 LiveData 的 inner observer 却是重新注册的,所以这个 observer 的 mLastVersion != mVersion,根据上文源码可知,此时一定会回调 onChanged,进而导致错误的弹出了对话框或错误的跳转至了其它页面,使用 Google samples 里面的 SingleLiveEvent 可以规避这个问题。

  3. LiveData 本身是没有 public 方法的,所以我们应该使用其子类 MutableLiveData。这样设计,我们就可以在 Model 中使用 MutableLiveData,在 ViewModel 中,只对 View 提供 LiveData,避免 View 去更新 LiveData。本人也写了一个类 SafeMutableLiveData,用于自动调用 setValue() 还是 postValue(),同时为 getValue() 提供一个默认值。

  4. 有些场景,比如网络连通性监听,仅需要在确实发生改变时,回调 onChanged(),但是目前 LiveData 无法实现。在 onCreate() 中,给 LiveData 添加 observer 后,只要走到 onStart(),必然会回调 onChanged(),并将上一次的 mData 发送过来,实际上此时并未产生网络连接改变的动作。所以,这种场景还需要特殊处理。

结语

架构设计的目的是提升代码的可读性、可维护性,而不是过度提升代码复杂性,更不是随意引入某某框架。Google 官方也说了,万能架构是不存在的。如果是已经非常稳定的项目,则没有必要重新架构。只不过作为开发人员,还是要保持对新技术的敏感性。

  1. Lifecycle.State

lifecycle-states

  1. ViewModel Lifecycle

viewmodel-lifecycle

图片来自 Google Android 官网