Architecture Components MVVM架构演进

在对MVP的架构实践中,发现写单元测试不是那么方便,因为Presenter持有了View的引用,而Mock View的 行为和方法特别的卡慢,因此只能把具体的业务逻辑再抽成一个个独立于Presenter的Logic,进而对Logic写单元测试。

为了探索一种更好的写单测的开发架构,本人转而探索MVVM架构,Google推出的Architecture Components 加上 DataBinding可以实现一个比较好的MVVM架构。 本文拟分享解Google 2017 的 Architecture Components的几个组件,帮助读者快速理解它们,最后加上本人对于MVVM架构的一些思考,如有问题请留言指出。

一、Architecture Components 是什么

按照官网的概述,Architecture Components包含了一系列的组件,这些组件能帮助你设计出稳健的,可测试的,架构清晰的app。
主要包含以下几个部分:

  • Lifecycles组件:定制能自动响应activity\Fragment生命周期的组件

  • LiveData:可被观察订阅的数据,数据发生变化能够通知 activie 态的 观察者

  • ViewModel:一种新的 保存 UI相关 数据的 组件,这种组件能够响应 activity\Fragment生命周期,当configuration发生变化,activity重新创建,它仍然活着,能够快速响应上一次获取的数据。

  • Pageing:分批加载,能够从指定数据源(比如Room数据库)分批加载数据,能配合PagedListAdapter、RecylerView实现分页加载,并且实现了==diff机制==,能够局部更新。

  • Room:Google对于sqlite的一层抽象和包装,实现了ORM,主要优点是体积小,编译检查sql语法,可测试。

注:根据国外大神的测试结果,Room体积非常小,但是性能并没有比得上Realm,Paging和Room理解起来很容易,本文不具体去详细介绍。

二、代码、快速了解

1. 创建一个ViewModel,持有一个liveData
public class MyViewModel extends ViewModel {

   // 创建可变LiveData
   private MutableLiveData<UserDTO> mUserDTO;

    public MutableLiveData<String> getCurrentUser() {
        if (mUserDTO == null) {
            mUserDTO = loadData()
        }
        return mUserDTO;
    }

    // 获取数据
    private MutableLiveData<UserDTO> loadData(){}
   
}
2. 使用ViewModel,观察其持有的LiveData
public class NameActivity extends AppCompatActivity {

    private NameViewModel mModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 其他代码

        // 获取ViewModel,绑定当前的activity的生命周期
        mModel = ViewModelProviders.of(this).get(MyViewModel.class);

        //创建观察者
        final Observer<UserDTO> userObserver = new Observer<UserDTO>() {
            @Override
            public void onChanged(@Nullable final UserDTO user) {
                // 更新UI
                mNameTextView.setText(user.TrueName);
            }
        };

        // 绑定观察者,和当前activity的生命周期
        mModel.getCurrentUser().observe(this, userObserver);
    }
}

三、详解Lifecycle、liveData、ViewModel

1. Lifecycle

以往我们写一些自定义组件,总是要求activity\Fragment在销毁的时候,调用组件的响应方法,比如广播需要在onDesDroy()取消注册,这种模式有两个问题:

  • activity、Fragment的开发者会==忘记调用==自定义组件的相应方法

  • 调用自定义组件的onDesdroy()等类似的代码重复、冗余,到处分布。

为了解决这个问题,Google设计了能响应activity\Fragment生命周期的机制,那就是Lifecycle

Lifecycle主要包含三个部分:

  • Lifecycle 抽象类:看源码可以知道,这个类持有了activity、fragment等组件的生命周期状态,是一个被观察的对象,拥有addObserver 和 removeObserver等方法。
    [图片上传失败...(image-299d96-1511760755846)]

  • LifecycleOwner接口 : 接口的实现者默认持有一个Lifecycle,外接可以调用getLifecycle获取到它持有的Lifecycle对象。26以后,SupportActivity、fragment等组默认实现了该接口

  • LifecycleObserver接口 : Lifecycle的观察者,没有任何方法,主要依靠@OnLifecycleEvent 起效果

public class MyObserver implements LifecycleObserver {
    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    public void connectListener() {
        ...
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    public void disconnectListener() {
        ...
    }
}

myActivity.getLifecycle().addObserver(new MyObserver());

如上代码所示,MyObserver组件的方法会随着myActivity的各种生命而得到调用了。

Lifecycle是LiveData和ViewModel的基础。

2. LiveData

LiveData主要用于包装其他数据,使其成为可被观察的数据,LiveData主要要以下特点:

  • 只要让观察者的生命周期处于active的(STARTED or RESUMED) 才会给它发送值变更事件。

