Android Architecture Component之LiveData 分析

一 什么是LiveData

LiveData是一个可观察数据变化的类,但是与其他观察类不同的是,LiveData是可以感知到所持有类的生命周期的。所以我们一般用它来和Activity、Fragment或者service一起使用。这里有个很有好处的地方就是,LiveData的onChange方法,只会在activity在前台的时候产生回调,后面我们可以用例子来说明。

二 LiveData的好处

那么使用LiveData有什么好处呢?从LiveData的相关特性,我们可以归纳出以下几点:

1 确保UI和数据状态保持一致
LiveData遵循观察者模式,当生命周期发生改变的时候,LiveData会通知观察者对象。因此我们可以通过统一在观察者对象数据中更新UI,而不是通过app数据的变更去改变UI。在我们这些对象发生变更的时候,我们的UI也会跟着改变。

2 不会有内存泄漏
所有的观察者都是绑定到owner的生命周期内的,也就是说,如果owner被finish的时候,所有的观察者也会被自动销毁。炒鸡智能方便有木有?

3 不会因为Activity被销毁而导致崩溃
这种情况多数发生在我们的异步回调函数中,比如有时候我们回去通过异步线程来获取网络数据后,刷新我们的界面。这个时候,当我们的activity被销毁的时候,我们的异步线程回调才刚回来,这就导致了我们在一个废弃的activity中更新他的UI,这就会导致异常崩溃的发生。

4 不再需要手动去控制生命周期
UI组件只会在对象不是stop或者resume的时候进行观察,LiveData会自动管理这一切。在正确的时候,给与观察者以回调。

5 永远保持最新的数据状态
当观察的对象不是处于激活状态的时候,LiveData并不会去刷新数据,而当观察的对象重新激活的时候,那么LiveData将会刷新最新的数据。最典型的是,如果你的Activity是后台的时候,再次期间所有的数据变更,liveData都会选择性的忽略,等到你重新唤醒Activity的时候,LiveData马上就会刷新他的值,让他和最后一个setValue(value)保持一致。

三 开始源码之旅

接下来,我们从一段简单的源码开始

viewModel.loadFiveUsers().observe(MainActivity.this, new Observer<List<User>>() {
        @Override
        public void onChanged(@Nullable List<User> users) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < users.size(); i++) {
                User user = users.get(i);
                sb.append("====================\n");
                sb.append("uid:" + user.getUid() + "\n " + user.getUserName() + "\n" + user.getLastName() + "\n");
                sb.append("\n");
            }
            tv_values.setText(sb.toString());
        }
    });

我们这里暂且忽略viewModel部门,我们只需要知道,viewModel.loadFiveUsers()返回的是一个LiveData<T>类型的数据就可以了。简单的说一下这段代码,就是实时获取当前数据库中的前5条数据,并且把这5条数据通过TextView显示出来。业务场景其实非常的简单。好了,接下来开始拆轮子

第一步:LiveData.observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer)Owner和observe是如何建立联系的
我们进入源码的世界

 @MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        // ignore
        return;
    }
    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    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;
    }
    owner.getLifecycle().addObserver(wrapper);
}

首先,LiveData强调的是,observe方法必须是在UI线程中进行调用的。我们从源码可知
1 如果当前的owner是Destroyed状态的,那么我们就直接忽略掉了,这个是显而易见的。
2 LiveData会先将当前的owner和observe结合在一起,生成一个wrapper对象,然后尝试observe和wrapper当成key和value 添加进mObservers中,这个mObservers其实就是一个map对象。如果发现这个key对应的value已经有值了,并且这value并不是我们即将put进去的value,那么就会抛错
3 加入不存在的话,那么wrapper将会被添加进当前owner的生命周期中,进行监听。

第二步:setValue(T value)
LiveData提供了两个setValue的方法,一个是同步情况的调用

@MainThread
protected void setValue(T value) {
    assertMainThread("setValue");
    mVersion++;
    mData = value;
    dispatchingValue(null);
}  

另一个是异步情况的调用

protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
   postTask = mPendingData == NOT_SET;
    mPendingData = value;
   }
   if (!postTask) {
       return;
   }
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}  

其实最后都是调用了 setValue((T) newValue);方法。我们来研究下这个方法,可以看到,他会先判断当前是否在主线程调用,如果不是的话,那么就会抛出错误。然后就会把当前LiveData的版本号mVersion++,把mData设置成最新的value。其实整个过程最麻烦的是dispatchingValue(null)这个方法。我们进入这个方法进行查看

private 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<T>, ObserverWrapper>> iterator =
                    mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                considerNotify(iterator.next().getValue());
                if (mDispatchInvalidated) {
                    break;
                }
            }
        }
    } while (mDispatchInvalidated);
    mDispatchingValue = false;
}

其实我们可以看到,这个个方法就是现在已有的观察者进行循环,检查到如果是当前的观察者,那么就调用considerNotify(iterator.next().getValue())方法。也即是找到最新的mData,进行onchanged(mData)回调给观察者。

 private void considerNotify(ObserverWrapper observer) {
    if (!observer.mActive) {
        return;
    }
    // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
    //
    // we still first check observer.active to keep it as the entrance for events. So even if
    // the observer moved to an active state, if we've not received that event, we better not
    // notify for a more predictable notification order.
    if (!observer.shouldBeActive()) {
        observer.activeStateChanged(false);
        return;
    }
    if (observer.mLastVersion >= mVersion) {
        return;
    }
    observer.mLastVersion = mVersion;
    //noinspection unchecked
    observer.mObserver.onChanged((T) mData);
}

其实这个方法,会调用到

 @Override
  boolean shouldBeActive() {
      return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
  }

这个方法就是会去判断当前的owner的状态是否是active状态,进行是否分发的动作。

四 生命周期中LiveData的回调情况

1 关于:永远保持最新的数据状态

我们主动setValue(T value)方法后,liveData调用的onChanged(T value)流程已经分析完毕了,那么前面我们所说的,当观察的对象不是处于激活状态的时候,LiveData并不会去刷新数据,而当观察的对象重新激活的时候,那么LiveData将会刷新最新的数据。最典型的是,如果你的Activity是后台的时候,再次期间所有的数据变更,liveData都会选择性的忽略,等到你重新唤醒Activity的时候,LiveData马上就会刷新他的值,让他和最后一个setValue(value)保持一致。这个是怎么实现的呢?

LifecycleBoundObserver 生命周期回调

其实就是如上图所说的,我们的activity在后台或者重新回到前台的时候,都会回调到上面的onStateChanged()方法。当Activity是后台的时候,shouldBeActive返回的是false,那么

void activeStateChanged(boolean newActive) {
        if (newActive == mActive) {
            return;
        }
        // immediately set active state, so we'd never dispatch anything to inactive
        // owner
        mActive = newActive;
        boolean wasInactive = LiveData.this.mActiveCount == 0;
        LiveData.this.mActiveCount += mActive ? 1 : -1;
        if (wasInactive && mActive) {
            onActive();
        }
        if (LiveData.this.mActiveCount == 0 && !mActive) {
            onInactive();
        }
        if (mActive) {
            dispatchingValue(this);
        }
    }

中的dispatchingValue(this)就不会进行分发,从而不会触发observe的onchanged(T mData) 方法。

2 Activity finish的时候,移除监听

上面的图中我们可以看到,LiveData还会去判断当前的owner是否已经被销毁了,如果被销毁了,还会调用removeObserver(mObserver);方法,这也就是我们不需要去关心内存泄漏,或者在Activity finish情况下,还去尝试更新UI的问题。

自此,LiveData的完整分析已经完成。

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

推荐阅读更多精彩内容