Android 结构组件之ViewModel

2017-06-07  本文已影响1806人  Ggx的代码之旅

ViewModel

ViewModel类设计用来存储和管理与UI相关的数据。这样数据就可以在配置更改中保存,比如屏幕旋转。

注:如何添加ViewModel依赖可以查看Android 结构组件之Adding Components to your Project

Android框架管理着ActivityFragment的生命周期。框架可能会根据一些完全超出您控制的用户操作或设备事件来决定销毁或重新创建它们。

如果系统销毁或者重创建UI控制器,那么存储在其中的任意UI相关的数据都将丢失。例如,如果你在Activity中有一组用户列表,当因为配置改变而被重新创建后,新的Activity必定会重新去获取用户列表。对于简单的数据,Activity可以使用onSaveInstanceState()方法在存储数据,并且可以在onCreate()方法的bundle参数中恢复。但是这种方法只适用于像UI状态这样的少量数据,而不是像用户列表那样的潜在的大量数据。

另一个问题是,这些控制器(activity和fragment)经常需要进行一些异步调用,这些调用可能需要一些时间才能返回结果。UI控制器需要管理这些调用,并在销毁时清除它们,以避免潜在的内存泄漏。这需要大量的维护,并且在为配置更改重新创建对象的情况下,由于需要重新发出相同的调用,这是对资源的浪费。

最后但同样重要的是,UI控制器(如Activity和Fragment)主要用于显示UI数据、对用户行为作出反应或处理操作系统通信,例如权限请求。要求UI控制器还负责从数据库或网络加载数据,增加了对类的膨胀。对UI控制器分配过多的责任可能导致单个类试图独自处理一个应用程序的所有工作,而不是将工作委托给其他类。以这种方式向UI控制器分配过度的责任也会使测试变得更加困难。

实现一个ViewModel

将视图数据所有权与UI控制器逻辑分离是更容易、更高效的。Lifecycle提供了一个新的叫做ViewModel的类。它是UI控制器的助手类,负责为UI准备数据。在配置更改期间,ViewModel会自动保留,这样它所保存的数据就会立即被下一个ActivityFragment实例所使用。在我们上面提到的例子中,应该是ViewModel负责获取和保存用户列表的责任,而不是FragmentActivity

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

    private void loadUsers() {
        // do async operation to fetch users
    }
}

现在Activity可像这样访问数据:

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}

如果这个Activity被重新创建,它将或获得由上一个被销毁的Activity所创建的相同的MyViewModel实例,当Activity被执行了finish()以后,框架会调用ViewModelonCleared()方法,这样就可以清楚一些资源。

注:由于ViewModelActivityFragment的实例要活的长,它不应该引用一个View,或者任何一个可能持有Activity的上下文引用的类。如果ViewModel需要引用Application的上下文(如,开启系统的服务),它可以继承AndroidViewModel类,此类有一个构造器接收Application(因为Application类继承自Context).

ViewModel的设计是超出LifecycleOwen生命周期的,这个设计还意味着您可以编写测试来更容易地覆盖ViewModel,因为它知道关于视图和生命周期对象。ViewModel能够包含LifecycleObservers,例如LiveData对象。然而,ViewModel对象绝不能对生命周期敏感的可观察对象(如LiveData对象)做更改。

ViewModel的生命周期

ViewModel对象在获取视图模型时,将范围限定为传递给ViewModelProvider的生命周期。ViewModel保留在内存中,直到它的作用域永久消失:例如当Activity 被finish或者当Fragment被detached。
下图说明的了一个Activity的不同的生命状态,例如它经过设备旋转并被销毁。插图还显示了与关联的Activity生命周期相邻的ViewModel的生命周期。这个图表说明了一个活动的状态,相同的基本状态适用于Fragment的生命周期。


viewmodel-lifecycle.png

通常,在系统调用活动对象的onCreate()方法时,您通常会请求一个ViewModel。系统可以在活动的整个生命周期中多次调用onCreate(),比如当设备屏幕旋转时。ViewModel存在于您第一次请求ViewModel时,直到活动完成并销毁为止。

在多个Fragment间共享数据

Activity中的多个Fragment需要通信这是很常见的。想象一个常见的主细节片段,其中有一个Fragment,用户从列表中选择一个项目,另一个Fragment显示所选项目的内容。这绝不是琐碎的因为两个Fragmengt需要定义一些接口描述,而Activity必须要将他们捆绑在一起才行。此外,两个Fragment都必须处理另一个尚未创建或不可见的情况。

这中常见的痛点可以通过使用ViewModel对象来解决。设想一个常见的主-从Fragment,其中有一个Fragment用户从一个列表中选择一项,而另一个Fragment来显示所选项的内容。
这些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.
        });
    }
}

注意两个Fragment通过使用getActivity()方法来获取ViewModelProviders,这意味着它们都将接收相同的SharedViewModel实例,该实例的作用域为Activity

这种方法的好处包括:

用ViewModel替换Loaders

像CursorLoader这样的Loader类经常被用来在一个应用程序的UI中保存数据与数据库同步。你也可以使用ViewModel或者其他的类来替换Loader,使用ViewModel将UI控制器与数据加载操作分开,这意味着类之间的强引用更少。
在使用Loader的的一种常见方法,应用程序可能会使用CursorLoader来观察数据库的内容。当数据库中的值发生变化时,加载器将自动触发数据的重新加载并更新UI:

viewmodel-loader.png
ViewModel使用RoomLiveData来替换加载器。ViewModel确保数据在设备配置更改中得以保存。当数据库发生变化时,Room通知您的LiveData,而LiveData反过来用修改后的数据更新UI。
viewmodel-replace-loader.png
这篇博客描述了如何使用带有LiveDataViewModel去替换AsyncTaskLoader
随着您的数据变得越来越复杂,您可能会选择一个单独的类来加载数据。ViewModel的目的是封装UI控制器的数据,以便在配置更改时让数据存活。有关如何跨配置更改加载、保存和管理数据的信息,请参阅UI状态保存
欢迎共同探讨更多安卓,java,c/c++相关技术QQ群:392154157
上一篇下一篇

猜你喜欢

热点阅读