Android MVVM 解读 3. Android MVVM 介绍(2) LiveData

2.3 LiveData

官方介绍LiveData Overview

包含

  1. LiveData的设计理念

  2. LiveData的优点

  3. 如何使用LiveData

  4. 转换LiveData

  5. 合并LiveData数据源

  6. 额外资源 demos+blogs+videos

添加lifecycle到工程中

2.3.1 理解

官方解释

  1. LiveData是可被观察的数据持有对象. 不像普通的被观察者,LiveData是对生命周期感知的, 意味着这个对象,会感知和遵守其他的应用组件的生命周期,像Activity,fragments,services. 这种对生命周期状态感知的组件,保证了app的这些观察者的组件,在处理时,都是处在有效的状态.
  2. LiveData考虑观察者,观察者用Observer表示, 在生命周期状态中,处于STARTED或者RESUMED状态的组件是处于激活状态,LiveData只有通知active的observer,那些inactive的观察者,不会被通知.
  3. 在注册观察者时,需要把其对应的或者感兴趣的LifeCycleOwner携带着. 在这个配对的Lifecycle处于DESTROYED的状态时,观察者会被移除掉.这种方式对于activity和fragment非常有用,因为有效避免了由于没有反注册导致的内存泄漏

官方解释 : 使用LiveData的优点

  1. UI与数据状态匹配,不会在UI处于非活跃状态时的动态更新,仅有在活跃状态的observer才会被通知
  2. 无内存泄漏
  3. 处于停滞状态的activities不会crash
  4. 不再需要人工的维护监听组件的生命周期
  5. 数据会实时更新,例如: 页面从后台到前台时,如果数据有更新,会马上体现出来
  6. 恰到的页面的configuration changes, 页面方向等更改后,observer会马上获取到最新的数据
  7. 适当的集成LiveData可以让我们在app内部共享数据

个人理解

  1. 之前,我们在使用观察者时,是比较简单的, Observable 和Observer, 两者之间结合, 而这样使用时,我们除了给Observable的对象添加Observer外,还需要解除绑定.使用起来并不方便.
  2. 在大前端中,这些Observer一般是要带来UI的更新的, 但是在注册后,在activity或者fragment处于后台时,这些状态是不应该更新的.而从后台切换到前台时,又需要将数据更新到前端.
  3. 根据上面描述的两种情况, 我们要可以用一种数据,是对生命状态感知的, 因而可以结合Lifecycle或者说是LifecycleOwner.

具体的案例,请查看Google的官方文档

2.3.2 类图

android-mvvm-livedata.png

LiveData 常用的类

  1. LiveData<T>:基础类, 支持了observe时,和生命周期绑定的方式, 并且也支持无生命周期的绑定, observeForever, 另外,可以remove这些observer, 无论是和生命周期绑定的方式还是observeForever的observer.
    特别注意:

    • onActive方法和InActive的方法,当Observer的状态至少有一个是STARTED的状态时,才是onActive的状态, 如果没有一个是STARTED的,那么是InActive; 另外, observeForever的类在注册时,便是Active的状态.
  2. MutableLiveData: 表示此对象可以更改, 可以通过返回值设置值

  3. MediatorLiveData: 可以将多个数据源合并为一个数据源, 比较常见的是使用在LiveDataA变化时, 需要再次处理后,返回LiveDataB, 常用的工具类: Transformations的map和switchMap 查看案例, 案例查看后,分析源码
    <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
    @NonNull final Function<X, Y> func) 的源码

         /**
          * Applies the given function on the main thread to each value emitted by {@code source}
          * LiveData and returns LiveData, which emits resulting values.
          * <p>
          * The given function {@code func} will be executed on the main thread.
          * <p>
          * Suppose that you have a LiveData, named {@code userLiveData}, that contains user data and you
          * need to display the user name, created by concatenating the first and the last
          * name of the user. You can define a function that handles the name creation, that will be
          * applied to every value emitted by {@code useLiveData}.
          *
          * <pre>
          * LiveData<User> userLiveData = ...;
          * LiveData<String> userName = Transformations.map(userLiveData, user -> {
          *      return user.firstName + " " + user.lastName
          * });
          * </pre>
          *
          * @param source a {@code LiveData} to listen to
          * @param func   a function to apply
          * @param <X>    a type of {@code source} LiveData
          * @param <Y>    a type of resulting LiveData.
          * @return a LiveData which emits resulting values
          */
         @MainThread
         public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
                 @NonNull final Function<X, Y> func) {
             final MediatorLiveData<Y> result = new MediatorLiveData<>();
             result.addSource(source, new Observer<X>() {
                 @Override
                 public void onChanged(@Nullable X x) {
                     result.setValue(func.apply(x));
                 }
             });
             return result;
         }
    

