LiveData解析

1. 简介

长久以来我们都要去了解Activity或者Fragment的生命周期,因为界面的生命周期是我们处理数据的基础,我们需要知道在哪个地方“才能”去做哪些事,但是这些生命周期又是极其繁琐且样板的,一般来说我们只会去处理几个经常出问题的界面,而选择性的“忽视”一些问题。对于整个App来说,生命周期是一个让人有些不安的概念,因为它总会在不经意间给我们弹出几个bug,无论是稳定性还是数据的完整性,这些不应该是我们在开发业务时关心的事情,我们专注的应该是业务本身而不是被生命周期拖累出的bug,生命周期也应该有一套属于自己的封装。

2. 使用

Livedata

3. 优点

  1. UI和数据保持一致,在数据变更之后自动更新UI。
  2. 数据跟随界面(Activity/Fragment)生命周期,数据在可用时更新,在不可用时销毁,弱化生命周期的概念,同时避免组件在不可用时遭到更改而出现崩溃。
  3. 共享数据,可以在多个Fragmnt中共享数据。

4. 原理

4.1. 前置条件
  1. 理解观察者模式
    观察者模式

  2. 理解LifeCycleEventObserver
    MyLifeCycleEventObserver

理解了上述的两个条件,就能猜想到一大半的实现流程。
我们只要明确两个问题:LiveData是在什么时候去绑定生命周期的?观察者的notify又是什么时候触发的?

4.2. 如何绑定Activity的生命周期

我们在调用LiveData的时候调用的是 LiveDta.observe(LifecycleOwner owner,Observer observer),这个方法里的owner 其实就是AppCompatActivity本身。

@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        return;
    }
    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    // 存储Observer,便于后面更改值之后分发事件
    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    if (existing != null && !existing.isAttachedTo(owner)) {
            不能重复添加
    }
    // 使用wrapper,接收界面生命周期的回调,类似于上文(前置条件)中的MyLifeCycleEventObserver
    owner.getLifecycle().addObserver(wrapper);
}

在我们直接使用LiveData时,它已经不经意间绑定了生命周期,有了生命周期其实我们只要找准时机去触发事件就好了。

4.3. 如何更新(分发)数据

思考:我们想要触发界面的更新,即触发Activity中Observer的onChanged方法,需要在哪里调用?

既然上面说到了LifecycleEventObserver,那么我们很自然的去它的实现类去看一下

class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {

    @Override
    boolean shouldBeActive() {
        // 当前生命周期是否在Start之后,包括Start
        return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
    }

    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
            @NonNull Lifecycle.Event event) {
        if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
            // 反注册
            removeObserver(mObserver);
            return;
        }
        // 这里分两层,shouldBeActive其实就是判断当前生命周期是否在Start之后,其次才是active
        activeStateChanged(shouldBeActive());
    }

    void activeStateChanged(boolean newActive) {
        if (newActive == mActive) {
            return;
        }
        mActive = newActive;
        if (mActive) {
            // 可以理解为观察者的notify方法
            dispatchingValue(this);
        }
    }
}

我们保留最简洁的代码,可以直观的看出:

  • Observer在生命周期到Destroy时反注册自己
  • 在每次Start之后,都会触发更新方法,不过使用了标记位来避免重复的执行
    以上就是生命周期绑定的方法,下面我们分析主动的触发
    @MainThread
    protected void setValue(T value) {
        mVersion++;
        mData = value;
        dispatchingValue(null);
    }

setValue的方法更加简单,设置value并且触发分发,和activeStateChanged方法的处理类似,最终调用的更新方法是相同的dispatchingValue(),只是设置的变量不一样(一个是this,一个是null),这也影响了之后的处理逻辑。

void dispatchingValue(@Nullable ObserverWrapper initiator) {
    // 防止重复处理
    if (mDispatchingValue) {
        mDispatchInvalidated = true;
        return;
    }
    mDispatchingValue = true;
    do {
        mDispatchInvalidated = false;
        // 这就是不一样的地方
        if (initiator != null) {
            considerNotify(initiator);
            initiator = null;
        } else {
            for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
            mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                considerNotify(iterator.next().getValue());
                if (mDispatchInvalidated) {
                    break;
                }
            }
        }
    } while (mDispatchInvalidated);
    mDispatchingValue = false;
}

在Google的代码里也少不了这样的Flag方法,虽然代码有点啰嗦,但这是一个简单易行的方案。
initiator(发起人、创始者的意思,AndroidX中变量的名字起的是真好)就是Livedata本身,如果有值直接触发这个Livedata的更新,如果没有则触发所有Observer(但不一定都会调用onChanged方法)。

在dispatchingValue方法中最后会调用considerNotify

// 这个confider也起的好,这些notify不是一定会触发,而是要经过慎重思考的。
private void considerNotify(ObserverWrapper observer) {
    if (!observer.mActive) {
        return;
    }
    if (!observer.shouldBeActive()) {
        observer.activeStateChanged(false);
        return;
    }
    // 这个version也是用来减少重复处理的,每次setValue时都会version++
    if (observer.mLastVersion >= mVersion) {
        return;
    }
    observer.mLastVersion = mVersion;
    // 最后调用Observer的onChanged方法
    observer.mObserver.onChanged((T) mData);
}

5. 总结

LiveData的实现其实不能说是复杂,但是它实现的却很巧妙,简单的使用观察者就能把一直困扰我们的生命周期的问题解决。其实这是一个很好的切入点,我们总是习惯了去做很多事,也许这些事情并不一定需要我们每次都去做,findViewById不是必须的,生命周期的控制不是必须的,数据的设置也不一定是必须的……也许我们应该跳出Android开发的思维,用更省力的角度去解决一些我们已经习以为常的不是问题的问题,也许我们能打开编程的另一扇门。

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