Jetpack 之 LiveData

Jetpack 系列第二篇,这次回顾 LiveData,在回顾如何使用的同时,也通过源码对某些问题有了一些粗略了解。LiveData 与 Lifecycle 关系比较密切,再结合 ViewModel,可以说是构成 MVVM 架构的几个核心点。

一、LiveData 有什么用?

在 MVP 架构中,如果 M 层发起一个异步请求,在收到响应的时候,P 层需要判断 V 层是否已销毁,如果 V 层已销毁,这时候是不应该去更新 V 层的,否则会 NPE,而 P 层与 V 层的关系是我们手动维护的,要是没控制好就很容易内存泄漏。
要是在收到响应的时候,它可以自己去判断 V 层是否已销毁,如果已销毁就不去通知了那多好,通常 V 层这个角色都是有 Activity 或 Fragment 扮演的,而 Activity 和 Fragment 又实现了 LifecycleOwner,因此,LiveData 诞生了。
LiveData 在持有数据同时又可以感知观察者的生命周期,所以它有几个好处:
1.当持有的数据有更新的时候,它只会在合理的时机才会去通知观察者,观察者不活跃的时候 LiveData 是不会去通知它的;
2.当观察者销毁的时候 LiveData 会自动移除该观察者,避免了内存泄漏;
3.当不同页面间需要共享数据的时候,新的观察者在订阅的时候会收到最新的数据而不需要重新请求;
4.针对异步操作,在子线程中我们也可以直接更新数据,不用关心线程切换问题。
都有这些好处了,有什么理由不用它呢?

二、LiveData 怎么用?

LiveData 的使用也挺简单,它自己是个抽象类,我们一般用它的子类 MutableLiveData:

val liveData = object : MutableLiveData<String>() {
    override fun onActive() {
        super.onActive()
        // 当观察者数量从 0 变为大于 0 时回调
        Log.i(TAG, "onActive")
    
    override fun onInactive() {
        super.onInactive()
        // 当观察者数量变为 0 时回调
        Log.i(TAG, "onInactive")
    }
}

创建时指定的泛型表示 LiveData 持有的数据类型,也就是我们真正需要的数据类型。
想要对一个 LiveData 对象进行观察的话可以调用 observe(LifecycleOwner owner, Observer<? super T> observer) 方法,该方法第一个参数为 LifecycleOwner,我们就不用手动去关心它何时销毁了,它会自己移除观察者。也可以调用 observeForever(Observer<? super T> observer) 方法,如果是这种方式的话就需要自己手动调用 removeObserver(final Observer<? super T> observer) 去解注册了。
一般来说能获取到 LifecycleOwner 的实现类的对象的话,还是推荐第一种方式,能省心不少:

liveData.observe(this@LiveDataActivity) {
    Log.i(TAG, "onChanged--->$it")
}

这样,当 LiveData 里的数据有更新的时候,我们就可以在这个回调里面收到最新的数据了。
那如何去更新 LiveData 的数据呢?有两种方式:
1.setValue(T t):只能在主线程中更新数据。
2.postValue(T t):可以在主线程中,也可以在子线程中更新数据。

这两个方法有什么不同,来简单看看它们的源码:

@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() 中首先判断了当前线程是否是主线程,所以 setValue() 只能在主线程中执行。而 postValue() 方法,则是先将新数据的值赋给了变量 mPendingData,然后提交了一个任务 mPostValueRunnable,ArchTaskExecutor.getInstance() 的 postToMainThread() 其实是由一个 TaskExecutor 的代理类去完成,默认是 DefaultTaskExecutor,它的 postToMainThread() 方法中通过主线程的 Handler 去执行这个任务:

@Override
public void postToMainThread(Runnable runnable) {
    if (mMainHandler == null) {
        synchronized (mLock) {
            if (mMainHandler == null) {
                mMainHandler = createAsync(Looper.getMainLooper());
            }
        }
    }
    //noinspection ConstantConditions
    mMainHandler.post(runnable);
}

那刚才提交的 mPostValueRunnable 任务里面做了什么呢?

private final Runnable mPostValueRunnable = new Runnable() {
    @SuppressWarnings("unchecked")
    @Override
    public void run() {
        Object newValue;
        synchronized (mDataLock) {
            newValue = mPendingData;
            mPendingData = NOT_SET;
        }
        setValue((T) newValue);
    }
};

其实就是将 mPendingData 再取出来,调用了 setValue() 方法,所以更新数据最终都是通过 setValue() 方法来完成。

LiveData 的使用就这么简单,唯一需要注意的一点就是根据所在线程来调用不同的更新数据的方法,看来第一节中提到的 LiveData 的好处的第 4 点“不用关心线程切换问题”其实只是通过调用不同方法来实现,所以我们通常在封装 LiveData 的时候会将两个方法统一成一个。

三、为什么 LiveData 绑定的时候会收到最后更新的值的事件?

一般来说,会先注册观察者,再去更新数据,但也有在跳转到新界面的时候观察之前界面的数据,这种情况下,新界面在注册观察者的时候就会收到最新数据的回调,相当于一个粘性事件,这就是第一节提到的 LiveData 的好处的第 3 点。但有时候我们可能并不希望它是一个粘性事件,要想解决它的话,首先得知道它为什么会这样,来看看 observe() 方法中做了什么:

public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
    assertMainThread("observe");
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        // ignore
        return;
    }
    // 创建 observer 的装饰类
    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);
}

