ViewModel Overview

ViewModel Overview

ViewModel旨在以生命周期关联的方式存储和管理与UI相关的数据。ViewModel允许数据在配置更改(例如屏幕旋转)后继续存在。


引入方式

dependencies {
    def lifecycle_version = "2.0.0"

    // ViewModel and LiveData
    implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
    // alternatively - just ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" // For Kotlin use lifecycle-viewmodel-ktx
    // alternatively - just LiveData
    implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
    // alternatively - Lifecycles only (no ViewModel or LiveData). Some UI
    //     AndroidX libraries use this lightweight import for Lifecycle
    implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"

    annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" // For Kotlin use kapt instead of annotationProcessor
    // alternately - if using Java8, use the following instead of lifecycle-compiler
    implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"

    // optional - ReactiveStreams support for LiveData
    implementation "androidx.lifecycle:lifecycle-reactivestreams:$lifecycle_version" // For Kotlin use lifecycle-reactivestreams-ktx

    // optional - Test helpers for LiveData
    testImplementation "androidx.arch.core:core-testing:$lifecycle_version"
}

UI controller的生命周期由Android framework管理,framework可以决定销毁或重建UI controller来响应一些用户的操作或是完全不受用户控制的设备事件。

在系统销毁或重建UI controller时,所有存在里面的和UI相关的临时数据都会丢失。例如:一个Activity中有个用户列表,当Activity由于配置变化而recreate时,新的Activity必须重新取一次用户列表。对于简单的数据,Activity可以使用onSavedInstanceState()方法来restore数据。但这个方法只适用于可以序列号后再反序列号的少量数据,不适用于大量数据,如用户列表或者Bitmap。

另一个问题就是UI controller需要经常进行异步调用来执行一些耗时操作,所以UI controller也得管理这些调用,并确保他们销毁后系统能正确清理掉,以避免潜在的内存泄露。

像Activity、Fragment这类UI controller主要用于展示数据,对用户操作做出反应,或处理系统级的对话,如permission request。如果让UI controller再负责从数据库或网络加载数据的话,会太冗余膨胀,会让单个类处理了APP的所有工作,而不是把工作委托给其他类,同时也增加测试难度

所以需要将view data的管理从UI controller分离出来。


实现ViewModel

Android 架构组件为UI controller提供了ViewModel类,负责为UI准备数据。ViewModel对象在配置变化期间会自动保留,以便下个Activity或Fragment实例能立即获取到数据。例如,你需要在APP中展示用户列表,把获取和保存用户列表放在ViewModel来负责,而不是Activity或Fragment。

public class MyViewModel extends ViewModel {
    private MutableLiveData> users;
    public LiveData> getUsers() {
        if (users == null) {
            users = new MutableLiveData>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}

随后可以在Activity中读取用户信息

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.

        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}

当Activity重新创建时,会读取到Activity第一次创建时创建的MyViewModel实例。当持有ViewModel的Activity关闭时,framework调用ViewModel对象的onCleared()方法来清理资源

ViewModel不可以引用View、Lifecycle或任何可能包含activity context的类

ViewModel的设计就是为了存活在view或LifecycleOwner的生命周期之外。这样也方便测试覆盖ViewModel,因为ViewModel不了解view或 Lifecycle对象。ViewModel可以包含LifecycleOwner,例如LiveData对象。但ViewModel绝不能观察到LiveData的变化(LiveData这类属于有生命周期感知的可观察对象)。如果ViewModel需要Application context,例如要获取system service,那么可以继承AndroidViewModel类,这个类有个接收Application参数的构造函数。

ViewModel的生命周期

当获取ViewModel时,ViewModel对象被限定在为传递给ViewModelProvider的Lifecycle。ViewModel会一直在内存中,直到它被限定的Lifecycle永久的销毁掉:如Activity中的finish,Fragment中的detach。
图中展示了Activity在经历了旋转然后finish的生命周期状态。旁边是关联了Activity生命周期的ViewModel的生命周期。对于Fragment也同理。

图一

我们一般在系统第一次调用Activity的onCreate()方法时请求一个ViewModel对象。但Activity的生命周期中系统可能会多次调用onCreate()方法,例如屏幕旋转的时候。而最初请求到的这个ViewModel会一直存在直到Activity的finish和destroy。

Fragment之间共享数据

一个很常见的需求就是一个Activity中的多个Fragment需要进行通信。master-detail fragments就是个常见情况:用户在一个Fragment中选择一项,在另一个Fragment中要展示这一项的详情。很常见的方法就是两个Fragment都要定义一堆接口,而且host Activity必须把他们绑定在一起。此外两个Fragment都要处理其他Fragment未创建或不可见的情况。

使用ViewModel可以解决这个痛点。Fragment可以共享他们host Activity的ViewModel来通信。

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

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

    public LiveData 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.
        });
    }
}

各个Fragment都是使用包含他们的Activity来获取到ViewModelProvider返回的同一个SharedViewModel实例,这个实例受控于他们的Activity。

有以下好处:

  • Activity完全不需要做任何事,也不知道Fragment之间的通信
  • 除了和SharedViewModel的连接,Fragment也不需要知道其他Fragment。如果一个Fragment消失,另一个依旧正常使用。
  • 每个Fragment拥有自己的生命周期,不受其他Fragment影响,如果一个Fragment替代另一个,UI还能正常工作。

使用ViewModel来替换Loader

CursorLoader等Loader class经常用于保持APP中UI的数据和数据库同步。现在我们可以使用ViewModel和一些其他类来取代loader,使用ViewModel将UI controller和data loading的操作分开,这样就减少了class之间的依赖。

使用loader的常见方法是:APP中使用CursorLoader来观察数据库的内容,当数据库的值发生改变时,loader自动触发重新加载数据并更新UI
loading_data_with_loaders

ViewModel配合Room和LiveData来取代loader。ViewModel确保数据不受配置变化影响而被销毁,Room在数据库变化时通知LiveData,而LiveData返回来用新的数据更新UI。

loading_data_with_viewmodel

随着数据变得越来越复杂,我们可能选择单独一个类来专门加载数据。ViewModel的作用是为UI controller封装数据,来使数据能够不因为配置变化而销毁。关于如何在配置变化时加载、持久化数据,可查看Saving UI States

Samples

Codelabs