一文读懂LiveData

理解结论

定义

本质是一种具有声明周期感知能力的可观察的数据存储器类

优势

  • 确保页面符合状态
  • 不会发生内存泄漏
  • 不会因Activity停止而崩溃
  • 不需要手动处理生命周期
  • 数据始终保持最新态
  • 适应配置更改,因配置更改而重建的Activity会拿到最新的数据
  • 共享资源,单利LiveData使用场景下

活跃态

STARTED和RESUMED认为是活跃态


如何使用

基本使用

//创建LiveData对象,一般数据层会在ViewModel中声明
    private val testLiveData: MutableLiveData<Int> = MutableLiveData()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //注册观察者,一般在onCreate里
        testLiveData.observe(this){value->
            Log.i("TempActivity",value.toString())
        }
        //主线程改变值
        testLiveData.value = 1
        lifecycleScope.launch(Dispatchers.IO){
            delay(3000)
            //子线程改变值
            testLiveData.postValue(2)
        }
    }

组合使用

  • LiveData 结合单利
  • LiveData + Room
  • LiveData + 协程

扩展使用

  1. 活跃观察者监听,这个配合单利LiveData,可以完成当有观察者时开启某个服务,没有时取消该服务的操作,示例如下:
class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
    private val stockManager = StockManager(symbol)

    private val listener = { price: BigDecimal ->
        value = price
    }
    //当有活跃的观察者时开启服务,去跟新数据
    override fun onActive() {
        stockManager.requestPriceUpdates(listener)
    }
    //当没有活跃观察者时停止服务减少资源消耗
    override fun onInactive() {
        stockManager.removeUpdates(listener)
    }
}
  1. 使用Transformations 类转换LiveData,类似于RxJava的map和switchMap;应用与在数据分发给观察者时的改变(通过Transformations.map()方法实现)和根据一个数据返回不同LiveData实例(通过Transformations.switchMap()方法实现)的场景。
  2. 合并多个LiveData,通过MediatorLiveData实现

关键源码

类图

LiveData类图

关键方法

创建对象,注意有两个,但并不是重载构造方法

public LiveData(T value) {
        mData = value;
        //这里会对mVersion+1
        mVersion = START_VERSION + 1;
    }
    public LiveData() {
        mData = NOT_SET;
        //这里不会对mVersion+1
        mVersion = START_VERSION;
    }

设置值setValue和postValue

protected void setValue(T value) {
        assertMainThread("setValue");
        //先加版本号
        mVersion++; 
        //直接赋值,这里并没有做数据是否相同的校验,
        //也就是说及时设置的是同一个值,也会改版本号,并执行dispatchingValue逻辑
        mData = value;
        //发布数据变化
        dispatchingValue(null);
    }
protected void postValue(T value) {
        //是否发送任务
        boolean postTask;
        //对象锁,这种思路我们可以借鉴下,多线程改一个值如何保证其安全统一
        synchronized (mDataLock) {
            //预值是否为初始值,如果是,说明可以改变
            postTask = mPendingData == NOT_SET;
            //将值给预值
            mPendingData = value;
        }
        //如果预值已经不是初始值了,说明上次修改的还没恢复回来,则不能post任务
        if (!postTask) {
            return;
        }
        //满足修改条件,异步去改变任务,这里从方法字面看就是将修改只的操作切到主线程
        //从这里看一看出这是在子线程用的方法,以确保最后修改值后的事件通知能到主线程
        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
    }
//异步任务对象定义
private final Runnable mPostValueRunnable = new Runnable() {
        @Override
        public void run() {
            //新值定义
            Object newValue;
           //对象锁锁住赋值操作
            synchronized (mDataLock) {
                //将post是改了的预值给新值
                newValue = mPendingData;
               //将预值恢复为未设置,这里和postValue的逻辑呼应了,确保了线程安全
                mPendingData = NOT_SET;
            }
            //调用setValue修改mData及mVersion并分发变动
            setValue((T) newValue);
        }
    };

分发数据变化的逻辑

