Android ViewModel详解

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时。

image

图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:

image

图2. loaders加载数据

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

image

图3. ViewModel加载数据

6. 附加资源

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

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

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

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

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值