LiveData-原理全解析

LiveData是什么?

LiveData 是 Jetpack 推出的基于观察者的消息订阅/分发的可观察数据组件,具有宿主(Activity、Fragment)生命周期感知能力,这种感知能力可确保 LiveData 仅分发消息给处于活跃状态的观察者,即只有处于活跃状态的观察者才能收到消息。
而LiveData 的事件分发机制,会根据监听者的活跃状态来判断是否分发数据源变化事件,这样的话,我们就能避免当前页面在后台时,响应了事件,做出一些无用的逻辑浪费性能。

LiveData的特征

确保界面符合数据状态

LiveData 遵循观察者模式。当生命周期状态发生变化时,LiveData 会通知 [==Observer==]对象并把最新数据派发给它。观察者可以在收到==onChanged==事件时更新界面,而不是在每次数据发生更改时立即更新界面。

不再需要手动处理生命周期

只需要观察相关数据,不用手动停止或恢复观察。LiveData 会自动管理Observer的反注册,因为它能感知宿主生命周期的变化,并在宿主生命周期的==onDestory==自动进行反注册。因此使用==LiveData==做消息分发不会发生内存泄漏

数据始终保持最新状态

如果宿主的生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。

支持黏性事件的分发

即先发送一条数据,后注册一个观察者,默认是能够收到之前发送的那条数据的

LiveData的几种用法

声明一个LiveData

我们发现LiveData是一个抽象类,它的默认实现子类是MutableLiveData,但是看源码他们没有区别~唯一区别就是set和post方法公开了,之所以这么设计,是考虑到单一开闭原则,只有拿到 MutableLiveData 对象才可以发送消息,LiveData 对象只能接收消息,避免拿到 LiveData 对象时既能发消息也能收消息的混乱使用。

//1.声明一个MutableLiveData
val data = MutableLiveData("Test")

fun main(){
    //2.监听数据源变化
    data.observe(this){ data->
        //...do somethig
    }
}

组合多个LiveData统一观察

当我们有多个LiveData时候,某些场景下我们想统一监听,那这个时候我们可以使用==MediatorLiveData==来对多个LiveData进行统一监听。

//创建两个长得差不多的LiveData对象
LiveData<Integer> liveData1 =  new MutableLiveData();
LiveData<Integer> liveData2 = new MutableLiveData();

 //再创建一个聚合类MediatorLiveData
 MediatorLiveData<Integer> liveDataMerger = new MediatorLiveData<>();
 //分别把上面创建LiveData 添加进来。
liveDataMerger.addSource(liveData1, observer);
liveDataMerger.addSource(liveData2, observer);

Observer observer = new Observer<Integer>() {
  @Override
 public void onChanged(@Nullable Integer s) {
      titleTextView.setText(s);
 }
//一旦liveData或liveData发送了新的数据 ,observer便能观察的到,以便  统一处理更新UI

转换数据

比如我们希望对一个Int值的LiveData在监听里变成String,那么我们可以用到Transformations.map 操作符进行该操作

MutableLiveData<Integer> data = new MutableLiveData<>();

//数据转换
LiveData<String> transformData = Transformations.map(data, input ->   String.valueOf(input));
//使用转换后生成的transformData去观察数据
transformData.observe( this, output -> {

});

//使用原始的livedata发送数据
data.setValue(10);

LiveData核心方法

LiveData事件分发原理

接下来我们将会了解以下几项:

LiveData事件分发原理
为什么监听器在活跃时才收到事件?
反注册是怎么做到的?
粘性事件是怎么产生的?

LiveData注册流程

首先我们先看注册流程,我们可以了解到:

为什么监听器在活跃时才收到事件?
反注册是怎么做到的?
粘性事件是怎么产生的?

先从LiveData中的observe方法开始介绍,首先监听的所在的宿主要实现Lifecycle,然后LiveData会将监听结合宿主的生命周期做到了以上三件事~接着我们来看看源码吧!

 public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        //1.如果注册的时候,监听所在宿主生命周期已经结束了,则不注册
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // ignore
            return;
        }
        //2.将宿主和监听器进行包装,这一步是实现反注册、粘性事件、监听器活跃时收到事件的关键
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
        //3.如果以前已经添加过,则报错
        if (existing != null && !existing.isAttachedTo(owner)) {
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        if (existing != null) {
            return;
        }
        //将包装好的LifecycleBoundObserver开始监听监听器所在宿主的声明周期状态
        owner.getLifecycle().addObserver(wrapper);
    }

从上面源码我们可以看出,LiveData注册的关键在于LifecycleBoundObserver这个类,接着我们继续看看LifecycleBoundObserver做了什么?

    class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
        @NonNull
        final LifecycleOwner mOwner;
        //构造函数
        LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
            super(observer);
            mOwner = owner;
        }
        //... 省略部分代码
    }

我们聚焦构造函数,发现只保存了监听的宿主,那我们继续看看父类做了啥:

    private abstract class ObserverWrapper {
        //保存了监听对象
        final Observer<? super T> mObserver;
        //当前的监听器所在的宿主是否活跃状态
        boolean mActive;
        //版本号,用来对齐LiveData中的版本号用
        int mLastVersion = START_VERSION;

        //构造函数只负责保存了监听器
        ObserverWrapper(Observer<? super T> observer) {
            mObserver = observer;
        }
    
        //判断宿主的状态是否发生改变,如果是活跃状态则根据版本号判断是否走事件分发逻辑
        void activeStateChanged(boolean newActive) {
            if (newActive == mActive) {
                return;
            }
            // immediately set active state, so we'd never dispatch anything to inactive
            // owner
            mActive = newActive;
            changeActiveCounter(mActive ? 1 : -1);
            //如果是监听者的宿主正活跃,则进行事件分发(这里是活跃时才收到回调的关键实验)
            if (mActive) {
                dispatchingValue(this);
            }
        }
    }
    