void dispatchingValue(@Nullable ObserverWrapper initiator) {
        //如果正在分发数据变化
        if (mDispatchingValue) {
            //则修改分发无效变量为true
            mDispatchInvalidated = true;
            //停止分发
            return;
        }
        //修改正在分发标识为true,标志着进入分发中,分发未完成前不会重复分发
        mDispatchingValue = true;
        do {
            //分发无效标识为false,说明正在进行有效分发,从整体看只有重复分发才会改为true也就是说正常分发一次就会直接结束循环
            mDispatchInvalidated = false;
           //这是观察装饰者,如果观察装饰者,当单纯的改值的时候这个参数为null,当被动监听变化时才不为null
            if (initiator != null) {
               //考虑通知方法,真正的通知逻辑执行处,这里当观察者装饰器不为空时直接通知给当前观察者
                considerNotify(initiator);
                //并将当前观察设置为null
                initiator = null;
            } else {
                //当观察者为null时,也就是不是通知具体某个观察者,而是通知改数据的所有观察者
                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                       mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    //遍历已有的观察者,挨个考虑通知观察者
                    considerNotify(iterator.next().getValue());
                   //如果有一次通知导致分发无效了,则直接终止循环,
                   //这里决定如果有一个地方通知导致无效后会影响后继的观察者收到正确的通知
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        //当分发无效标识为true时会一直循环,从源码看只有重复调用此方法时才会将该值改为true,
        } while (mDispatchInvalidated);
       //结束分发,标识值改为flase
        mDispatchingValue = false;
    }
//考虑通知,也就是不一定通知,接收一个观察者装饰器
private void considerNotify(ObserverWrapper observer) {
        //如果观察者属于当前非活跃态则不发给它了
        if (!observer.mActive) {
            return;
        }
        //如果观察者不应该是活跃态了,这里的具体实现其实是 isAtLeast(STARTED)
        if (!observer.shouldBeActive()) {
            //调用观察者活跃态变化方法,这个方法本质实现的是当前LiveData的观察者变化逻辑
            observer.activeStateChanged(false);
            //不会分发
            return;
        }
        //到了这里,说明观察者当前处于活跃态,且应该处于活跃态
        //如果当前观察者的最新版本大于等于LiveData数据的当前版本,也就是数据的当前版本低于观察者记录的最新数据版本,也就是当前数据版本比较旧,那则没必要通知
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        //数据有新版本了,更新观察者的版本
        observer.mLastVersion = mVersion;
        //调用观察者的onChanged方法,到这里就通知完了
        observer.mObserver.onChanged((T) mData);
    }

observe的逻辑

 //标记是一个主线程中使用的方法
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        //主线程断言
        assertMainThread("observe");
        //如果当前声明周期的拥有者的状态为销毁,则终止
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            return;
        }
        //将观察者进行装饰,这个装饰者其实大有文章
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        //下面这个判断主要是防止不同的生命周期拥有者不能添加相同的观察者,
        //这里决定了如果出现复杂的观察者需要封装时,切莫做成单利或者多个Activity,Fragment公用一个观察者
        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
        if (existing != null && !existing.isAttachedTo(owner)) {
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        //没异常的话,且已经添加过了,则没必要添加了
        if (existing != null) {
            return;
        }
       //都合理则将观察者的装饰器作为生命周期响应的观察者,
       //结合LifeCycle的粘性事件特性,当生命周期状态变化时也会响应变化,
       //如果这时候数据版本发生变化的话,就会最终走到onChanged方法,造成数据倒灌
        owner.getLifecycle().addObserver(wrapper);
    }

观察者装饰类,一些核心思想的逻辑所在处

