Jetpack系列组件--LiveData从相遇到相知

一、什么是LiveData
  • LiveData组件是Jetpack新推出的基于观察者的消息订阅/分发组件,具有宿主(Activity、Fragment)生命周期感知能力,这种感知能力可确保 LiveData 仅分发消息给处于活跃状态的观察者,即只有处于活跃状态的观察者才能收到消息。

  • LiveData的消息分发机制,是以往的HandlerEventBusBroadcastReceiver无法比拟的,它们不会顾及当前页面是否可见,一股脑的有消息就转发。导致即便应用在后台,页面不可见,还在做一些无用的绘制,计算(细心的同学可以发现微信消息列表是在可见状态时才会更新列表最新信息的)。

活跃状态:Observer所在宿主处于可见状态。即onStart、onCreate、onResume

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

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

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

  • 支持黏性事件的分发
    即先发送一条数据,后注册一个观察者,默认是能够收到之前发送的那条数据的

  • 共享资源
    我们可以使用单例模式拓展 LiveData,实现全局的消息分发总线。

三、如何引入LiveData
  //指明使用livedata
  api 'androidx.lifecycle:lifecycle-livedata:2.1.0'
  api 'androidx.lifecycle:lifecycle-livedata-core:2.1.0'
 
  //或者使用聚合依赖,包含了 viewmodel 和 livedata
  api 'androidx.lifecycle:lifecycle-extensions:2.1.0'

四、LiveData衍生类
  • MutavleLiveData
    • MutableLiveData 直接继承自LiveData,这个类非常简单简单,仅仅是把复写了父类的postValue,setValue方法,把方法权限改成public,这两个方法在父类中是protect修饰的,无法直接调用。

    • 我们在使用LiveData的做消息分发的时候,需要使用这个子类。之所以这么设计,是考虑到单一开闭原则,只有拿到MutableLiveData对象才可以发送消息,LiveData对象只能接收消息,避免拿到LiveData对象时既能发消息也能收消息的混乱使用。

public class MutableLiveData<T> extends LiveData<T> {
    @Override
    public void postValue(T value) {
        super.postValue(value);
    }

    @Override
    public void setValue(T value) {
        super.setValue(value);
    }
}
  • MediatorLiveData
    • 该类顾名思义是LiveData的聚合类。调用addSource方法能够把其他LiveData对象注册进去,有什么好处呢?比如有多个LiveData需要观察同一个事件,常规的写法,我们势必要拿到每一个LiveData对象分别注册一个Observer。
    • 有了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
五、LiveData工作原理源码分析
public abstract class LiveData<T> {
    private static final int START_VERSION = -1;
    private SafeIterableMap<Observer<T>, ObserverWrapper> mObservers =
            new SafeIterableMap<>();
    private int mActiveCount = 0;
    private int mVersion = START_VERSION;
//添加观察者到liveData必须在主线程
   @MainThread
 public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
        //该方法会把传递进来的入参 observer包括成LifecycleBoundObserver
        // LifecycleBoundObserver顾名思义具有生命周期感知的观察者 
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
       //于此同时会把包装之后的观察者添加到LifeOwner中。
       //这句话至关重要,不仅仅是添加到lifeOwner中那么简单。
       //他还会计算当前宿主的生命周期的状态,并把生命周期状态分发给wrapper对象
      // 也就是会触发LifecycleBoundObserver#onStateChanged(LifecycleOwner source, Lifecycle.Event event);方法,我们下面再介绍
        owner.getLifecycle().addObserver(wrapper);
    }

 
//可以发现LiveData的postValue方法是protected修饰的,所以我们无法直接使用.
//必须使用子类MutableLiveData才可以发送数据
//该方法可用于在子线程发送数据,该方法会调用Handler.Post抛到主线程,最终还是调用setValue在主线程上分发数据。
 protected void postValue(T value) {
       // ArchTaskExecutor.getInstance().postToMainThread  这句话实际上就是拿到内部创建的Handler对象
        ArchTaskExecutor.getInstance().postToMainThread(new runnable()->{
                  setValue((T) newValue);
            });
    }

//该方法同样是protected 修饰,无法直接使用
//该方法也可以用于发送数据,但必须在主线程中应用,方法内部有个线程判断的断言
 @MainThread
 protected void setValue(T value) {
        assertMainThread("setValue");
       // mVersion字段至关重要,该字段的值标记着liveData已经发射了几次数据了,默认为-1
        mVersion++;
        //将本次要发射的数据 赋值给mData字段,临时保存,如果继续发射一条新的数据,该数据将会被覆盖
        mData = value;
        //我们每次调用setValue,最终都会调用dispatchingValue方法把本次数据分发给注册进来的所有的观察者
        dispatchingValue(null);
    }

