欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android ViewModel详解

程序员文章站 2022-06-08 12:58:28
...

1. ViewModel概述

ViewModel类被设计为通过lifecycle感知的方式存储和管理UI相关数据。ViewModel类允许数据在配置更改(如屏幕旋转)中保活。

注意:要将ViewModel导入到Android项目中,请参见将组件添加到项目中

Android框架管理UI控制器的生命周期,如activities和fragments。该框架可以决定销毁或重新创建UI控制器,以响应某些完全超出您控制的用户操作或设备事件。

如果系统销毁或重新创建UI控制器,则在其中存储的任何与UI相关的临时数据都会丢失。例如,您的应用程序可能在一个Activity中包含用户的列表。当配置更改时候activity会重新创建,新Activity必须重新获取用户列表。对于简单数据,该Activity可以使用onSaveInstanceState()方法并从onCreate()中的bundle恢复其数据,但是这种方法仅适用于可以序列化/反序列化的少量数据,而不适用于潜在的大量数据,如用户列表或位图。

另一个问题是UI控制器经常需要进行异步调用,这可能需要一些时间才能返回。UI控制器需要管理这些调用,并确保在控制器销毁的时候,系统能清理它们,以避免潜在的内存泄漏。这种管理需要大量的维护,在配置更改时需要重新创建对象的情况下,这是资源的浪费,因为对象可能必须重新发出它已经发出的调用。

UI控制器(如activities和fragments)主要用于显示UI数据、对用户动作作出反应或处理与操作系统间的通信(如权限请求)。要求UI控制器还负责从数据库或网络加载数据,这些都造成了控制器代码急剧膨胀。将过多的职责分配给UI控制器会导致一个类试图自己处理应用程序的所有工作,而不是将工作委托给其他类。以这种方式向UI控制器分配过度的职责也会使测试变得更加困难。

将视图数据所有权与UI控制器逻辑分离是更容易和更有效的。

2. 实现一个ViewModel

架构组件为UI控制器提供ViewModel帮助类,用于负责为UI准备数据。ViewModel对象在配置更改期间自动保留,以便它们保存的数据可立即用于下一个activity或fragment实例。例如,如果需要在app中显示用户列表,请确保将获取和保持用户列表的责任分配给ViewModel,而不是activity或fragment,如下面的示例代码所示:

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

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

然后,可以从以下活动访问列表:

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被重新创建,它将接收由第一个活动创建的相同的MyViewModel实例。当所有者activity finished时,框架调用ViewModel对象的onCleared()方法,以便它可以清理资源。

注意:ViewModel绝不能引用view、Lifecycle或任何可能引用了activity上下文的类

ViewModel对象被设计为Lifecycle超出views或LifecycleOwners的特殊的实例。这个设计还意味着你可以编写测试来更容易地覆盖ViewModel,因为它不知道view和Lifecycle对象。ViewModel对象可以包含LifecycleObservers,如LiveData对象。然而,ViewModel对象绝不可能观察到具有生命周期感知变量(如LiveData对象)的变化。如果ViewModel需要应用程序上下文,例如查找系统服务,那么它可以继承AndroidViewModel类,并具有接收Application的构造函数,因为Application类继承了Context

3. ViewModel的生命周期

在获ViewModel时,ViewModel对象被视为Lifecycle传递给ViewModelProvider。ViewModel一直保留在内存中,直到它的作用域永久消失:在activity的情况下,当它finishes时,而在fragment的情况下,当它被detached时。

Android ViewModel详解

图1说明了一个activity的各种生命周期状态,因为它经历了一个旋转,然后finished。该插图还显示了与activity关联的ViewModel的生命周期。这个特殊的图表说明了activity的状态。相同的基本状态适用于fragment的生命周期。

通常,当系统首次调用Activity对象的onCreate()方法时,通常会请求ViewModel。系统可能在活动的整个生命周期中多次调用onCreate(),例如当设备屏幕被旋转时。从你第一次请求ViewModel时,ViewModel一直存在,直到activity完成并销毁。

4. 在fragments之间共享数据

一个activity中的两个或多个fragments需要相互通信是很常见的。设想一个主细节fragments的常见情况,其中有一个fragment,其中用户从列表中选择项,另一个fragment显示所选择的内容。这种情况从来都是很重要的,因为两个fragments都需要定义一些接口描述,并且所有者activity必须将两者绑定在一起。此外,两个fragment都必须处理其他fragment尚未创建或可见的场景。

可以通过使用ViewModel对象来解决这个共同的疼痛点。这些fragments可以使用其activity范围内的共享ViewModel来处理此通信,如下面的示例代码所示:

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

注意,在获取ViewModelProvider时,两个fragments都使用getActivity()。结果,两个fragments都接收到与activity相关的SharedViewModel实例。

这种方法提供了以下好处:

  • activity不需要做任何事情,也不需要知道关于通信的任何事。

  • 除了SharedViewModel契约之外,Fragments不需要互相了解。如果其中一个fragment消失,另一个像往常一样继续工作。

  • 每个fragment都有自己的生命周期,不受另一个生命周期的影响。如果一个fragment替换了另一个fragment,则UI继续工作而不会出任何问题。

5. ViewModel替换Loaders

CursorLoader这样的Loader类经常被用来保持app的UI中的数据与数据库同步。您可以使用ViewModel和其他几个类来替换loader。使用ViewModel将UI控制器与数据加载操作分离,这意味着类之间的强引用更少。

在使用loaders的一种常见方法中,app可能使用CursorLoader来观察数据库的内容。当数据库中的值发生变化时,loader将自动触发数据的重新加载并更新UI:

Android ViewModel详解

图2. loaders加载数据

ViewModel使用RoomLiveData来替换loader。ViewModel能确保数据幸存于设备配置更改。当数据库改变时,Room通知您的LiveData,LiveData又用修改后的数据更新UI。

Android ViewModel详解

图3. ViewModel加载数据

6. 附加资源

这篇博客文章描述了如何使用一个LiveData的ViewModel来代替AsyncTaskLoader

随着数据变得越来越复杂,您可能会选择单独的类来加载数据。ViewModel的目的是封装UI控制器的数据,让数据在配置更改时继续存活。有关如何跨配置更改加载、保存和管理数据的信息,请参阅保存UI状态

Android应用程序体系结构指南建议建立一个仓库类来处理这些功能。

ViewModel是一个Android Jetpack架构组件。在Sunflower演示应用程序中使用了它。