//这是观察者装饰类的抽象类,主要实现了针对活跃观察者的逻辑
private abstract class ObserverWrapper {
        //被装饰的观察者
        final Observer<? super T> mObserver;
        //是否为活跃态
        boolean mActive;
        //最新版本,这里可以看到初始时与mVersion的初始值是一样的
        int mLastVersion = START_VERSION;
        //构造方法
        ObserverWrapper(Observer<? super T> observer) {
            mObserver = observer;
        }
        //是否应当是活跃态,这里是个抽象,因为有两种实现,一个是正常的,根据atLast方法实现,
        //另一个是Always,用在observeForever的逻辑里,具体实现是始终返回true
        abstract boolean shouldBeActive();
        //是否关联到owner,在判断同观察者绑定到不同生命周期拥有者的时候使用
        boolean isAttachedTo(LifecycleOwner owner) {
            return false;
        }
        //解除观察者的关联
        void detachObserver() {
        }
       //很关键的方法,当活跃态发生变化时调用
        void activeStateChanged(boolean newActive) {
            //如果新的活跃态与当前的活跃态一直,则不会继续执行
            if (newActive == mActive) {
                return;
            }
            //修改当前活跃态为目标活跃态
            mActive = newActive;
            //当前LiveData的活跃态观察者数量为0时视为不活跃态
            boolean wasInactive = LiveData.this.mActiveCount == 0;
            //LiveData活跃态观察者数量基于当前活跃态进行+ - 1
            LiveData.this.mActiveCount += mActive ? 1 : -1;
           //LiveData没有活跃态观察者,本次变为活跃态时,也就是第一次有活跃态时,回调onActive,
            //这里时LiveData可以根据有无活跃态执行特定逻辑的功能的出处
            if (wasInactive && mActive) {
                onActive();
            }
            //LiveData的活跃态为0了,且本次更新变为了不活跃,说明一个活的都没有了,执行onInactive()
            if (LiveData.this.mActiveCount == 0 && !mActive) {
                onInactive();
            }
            //如果最新是活跃态,则通知当前观察者执行数据变化发送
            //可以看到,当观察者活跃态变为活跃态时,会去发一下最新的数据变化通知
            //这里就是数据倒灌的直接上层原因,如果一个观察者变为活跃态之前,有了数据变化,主要是导致了mVersion++的话,观察者变为活跃态时自然就会收到最新的数据
            //这里之所以说是直接原因,是因为要走到这里必须要自动监听到状态变化才行
            //这里也是LiveData能自动接收最新数据变化功能的原因
            if (mActive) {
                dispatchingValue(this);
            }
        }
    }
//observe()方法时需要创建的观察者装饰器的实现,
//这里要注意,除了是一个ObserverWrapper的实现外,同时还实现了LifecycleEventObserver接口
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
         //生命周期拥有者
        @NonNull
        final LifecycleOwner mOwner;
        LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
            super(observer);
            mOwner = owner;
        }
         //这个是应当活跃的实现,可以看到本质是isAtLeast,也就是STARTED和RESUMED状态视为活跃态
        @Override
        boolean shouldBeActive() {
            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
        }
        //这里是LifecycleEventObserver的实现,这个方法当生命周期状态变化时作为生命周期的观察者会执行此方法,
         //还有addObserver时会执行此方法,这个方法的执行其实就是数据倒灌的根级触发点
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
                //如果是销毁态,直接移除当前观察者,
                //这里就是为啥LiveData添加的观察不会导致内存泄漏的原因了,
                //因为到这个状态前,是在onDestroy前,也就是onDestroy前就会自动移除无效的观察者了
                removeObserver(mObserver);
                return;
            }
            //灵魂入口,调用父类的活跃态变化方法
            activeStateChanged(shouldBeActive());
        }
        //是否关联到声明周期拥有者,其实就是新建的和老的是不是一个生命周期拥有者
        @Override
        boolean isAttachedTo(LifecycleOwner owner) {
            return mOwner == owner;
        }
        // 解除生命周期观察者实现
        @Override
        void detachObserver() {
            mOwner.getLifecycle().removeObserver(this);
        }
    }

总结点东西

粘性事件的原因

LifecycleRegistry-->addObserver-->statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));-->mLifecycleObserver.onStateChanged(owner, event);

数据倒灌的原因

LiveData--observe-->owner.getLifecycle().addObserver(wrapper)-->LifecycleBoundObserver-->onStateChanged(owner, event)-->activeStateChanged(shouldBeActive())-->dispatchingValue(@Nullable ObserverWrapper initiator)-->considerNotify(ObserverWrapper observer)-->observer.mObserver.onChanged((T) mData);

数据倒灌的解决方案
  1. 利用打标记的方式,第一次执行observe时不触发onChanged方法,这也是UnPeekLiveData的思路
  2. 利用新建如果不传值的方式结合中介LiveData思路,将行为给一个空的LiveData,因为空的时数据Version是一致的不会触发onChanged
数据倒灌方案的应用场景

防止数据倒灌,只是防止首次observe的场景,这有时候会与LiveData观察即可拿到最新数据的设计不符,需要基于实际情况使用

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

推荐阅读更多精彩内容