项目经验,如需转载,请注明作者: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 不重建原理
- 保存 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;
}
- 恢复 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 安全更新原理
- 注册 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);
}
- 能够监听生命周期改变的 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 类的布局)
缺点
- 实际编写 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了。
-
由于 LiveData 使用的是观察者模式,所以要避免产生循环调用。比如接口请求失败时,通过 LiveData 回调请求失败,在回调中发起新的接口请求。如果接口请求一直失败,又没有做特殊处理的话,就产生了循环调用,这会占用大量的 CPU 资源,降低 App 的性能。
-
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 可以规避这个问题。
-
LiveData 本身是没有 public 方法的,所以我们应该使用其子类 MutableLiveData。这样设计,我们就可以在 Model 中使用 MutableLiveData,在 ViewModel 中,只对 View 提供 LiveData,避免 View 去更新 LiveData。本人也写了一个类 SafeMutableLiveData,用于自动调用 setValue() 还是 postValue(),同时为 getValue() 提供一个默认值。
-
有些场景,比如网络连通性监听,仅需要在确实发生改变时,回调 onChanged(),但是目前 LiveData 无法实现。在 onCreate() 中,给 LiveData 添加 observer 后,只要走到 onStart(),必然会回调 onChanged(),并将上一次的 mData 发送过来,实际上此时并未产生网络连接改变的动作。所以,这种场景还需要特殊处理。
结语
架构设计的目的是提升代码的可读性、可维护性,而不是过度提升代码复杂性,更不是随意引入某某框架。Google 官方也说了,万能架构是不存在的。如果是已经非常稳定的项目,则没有必要重新架构。只不过作为开发人员,还是要保持对新技术的敏感性。
附
图片来自 Google Android 官网