可以看出来,构造函数好像只是为了保存监听器而已,那么既然LifecycleBoundObserver同样也是LifecycleObserver,那么我们判断,逻辑的开始应该在生命周期的事件回调中,事不宜迟,我们go:

    class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
        @NonNull
        final LifecycleOwner mOwner;
 
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
            //1.判断当前监听所在宿主的状态是否已经销毁
            if (currentState == DESTROYED) {
                //1.1 如果目前宿主已经销毁,则进行反注册(这里是反注册的关键)
                //因为是内部类,所以调用removeObserver则会调到去外部类LiveData的函数
                removeObserver(mObserver);
                return;
            }
            Lifecycle.State prevState = null;
            //2.这里会进行至少一次的状态对齐
            while (prevState != currentState) {
                prevState = currentState;
                //调用该函数意义在于判断状态有没变化,如果有变化则走事件分发逻辑(这里是粘性事件的关键)
                activeStateChanged(shouldBeActive());
                currentState = mOwner.getLifecycle().getCurrentState();
            }
        }
        //...省略部分代码
    }

注册的流程基本上看完了,接着我们用文字来描述一下,将一个Observer注册到LiveData会经历了什么?

首先会将Observer与其宿主包装成一个WrapObserver,继承自LifecycleObserver
宿主将WrapObserver注册到Lifecycle监听自身状态,此时会触发Lifecycle的事件回调
判断监听的宿主当前状态,是否已经销毁,如果是的话则进行==反注册==
如果不是,则进行至少一次的状态对齐,如果当前监听的宿主是活跃的则继而触发事件分发逻辑
如果版本号不一致,则进行触发监听器,同步数据源(粘性事件)

从上面的流程,大家也应该明白了以下原理:

反注册的逻辑
活跃时候分发事件的逻辑
粘性事件是怎么诞生的

LiveData事件分发流程

那么,注册的过程算是说完了,接着要说事件是怎么分发的了,就是通过setValue/postValue更新数据源后,会发生什么,我们先看看postValue

postValue

postValue自带切换主线程的能力,我们看看postValue是怎么进行线程切换的:

public abstract class LiveData<T> {
    static final Object NOT_SET = new Object();
    
    //用于线程切换时,暂存Data用的变量
    volatile Object mPendingData = NOT_SET;
 
    protected void postValue(T value) {
        boolean postTask;
        synchronized (mDataLock) {
            //1.判断是否为一次有效的postValue
            postTask = mPendingData == NOT_SET;
            //2.暂存对象到mPendingData中
                mPendingData = value;
        }
        //3.过滤无效postValue
        if (!postTask) {
            return;
        }
        //4.这里通过ArchTaskExecutor切换线程,其实ArchTaskExecutor切换线程的核心靠一个掌握着MainLooper的Handler切换,这里不展开说了
        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
    }
    
    //用于给Handler执行切换线程的Runnable
        private final Runnable mPostValueRunnable = new Runnable() {
        @SuppressWarnings("unchecked")
        @Override
        public void run() {
            Object newValue;
            synchronized (mDataLock) {
                //5.从缓存中获得Data
                newValue = mPendingData;
                mPendingData = NOT_SET;
            }
            //6.通过setValue设置数据
            setValue((T) newValue);
        }
    };
}

通过postValue的源码我们可以得知,postValue主要的责任是利用ArchTaskExecutor将线程切换到主线程,最后通过setValue设置数据, 那么我们就去看看setValue的具体实现吧~最后一步啦!

setValue(T data)

事件分发的关键函数,我们通过setValue能看到整个分发的机制是怎么样的:

   @MainThread
    protected void setValue(T value) {
       //1.判断当前是否主线程,如果不是则报错(因为多线程会导致数据问题)
        assertMainThread("setValue");
       //2.版本号自增
        mVersion++;
       //3.数据源更新
        mData = value;
       //4.分发事件逻辑
        dispatchingValue(null);
    }
    
    //事件分发的函数
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    void dispatchingValue(@Nullable ObserverWrapper initiator) {
    //5.判断是否分发中,如果是的话,忽略这次分发
        if (mDispatchingValue) {
            mDispatchInvalidated = true;
            return;
        }
        //6.设置表示
        mDispatchingValue = true;
        do {
            mDispatchInvalidated = false;
            //7.判断参数中,有没指定Observer,如果有则只通知指定Observer,没有的话则遍历全部Observer通知
            if (initiator != null) {
                //7.0 
                considerNotify(initiator);
                initiator = null;
            } else {
                //7.1 遍历全部Observer通知更新
                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }
    
    //分发函数
    private void considerNotify(ObserverWrapper observer) {
        //8. 判断如果当前监听器不活跃,则不分发
        if (!observer.mActive) {
            return;
        }
        //9. 二次确认监听器所在宿主是否活跃,如果不活跃,则证明Observer中的mActive状态并非最新的,调用activeStateChanged更新状态
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        //10. 判断版本号是否一致,如果一致则不需要分发
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        //11. 对齐版本号
        observer.mLastVersion = mVersion;
        //12. 通知监听器
        observer.mObserver.onChanged((T) mData);
    }

以上就是LiveData中事件分发的逻辑原理,通过注释加序号解释了整个分发流程,如果看不明白的话可以重新多看几次,理解LiveData的事件分发逻辑是怎么样的。

来自:https://juejin.cn/post/7119043022997684254

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

推荐阅读更多精彩内容