LiveData源码分析,粘性事件,数据倒灌

最近面试天天被虐,有个问题问的很频繁,就是 LiveData 的数据倒灌问题怎么解决。

我不知道有多少人连数据倒灌是什么都没听过的,更不要说什么解决方案啦。

我按照我的理解描述一下数据倒灌:就是设置了 LiveData 的数据之后,再观察 LiveData,这时候拿到的数据是观察之前设置的数据,用比较难懂的说法就是之前设置的数据倒灌过来了。越说越乱了,其实就是一个粘性事件,不管你什么时候观察,都可以拿到最后设置的数据。

举个例子:我把接口返回的错误信息保存到一个 LiveData 中,在当前 Fragment 的 onViewCreated 中绑定,观察到错误信息后弹出了提示框,我关闭了提示框,跳到了另一个 Fragment,然后再返回刚才那个 Fragment,奇迹发生了,它又弹窗了!!!

先分析源码:

以下的代码都是部分关键代码,一些不想看的看不懂的代码我直接删掉了,,,

创建LiveData对象:

// 这样是正常写法吧
val liveData = MutableLiveData<String>()

// 类:LiveData
// 这个是LiveData的构造方法
public LiveData() {
    // mData就是LiveData保存的最后一次更新的数据
    // private volatile Object mData;
    // static final Object NOT_SET = new Object();
    mData = NOT_SET;

    // 这个是LiveData的数据版本号,每一次更新数据版本号都会+1
    // private int mVersion;
    // static final int START_VERSION = -1;
    mVersion = START_VERSION;
}

观察LiveData对象:

// 是这样观察吧
liveData.observe(lifeCycleOwner) { 
    println(it) 
}

// 执行:liveData.observe(lifeCycleOwner) { println(it) }
// 类:LiveData
public void observe(LifecycleOwner owner, Observer<? super T> observer) {
    // 把生命周期和观察者绑定起来,构造方法在下面
    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    
    // 所有观察者保存到mObservers里面
    // private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers = new SafeIterableMap<>();
    // 如果mObservers已经存在wrapper,则返回
    // 如果mObservers不存在wrapper的话,则put进去,返回null
    // 所以最终mObservers里面保存着所有的观察者
    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    
    // 我们这里只考虑第一次添加观察者的情况,直接绑定生命周期
    // 至此,观察者已经添加完毕
    owner.getLifecycle().addObserver(wrapper);
}

// 执行:new LifecycleBoundObserver(owner, observer);
// 类:LifecycleBoundObserver
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
    @NonNull
    final LifecycleOwner mOwner;

    LifecycleBoundObserver(LifecycleOwner owner, Observer<? super T> observer) {
        super(observer);
        mOwner = owner;
    }
}

// 执行:super(observer);
// 类:ObserverWrapper
private abstract class ObserverWrapper {
    final Observer<? super T> mObserver;

    ObserverWrapper(Observer<? super T> observer) {
        mObserver = observer;
    }
}

进入生命周期:

ObserverWrapper里面有一个mActive变量,如果没有进入生命周期,mActive默认是false的。
从人类角度思考的话,就是某个观察者,如果它绑定的生命周期没有进入到STARTED 状态的话,是不会激活的,没激活的话就不会观察到任何东西。
生命周期变化会回调onStateChanged 方法:

// 类:LifecycleBoundObserver(继承LifecycleEventObserver)
public void onStateChanged(@NonNull LifecycleOwner source,
        @NonNull Lifecycle.Event event) {
    // DESTROYED状态,移出观察者
    // 从这里可以看出LiveData不需要手动解除观察者,都是自动的
    Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
    if (currentState == DESTROYED) {
        removeObserver(mObserver);
        return;
    }

    // 这里加了个prevState,确保在状态有变化之后才处理
    Lifecycle.State prevState = null;
    while (prevState != currentState) {
        prevState = currentState;
        // shouldBeActive(),至少是STARTED状态以上才会返回true
        activeStateChanged(shouldBeActive());
        currentState = mOwner.getLifecycle().getCurrentState();
    }
}

// 执行:activeStateChanged(shouldBeActive());
// 类:ObserverWrapper
void activeStateChanged(boolean newActive) {
    // 我们只考虑正常情况,newActive=true
    // mActive默认为false
    if (newActive == mActive) {
        return;
    }
    mActive = newActive;
    if (mActive) {
        // 分发,具体看setValue()部分,传入了具体的观察者
        // 这个代码是在ObserverWrapper里面的,这个this就是观察者,
        // 意思就是向这个观察者分发数据
        dispatchingValue(this);
    }
}

