ViewModel概述

ViewModel类旨在以一种有生命周期意识的方式存储和管理与UI相关的数据。
ViewModel类允许数据在配置变化(例如屏幕旋转)后存活。
注意:要将ViewModel导入到Android项目中,请参阅向项目添加组件

Android framework管理UI控制器的生命周期,例如Activity和Fragment。
framework可能会决定销毁或重新创建UI控制器,以响应完全不受您控制的特定用户操作或设备事件。

如果系统销毁或重新创建UI控制器,则存储在其中的任何临时UI相关的数据都将丢失。例如,您的应用可能包含其中一项活动中的用户列表。当为配置更改重新创建活动时,新活动必须重新获取用户列表。
对于简单的数据,Activity可以使用onSaveInstanceState()方法并从onCreate()中的bundle中恢复其数据,但此方法仅适用于可以序列化然后反序列化的少量数据,可能不适合像用户或位图的列表这样的大量数据。

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

UI控制器(如Activity和Fragment)主要用于显示UI数据,对用户操作做出反应或处理操作系统通信(如权限请求)。如果要求UI控制器也负责从数据库或网络加载数据,就会使改类变得臃肿。为UI控制器分配过多的责任可能会导致一个类尝试单独处理应用程序的所有工作,而不是将工作委托给其他类。通过这种方式给UI控制器分配过多的责任也使测试变得更加困难。

将视图数据所有权从UI控制器逻辑中分离出来更简单,更高效。

实现一个ViewModel

架构组件为UI控制器提供ViewModel助手类。ViewModel对象在配置更改期间会自动保留,以便它们保存的数据立即可用于下一个Activity或fragment实例。例如,如果您需要在应用中显示用户列表,请明确分配职责来获取数据并将用户列表保存到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.
    }
}

然后你可以从一个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 finish后,框架将调用ViewModel对象的onCleared()方法,以便它可以清理资源。

警告:ViewModel绝不能引用视图,生命周期或可能持有对活动上下文的引用的任何类。

ViewModel对象被设计为脱离视图或LifecycleOwners的特定实例。这种设计还意味着您可以更轻松地编写测试来覆盖ViewModel,因为它不知道视图和生命周期对象。ViewModel对象可以包含LifecycleObservers,例如LiveData对象。但是,ViewModel对象绝不能观察对生命周期感知的可观察对象(如LiveData对象)的更改。如果ViewModel需要应用程序上下文(例如查找系统服务),那么它可以扩展AndroidViewModel类并具有构造函数,该构造函数在构造函数中接收Application,因为Application类扩展了Context。

ViewMode的生命周期

ViewModel对象的范围是在获取ViewModel时传递给ViewModelProvider的生命周期。ViewModel保留在内存中,直到生命周期的范围永久消失:在一个Activity的情况下,finish()时,在一个Fragment的情况下,当它被detached(分离)时。

图1说明了一个Activity在进行一次旋转然后finish后的各种生命周期状态。该图还显示了相关Activity生命周期旁边ViewModel的生命周期。这个特定的图表说明了一个Activity的状态。这些相同的基本状态同样适用于Fragment的生命周期。
图片描述

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

在片段之间共享数据

Activity中的两个或更多fragment需要彼此进行通信是很常见的。想象一下,主-从关系的F让给met的一种常见情况,其中有一个Fragment,用户从列表中选择一个项目,另一个fragment显示所选项目的内容。这种情况有些麻烦,因为这两个片段都需要定义一些接口描述,并且所有者Activity必须将两者绑定在一起。此外,这两个fragment必须处理其他fragment尚未创建或可见的场景。

可以使用ViewModel对象解决这个常见的痛点。这些fragment可以使用其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时,这两个Fragment都使用getActivity()。因此,两个Fragment 都接收相同的SharedViewModel实例,该实例的范围限定为Activity。
这种方法具有以下优点:

  • Activity不需要做任何事情,也不需要了解这种沟通。
  • 除了SharedViewModel约定之外,fragment不需要彼此了解。如果其中一个fragment消失,另一个fragment继续照常工作。
  • 每个片fragment都有其自己的生命周期,并且不受其他生命周期的影响。如果一个fragment替换另一个fragment,UI将继续工作而不会出现任何问题。

用ViewModel替换Loaders

CursorLoader这样的Loader类经常用于保持应用程序UI中的数据与数据库同步。您可以使用ViewModel和其他几个类来替换Loaders。使用ViewModel将您的UI控制器与数据加载操作分开,这意味着您在类之间的强引用减少了。

在使用loaders的一种常见方法中,应用程序可能使用CursorLoader来观察数据库的内容。当数据库中的值发生更改时,加载程序会自动触发重新加载数据并更新UI:
图片描述
图2.使用加载器加载数据

ViewModel与Room和LiveData一起使用来替换Loaders。ViewModel可确保数据在设备配置更改后仍然存在。当数据库发生更改时,Room会通知您的LiveData,而LiveData则会用修改的数据更新您的UI。
图片描述
图3.使用ViewModel加载数据

此博客文章描述了如何将ViewModel与LiveData一起使用来替换AsyncTaskLoader

随着你的数据变得越来越复杂,你可能会选择一个单独的类来加载数据。ViewModel的目的是封装UI控制器的数据,以使数据不受配置更改的影响。有关如何跨配置更改加载,保留和管理数据的信息,请参阅保存UI状态

Android App Architecture指南建议构建一个存储库类来处理这些功能。


杨旭
189 声望24 粉丝

专注,摩羯