在对MVP的架构实践中,发现写单元测试不是那么方便,因为Presenter持有了View的引用,而Mock View的 行为和方法特别的卡慢,因此只能把具体的业务逻辑再抽成一个个独立于Presenter的Logic,进而对Logic写单元测试。
为了探索一种更好的写单测的开发架构,本人转而探索MVVM架构,Google推出的Architecture Jectpack 加上 DataBinding可以实现一个比较好的MVVM架构。 本文拟分享解Google 2017 的 Architecture Jectpack的几个组件,帮助读者快速理解它们,最后加上本人对于MVVM架构的一些思考,如有问题请留言指出。
一、Architecture Jectpack 是什么
按照官网的概述,Architecture Jectpack包含了一系列的组件,这些组件能帮助你设计出稳健的,可测试的,架构清晰的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架构
非常传统,不多解释了
MVP架构
相比MVC,断开了View和Model的引用,更方便重构View和Model层,在实践中,一般一个View(Activity或者Fragment)对应一个Presenter,Presenter和View相互抽象出接口供对方调用。
MVVM架构
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 Jetpack的LiveData机制,可以完全实现上述描述的数据驱动概念,View观察ViewModel(或者叫做Presenter,命名无所谓)中的LiveData数据,根据数据的变化而呈现不同的UI。
ViewModel 不需要关心View是怎么样的,不需要调用View的方法,因此也不需要再持有View的引用,这种架构更方便重构、也不需要再从Presenter抽出一个个Logic(以前是为了重用和写单元测试),我们可以直接针对ViewModel写单元测试。
3. ViewModel重用性更好
ViewModel相当于一个高内聚的数据获取器,不再局限于于被哪个View使用,因此它的可用性更好,界面A可以用,界面B也可以用。
3. 更少的代码
MVVM一般会由框架层实现数据和View的双向绑定,因此代码会更加精简。
好了,以前是本人对于Architecture Jetpack组件带来的一些关于架构的思考,不再上代码了,相信实践MVP和痛苦于写单元测试的人会有更深的理解。
非常传统,不多解释了