LiveData.setValue():

// kotlin:setValue()
liveData.value = "hello"

// 类:LiveData
protected void setValue(T value) {
    // 每次setValue版本号都会+1,postValue最终也是setValue
    mVersion++;
    // 保存最后更新的数据
    mData = value;
    // 给观察者们分发数据
    // 没有传入具体的观察者,而是传入了null,表示给所有观察者分发
    dispatchingValue(null);
}

// 执行:dispatchingValue(null);
// 类:LiveData
void dispatchingValue(@Nullable ObserverWrapper initiator) {
    if (mDispatchingValue) {
        mDispatchInvalidated = true;
        return;
    }
    mDispatchingValue = true;
    do {
        mDispatchInvalidated = false;
        if (initiator != null) {
            // 如果传入了具体的观察者,则直接调用considerNotify
            // 绑定观察者的时候会马上分发
            considerNotify(initiator);
            initiator = null;
        } else {
            // 没有传入具体的观察者,则遍历mObservers,拿到每一个观察者执行considerNotify
            for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                    mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                considerNotify(iterator.next().getValue());
                if (mDispatchInvalidated) {
                    break;
                }
            }
        }
    } while (mDispatchInvalidated);
    mDispatchingValue = false;
}

// 执行:considerNotify(iterator.next().getValue());
// 类:LiveData
private void considerNotify(ObserverWrapper observer) {
    if (!observer.mActive) {
        return;
    }

    // 我们只考虑分发的情况
    // mLastVersion默认是-1
    // 这里很重要,
    // 观察者每次收到数据后都会把自己的版本号设置成LiveData的版本号
    // 所以当观察者的版本号大于等于LiveData的版本号,
    // 那就说明这个观察者已经处理过这个版本的数据了
    if (observer.mLastVersion >= mVersion) {
        return;
    }

    // 每个观察者也会保存一份自己的版本号
    observer.mLastVersion = mVersion;

    // 至此回调用户定义的观察者,收工
    observer.mObserver.onChanged((T) mData);
}

LiveData.postValue():

// 使用:
liveData.postValue("hello")

// 类:LiveData
protected void postValue(T value) {
    boolean postTask;
    synchronized (mDataLock) {
        // mPendingData默认是NOT_SET
        // static final Object NOT_SET = new Object();
        // volatile Object mPendingData = NOT_SET;
        postTask = mPendingData == NOT_SET;
        mPendingData = value;
    }
    if (!postTask) {
        return;
    }
    
    // 通过hanlder提交到主线程执行(所有需要主线程跑的代码全部都是通过handler提交的)
    // 很多JectPack的库都有用到这个ArchTaskExecutor
    // 我们自己的代码也可以直接用它,也可以给它设置我们自己的线程池
    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}

private final Runnable mPostValueRunnable = new Runnable() {
    public void run() {
        Object newValue;
        synchronized (mDataLock) {
            newValue = mPendingData;
            mPendingData = NOT_SET;
        }
        // 这里回到setValue()的情况
        setValue((T) newValue);
    }
};

粘性事件和数据倒灌

从上面分析可以看到LiveData里面保存的上一次分发的数据mData,这是一个Object对象,并且在分发完毕后不会置空,所以后来的观察者也能观察到这个对象。

解决思路:

不要解决!!!

LiveData 本来就是这么设计的,是用来保存数据的,不是用来分发事件的。

解决思路参考:

1、反射。我看很多博客都说可以用反射,在 observe 的时候反射拿到 LiveData 的版本号,再反射赋值给 Observer 的版本号,但是,从理论上分析我就觉得不可能。首先 LifecycleBoundObserver 是在 observe 方法中生成的,通过反射根本拿不到这个对象。但是可以从 mObservers 中拿到,然而是在 super.observe(owner, observer) 之后才能拿到,这时候已经绑定生命周期并且触发分发了,拿到 Observer 还有什么意义。

2、https://github.com/KunMinX/UnPeek-LiveData 这个库看着可行。大概原理就是,自己定义一个 MyLiveData 和 MyObserver,然后自己维护一份 MyLiveData 和 MyObserver 的版本号,在创建 MyObserver 的时候把 MyObserver 的版本号设置成 MyLiveData 的版本号,在 MyObserver 的 onChanged 方法中判断 MyLiveData 的版本号大于等于 MyObserver 的版本号才执行。和前面反射的原理其实是差不多的,只是不反射了,而是自己维护一套版本号。

3、其他的都不用考虑了。

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

推荐阅读更多精彩内容