  • 当观察者结束生命周期时,LiveData会自动移除该观察者

@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        removeObserver(observer);
            return;
        }
        // immediately set active state, so we'd never dispatch anything to inactive
        // owner
        activeStateChanged(isActiveState(owner.getLifecycle().getCurrentState()));
    }

  • 由上可知,LiveData和观察者之间是一个双向绑定的过程,实际上,当观察者(LifecycleOwner)注册到LiveData的时候,LiveData也会在内部 初始化一个LifecycleObserver去观察它的观察者。
  //LiveData的添加观察者的方法
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // ignore
            return;
        }
        
        //LiveData会初始化一个LifecycleObserver去观察它的观察者
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        
        LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper);
      
        if (existing != null && existing.owner != wrapper.owner) {
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        if (existing != null) {
            return;
        }
        owner.getLifecycle().addObserver(wrapper);
    }

3. ViewModel

ViewModel究竟是个什么?

先不谈是是不是MVVM中那个ViewModel,在Android里面ViewModel可以理解成一个数据组件,用来持有UI的数据,它的生命周期图如下:
[图片上传失败...(image-3e55d-1511760755846)]

  • 从上图可以看到,ViewModel的生命周期已经超出了activity,即使因为旋转等原因,activity重新创建,它仍然活着,只有当activity彻底finidhed的时候,它才会被回收。

  • 实际上ViewModel的生命周期和传入的Lifecycle绑定,如果传入的是activity,在activity finished之后ViewModel销毁,如果是Fragment,则 dettach后销毁。

// 获取ViewModel,绑定当前的activity的生命周期
mModel = ViewModelProviders.of(this).get(MyViewModel.class);
ViewModel使用场景
  • 用来保存UI的数据,比传统方式会有一些优势(生命周期)

  • 用于一个activity钟的Fragment共享数据

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}

public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // Update the UI.
        });
    }
}
  • 和DataBinding机制联合起来用,省去数据和View的相互绑定代码。

  • 注意,ViewModel应为生命周期比较长,所以不应该以任何形式持有一个View\Lifecycle\activity,以免造成内存泄漏。

四、Architecture Components带来的MVVM架构

1. 先看下 MVC和MVP和MVVM的概念 (图片来自阮一峰的博客)
MVC架构
image

非常传统,不多解释了

MVP架构
image

相比MVC,断开了View和Model的引用,更方便重构View和Model层,在实践中,一般一个View(Activity或者Fragment)对应一个Presenter,Presenter和View相互抽象出接口供对方调用。

MVVM架构
image

MVVM相比MVP Presenter换成了 ViewModel,ViewModel和View变成了双向绑定,View的变动,自动反映在 ViewModel,反之亦然。

这种双向绑定,比较清晰的例子就是Vue.js的 v-model指令,实现了数据和View的双向绑定。

MVVM有什么优势呢?

1. 先谈谈数据驱动的概念

支持MVVM的人(比如Vue.js的支持者)都会养成一个概念,view是由给与它的数据决定的,view的变化也应该是数据的变化引起的。

即使是showLoading这种看似无关的view的状态,也应该转化为数据model的一个Bool值——isShowLoding,view根据model中的isShowLoding的值,决定是否显示Loading界面。

(如果怕麻烦,直接使用回调,View调用ViewModel的方法,同时传入回调,获取到数据后,在回调里关闭Loading界面,这样ViewModel也不会持有View的引用. 因为Kotlin写回调太方便了,这种方式也不错,但是不注意不要陷入回调地狱)

2. 断开引用

Architecture Components的LiveData机制,可以完全实现上述描述的数据驱动概念,View观察ViewModel(或者叫做Presenter,命名无所谓)中的LiveData数据,根据数据的变化而呈现不同的UI。

ViewModel 不需要关心View是怎么样的,不需要调用View的方法,因此也不需要再持有View的引用,这种架构更方便重构、也不需要再从Presenter抽出一个个Logic(以前是为了重用和写单元测试),我们可以直接针对ViewModel写单元测试。

3. ViewModel重用性更好

ViewModel相当于一个高内聚的数据获取器,不再局限于于被哪个View使用,因此它的可用性更好,界面A可以用,界面B也可以用。

3. 更少的代码

MVVM一般会由框架层实现数据和View的双向绑定,因此代码会更加精简。

好了,以前是本人对于Architecture Components组件带来的一些关于架构的思考,不再上代码了,相信实践MVP和痛苦于写单元测试的人会有更深的理解。

非常传统,不多解释了

推荐阅读更多精彩内容