Architecture Jectpack MVVM架构演进

在对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架构
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 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和痛苦于写单元测试的人会有更深的理解。

非常传统,不多解释了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容