该方法判断了一下所在线程和当前生命周期的状态,然后创建了一个 observer 的装饰类的对象,判断了一下是否已添加,如果已添加就 return,如果没有添加就将装饰类对象作为参数去添加观察者,逻辑很简单。这里的重点是 LifecycleBoundObserver,它是 ObserverWrapper 的一个实现类,而 ObserverWrapper 中有一个属性 mLastVersion,它的初值为 START_VERSION(-1),记住这个值。
添加观察者是调用的 Lifecycle 的 addObserver() 方法,这里又回到了 Lifecycle。该方法中具体做了什么可以参考一下我另一篇博文:Jetpack之Lifecycle_禽兽先生不禽兽-CSDN博客。在我们现在的问题中,我们只需要知道 addObserver() 中会将 observer 再次进行包装,然后比较一下生命周期,在同步生命周期的循环中会去调用 observer 的装饰类(ObserverWithState)的 dispatchEvent() 方法,该方法中调用了 LifecycleEventObserver 的 onStateChanged() 方法,而 LifecycleBoundObserver 正是 LifecycleEventObserver 的实现类,所以我们来看看 LifecycleBoundObserver() 方法里做了什么:

class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
    ...
    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
            @NonNull Lifecycle.Event event) {
        Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
        if (currentState == DESTROYED) {
            removeObserver(mObserver);
            return;
        }
        Lifecycle.State prevState = null;
        while (prevState != currentState) {
            prevState = currentState;
            activeStateChanged(shouldBeActive());
            currentState = mOwner.getLifecycle().getCurrentState();
        }
    }
    ...
}

该方法中也是在进行一个生命周期状态的同步,循环中调用了 ObserverWrapper 的 activeStateChanged() 方法:

private abstract class ObserverWrapper {
    ...
    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);
        }
    }
}

该方法中设置了 observer 的活跃状态,并且如果处于活跃状态的话,将自己作为参数调用了 LiveData 的 dispatchingValue() 方法:

void dispatchingValue(@Nullable ObserverWrapper initiator) {
    ...
    do {
        ...
        if (initiator != null) {
            considerNotify(initiator);
            ...
        } else {
            for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                    mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                considerNotify(iterator.next().getValue());
                ...
            }
        }
    } while (mDispatchInvalidated);
    ...
}

该方法虽然对入参有一个 null 判断,但都会调用 considerNotify 方法:

private void considerNotify(ObserverWrapper observer) {
    // 观察者不活跃了,直接 return
    if (!observer.mActive) {
        return;
    }
    if (!observer.shouldBeActive()) {
        observer.activeStateChanged(false);
        return;
    }
    // mLastVersion 如果大于等于 mVersion,直接 return
    if (observer.mLastVersion >= mVersion) {
        return;
    }
    // 同步 mLastVersion
    observer.mLastVersion = mVersion;
    // 通知观察者,数据更新
    observer.mObserver.onChanged((T) mData);
}

观察者是否活跃自不必说,注意 mLastVersion 和 mVersion 比较大小这里,也就是说,如果 mLastVersion 比 mVersion 小,就会去通知观察者。还记得这两个值什么时候会改变吗?刚才提到 ObserverWrapper 实例创建的时候会给它的属性 mLastVersion 赋值为 START_VERSION,后面就只有在 considerNotify() 方法中与 mVersion 同步时改变值了。而 mVersion 会在 LiveData 的构造方法中赋初值,调用无参的构造方法初值也是 START_VERSION,如果调用的有参的构造方法,则是 START_VERSION+1,在第二节中我们看 setValue() 方法的时候,该方法也会将 mVersion 自增 1,也就是说数据一旦有变化,mVersion 就会 +1。如此说来,在调用 observe() 方法之前,如果数据变化过,则 mLastVersion 一定比 mVersion 小,所以此时上面的 (observer.mLastVersion >= mVersion) 不成立,不会 return,所以继续往下走就会去通知一次观察者最新的数据。

四、为什么退到后台了 Observer 收不到事件?

其实这个问题在刚才的 considerNotify() 方法中已经有答案了,第一行中,如果观察者处于非活跃状态就直接 return 了。为什么要再单独提一下这个问题呢,主要是因为通常我们使用 LiveData 是结合数据请求用的,收到响应后一般就渲染 UI 了,而在后台的时候因为看不到 UI,所以也不关心非活跃状态的时候观察者是否有收到通知。但是如果我们做的是一些在后台也关心数据更新的功能,如即时通信中在后台收到消息的话需要通知栏展示,数据采集中在后台也应该上报服务器,诸如此类,如果结合着 LiveData 使用,一定要知道问题出在哪里,然后才知道去修改才能满足需求。

五、总结

LiveData 算是 Lifecycle 的一个实际应用了,结合生命周期确实能让我们不再操心内存泄漏的问题,它的部分特性确实很有用,官方既然在推它,那肯定还是有点作用的,而且我们还可以使用 LiveData 来封装事件总线框架,如果使用 Jetpack 全家桶的话,也可以减少一些包体积,就算它在面对一些实际需求的时候可能存在一些问题,但是我们只要知道它的原理了就知道如何去改它,总的来说利大于弊,所以快快用起来。

六、Demo 地址

禽兽先生/JetpackDemo-LiveDatahttps://gitee.com/MrQinshou/jetpack-demo/tree/master/app/src/main/java/com/qinshou/jetpackdemo/livedata

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值