Android 结构组件之ViewModel

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

这种方法的好处包括:

  • Activity不需要做任何事也不需要知道关于通信的任何信息。
  • 除了SharedViewModel契约之外,Fragmengt不需要相互了解。如果其中一个消失了另一个就像平常一样继续工作。
  • 每个Fragment都有自己的生命周期,并且不受另一个生命周期的影响。事实上在UI中一个Fragment替换另一个Fragment,UI工作也没有任何问题。

用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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354

推荐阅读更多精彩内容