方法的目的是将LiveData的X数据,经过Function<X, Y>后返回的是LiveData<Y> 数据,但是方法转换的返回值是LiveData<Y>的数据部分Y, LiveData<X>的变化,需要被检测到,然后应用func的执行方法,得到数据结构.而实现检测source的部分,便是通过MediatorLiveData类实现.

***<X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
@NonNull final Function<X, LiveData<Y>> func) ***

    /**
     * Creates a LiveData, let's name it {@code swLiveData}, which follows next flow:
     * it reacts on changes of {@code trigger} LiveData, applies the given function to new value of
     * {@code trigger} LiveData and sets resulting LiveData as a "backing" LiveData
     * to {@code swLiveData}.
     * "Backing" LiveData means, that all events emitted by it will retransmitted
     * by {@code swLiveData}.
     * <p>
     * If the given function returns null, then {@code swLiveData} is not "backed" by any other
     * LiveData.
     *
     * <p>
     * The given function {@code func} will be executed on the main thread.
     *
     * <p>
     * Consider the case where you have a LiveData containing a user id. Every time there's a new
     * user id emitted, you want to trigger a request to get the user object corresponding to that
     * id, from a repository that also returns a LiveData.
     * <p>
     * The {@code userIdLiveData} is the trigger and the LiveData returned by the {@code
     * repository.getUserById} is the "backing" LiveData.
     * <p>
     * In a scenario where the repository contains User(1, "Jane") and User(2, "John"), when the
     * userIdLiveData value is set to "1", the {@code switchMap} will call {@code getUser(1)},
     * that will return a LiveData containing the value User(1, "Jane"). So now, the userLiveData
     * will emit User(1, "Jane"). When the user in the repository gets updated to User(1, "Sarah"),
     * the {@code userLiveData} gets automatically notified and will emit User(1, "Sarah").
     * <p>
     * When the {@code setUserId} method is called with userId = "2", the value of the {@code
     * userIdLiveData} changes and automatically triggers a request for getting the user with id
     * "2" from the repository. So, the {@code userLiveData} emits User(2, "John"). The LiveData
     * returned by {@code repository.getUserById(1)} is removed as a source.
     *
     * <pre>
     * MutableLiveData<String> userIdLiveData = ...;
     * LiveData<User> userLiveData = Transformations.switchMap(userIdLiveData, id ->
     *     repository.getUserById(id));
     *
     * void setUserId(String userId) {
     *      this.userIdLiveData.setValue(userId);
     * }
     * </pre>
     *
     * @param trigger a {@code LiveData} to listen to
     * @param func    a function which creates "backing" LiveData
     * @param <X>     a type of {@code source} LiveData
     * @param <Y>     a type of resulting LiveData
     */
    @MainThread
    public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
            @NonNull final Function<X, LiveData<Y>> func) {
        final MediatorLiveData<Y> result = new MediatorLiveData<>();
        result.addSource(trigger, new Observer<X>() {
            LiveData<Y> mSource;

            @Override
            public void onChanged(@Nullable X x) {
                LiveData<Y> newLiveData = func.apply(x);
                if (mSource == newLiveData) {
                    return;
                }
                if (mSource != null) {
                    result.removeSource(mSource);
                }
                mSource = newLiveData;
                if (mSource != null) {
                    result.addSource(mSource, new Observer<Y>() {
                        @Override
                        public void onChanged(@Nullable Y y) {
                            result.setValue(y);
                        }
                    });
                }
            }
        });
        return result;
    }

比较多的应用switchMap的场景是: LiveData A变了, 但是不能从A直接获取到结果数据,需要根据A再次获取到新的LiveDataB,B是可用的结果数据.

但是在实现时, 因为A变了,获取到新的LiveDataB,但是LiveDataB可能是一个变量,因而B是直接不能使用的, 是一个中间变量, 需要引入新的变量,作为结果, 因而根据B生成C作为一个稳定的引用变量, LiveDataA是入参中的trigger, LiveDataB,是实现中的mSource, 而C是实现中的result, 因为trigger的变化和mSource的变化都会带来结果的更改,所以result监听这trigger和mSource,trigger变化时,重新调用func, 生成新的mSource; 而mSource自身也可能变化,自身变化时, 便直接设置给结果result即可

数据源的变化的总结

LiveData Practice.png

2.3.3 总结

LiveData 巧妙的结合了Lifecycle, 使其可感知生命周期, 并且自身可被观察, 使其拥有了三个特性

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

推荐阅读更多精彩内容