//该方法用于分发数据给观察者.如果方法入参observer不为空,则只分发给它。
//否则把数据分发给mObservers中已经注册的所有观察者
 private void dispatchingValue(@Nullable ObserverWrapper observer) {
       if (observer != null) {
                //如果入参不为空,那么就只把本次数据分发给他了。
               //这个情况在有新的observer调用observer()方法被注册进来时触发,这也就是为什么支持黏性事件的原因了。
                //considerNotify做真正的判断和实际分发
                considerNotify(observer);
                initiator = null;
            } else {
                //这个情况会在调用 postValue(),setValue()方法时触发
                for (Iterator<Map.Entry<Observer<T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());
                }
            }
    }

 

    private void considerNotify(ObserverWrapper observer) {
       //如果该observer不活跃,实际上是该observer所在宿主生命周期不活跃时,则不分发
        if (!observer.mActive) {
            return;
        }
       //判断当前宿主的生命周期是否在活跃的生命周期范围内 即宿主的生命周期在onStart,onCreate,onResume等
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
//还有一层判断。当前观察者接收数据的次数是否大于等于liveData发射数据的次数.
//只有观察者接收消息的次数小于发送消息的次数 才会分发给它,否则就会有重复接收数据了
        if (observer.mLastVersion >= mVersion) {
            return;
        }
//最后则把数据分发给被ObserverWrapper包装的mObserver对象,与此同时跟liveData的version字段做一次同步
        observer.mLastVersion = mVersion;
    
        observer.mObserver.onChanged((T) mData);
    }
    //该方法至关重要,当且仅当有第一个观察者被注册到LiveData时会被激活
    //实际上列表分页库组件 Paging 就是利用了该方法,巧妙的实现了跟LiveData的搭配

   //当LiveData中的观察者都不活跃的时候,实际上也就是宿主不活跃的时候,会激活该方法
    protected void onInactive() {
    }


//重点来了。具有生命周期感知的观察者
//ObserverWrapper 类 在下面 
class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
        @NonNull final LifecycleOwner mOwner;
        LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<T> observer) {
            super(observer);
            mOwner = owner;
        }

        @Override
        boolean shouldBeActive() {
            //判断当前宿主是否在活跃状态内,也就是判断宿主的生命周期的值是否在onStart,onCreate ,onResume
            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
        }

        @Override
        public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
          //当观察到宿主将被销毁的时候,就会主动清理注册进来的mObserver了
            if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
                removeObserver(mObserver);
                return;
            }
           //执行父类的方法,宿主的生命周期变了,当前观察者要跟着做出响应
           //(宿主活跃:接收数据,宿主不活跃:把自己也变成非活跃状态)
            activeStateChanged(shouldBeActive());
        }
    }

    private abstract class ObserverWrapper {
        final Observer<T> mObserver;
       //标记自己是否活跃,也对应着宿主是否活跃
        boolean mActive;
       //接收数据的次数,至关重要并且mLastVersion =START_VERSION =-1;这 
       //无论之前liveData已经发射了几次数据,新注册进来的Observer接收数据的次数.都是小于等于liveData发送数据的次数。为黏性事件做了伏笔。
       //后续我们打造0反射实现消息分发总线LiveDataBus(支持黏性事件)也会基于此设计。
        int mLastVersion = START_VERSION;
         //传递一个被包装的observer对象。
        ObserverWrapper(Observer<T> observer) {
            mObserver = observer;
        }

    
        void activeStateChanged(boolean newActive) {
            if (newActive == mActive) {
                return;
            }
           //变更当前观察者的状态,activeStateChanged被触发的实际有以下几种
           //1.一个新的observer被注册进来,于此同时宿主会主动分发生命周期状态给它,就会触发该方法
          //2.宿主生命周期发生了变化,宿主也会分发生命周期给它,触发该方法。
          //3.在做数据分发的时候considerNotify,如果判断宿主处于非活跃状态,也会触发该方法
            mActive = newActive;
           //计算liveData是否正处于非活跃状态
            boolean wasInactive = LiveData.this.mActiveCount == 0;
          //因为有了本次变更,再次计算liveData中活跃观察者的个数
            LiveData.this.mActiveCount += mActive ? 1 : -1;
            //如果LiveData之前处于非活跃状态,而现在至少有一个活跃的观察者了
            if (wasInactive && mActive) {
                //则触发onActive方法
                onActive();
            }

            //如果LiveData中活跃的观察者个数为0 ,则执行 onInactive();
           //也就是宿主不活跃了,那么活跃的观察者个数就是0 了
            if (LiveData.this.mActiveCount == 0 && !mActive) {
                onInactive();
            }
          
          //调用LiveData的方法吧数据分发给自己。嘿嘿 
           dispatchingValue(this);
        }
    }
六、LiveData工作原理图解
LiveData工作原理图示
七、LiveData衍生用法
  • 0反射打造不用反注册的消息分发总线LiveDataBus
  • 配合Paging实现页面初始化数据的自动化加载
  • 配合ViewModel实现数据层和View层的分离
  • 配合Room数据库实现数据库数据变更 UI自动更新
  • 配合Transformations.Map()可以实现数据发射前的过滤,转换等操作。
wx搜索会编程的丘比龙---好文不断推送
1.Jetpack全系列组件高级用法&原理分析实战
2.Jetpack系列组件--LiveData从相遇到相知
3.Jetpack系列组件--ViewModel从相遇到相知
4.Jetpack系列组件--优雅的打造一款事件总线LiveDataBus
5.Jetpack系列组件--列表分页库Paging从相遇到相知
6.Jetpack系列组件--Navigation组件工作原理分析&灵活改造应用
7.Jetpack系列组件--dataBinding从相遇到相知
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容