Android LiveData

542 阅读2分钟

Android LiveData

  • LiveData 是一个可观察的数据持有类,旨在存储和传递数据,遵循观察者模式,基于 Lifecycle 生命周期感知组件进行工作,意味着 LiveData 有感知其他组件生命周期(比如 Activity 和 Fragment 等)的能力,在应用程序中实现数据和 UI 组件之间的响应式绑定(能在数据发生变化时通知 UI 进行相应的更新),这种感知能力可确保 LiveData 只更新处于活跃生命周期状态(比如 Lifecycle$State#STARTED 或 Lifecycle$State#RESUMED)下的观察者,而且通常情况下不需要手动移除观察者(LiveData 会自动管理,当观察者被销毁时 LiveData 会自动取消对其的订阅),有助于避免在观察者不可见或已销毁时发送不必要的更新,从而减少内存泄漏和资源浪费等问题
  • LiveData 在更新数据时是线程安全的,LiveData#setValue 必须在主线程中调用,而 LiveData#postValue 可以在后台子线程中调用,无论数据是在主线程还是子线程中更新,LiveData 都会确保在主线程中通知观察者,有助于简化多线程编程中的线程同步问题
  • LiveData 默认被设计为 Sticky Events 粘性事件(事件发送后,观察者才订阅,能收到订阅之前的事件)的,先更新数据,再注册观察者依然能接收到之前的数据,新注册的观察者版本号为 -1 符合小于数据版本号的条件,所以注册时会立即收到一次数据(LiveData 每次改变数据都会对 LiveData#mVersion 数据版本号加 1,并触发 LiveData$ObserverWrapper#mLastVersion 观察者版本号小于 LiveData#mVersion 数据版本号的观察者进行 Observer#onChanged 回调,同时将数据版本号赋值给观察者版本号进行保持一致),所以粘性事件这一特性本身并不算是一种问题,反而这种特性在很多情况下都很实用,符合 LiveData 设计出来被用来处理的场景(返回的是 “UiState”,比如页面重建展示时,自动推送最后一次数据而不必重新进行请求),而在 “页面间通信” 这种场景(返回的是 “UiEvent”,比如显示 Toast、显示 Snackbar 、界面跳转或者 Dialog 的展示等)下,相当于侧重用来保存数据状态的,而不侧重用来事件传递的

被观察者

public abstract class LiveData<T> {
    final Object mDataLock = new Object();
    static final int START_VERSION = -1;
    static final Object NOT_SET = new Object();
    //SafeIterableMap 是一个基于双向链表的数据结构,支持在遍历过程中安全地删除元素,线程不安全的
    private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers = new SafeIterableMap<>();
    //...
    private volatile Object mData; //mData 就是 LiveData 保存的最后一次更新的数据
    volatile Object mPendingData = NOT_SET;
    private int mVersion; //是LiveData的数据版本号,每一次更新数据版本号都会+1 
    //...
    private final Runnable mPostValueRunnable = new Runnable() {
        @SuppressWarnings("unchecked")
        @Override
        public void run() {
            Object newValue;
            synchronized (mDataLock) {
                newValue = mPendingData;
                mPendingData = NOT_SET;
            }
            //最终还是调用了 LiveData#setValue 方法
            setValue((T) newValue);
        }
    };
    public LiveData(T value) {
        mData = value;
        mVersion = START_VERSION + 1;
    }
    public LiveData() {
        mData = NOT_SET;
        mVersion = START_VERSION;
    }
    private void considerNotify(ObserverWrapper observer) {
        if (!observer.mActive) {
            return;
        }
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        //
        observer.mObserver.onChanged((T) mData);
    }
    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<? super T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    //
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }
    @MainThread
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        assertMainThread("observe");
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // ignore
            return;
        }
        //LifecycleBoundObserver 实现了 LifecycleObserver 接口并且包装了 LiveData 对应的 Observer 观察者,做到了将生命周期观察者和 LiveData 的观察者绑定在一起
        //每次走 observe 方法都会重新创建出 LifecycleBoundObserver 实例
        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);
    }
    //...
    protected void postValue(T value) {
        boolean postTask;
        synchronized (mDataLock) {
            postTask = mPendingData == NOT_SET;
            mPendingData = value;
        }
        if (!postTask) {
            return;
        }
        //通过 Handler#post 到主线程(内部是发异步消息的 Handler)
        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
    }
    //setValue 方法必须要在主线程调用
    @MainThread
    protected void setValue(T value) {
        assertMainThread("setValue");
        mVersion++;
        mData = value;
        //分发数据(消息)
        dispatchingValue(null);
    } 
    //...  
}
//实现对 LiveData#postValue 和 LiveData#setValue  这两个 protected 方法的访问
public class MutableLiveData<T> extends LiveData<T> {

    public MutableLiveData(T value) {
        super(value);
    }

    public MutableLiveData() {
        super();
    }

    @Override
    public void postValue(T value) {
        super.postValue(value);
    }

    @Override
    public void setValue(T value) {
        super.setValue(value);
    }
}

观察者

fun interface Observer<T> {
    /**
     * Called when the data is changed to [value].
     */
    fun onChanged(value: T)
}

使用

class MainActivity : AppCompatActivity() {

    private val _testMutableLiveData = MutableLiveData<String>()
    val testLiveData:LiveData<String> = _testMutableLiveData

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
 
        testLiveData.observe(this, Observer { data ->
            //根据接收到的数据进行 UI 更新
        })

        //设置值
        _testMutableLiveData.value = "new data"
        _testMutableLiveData.postValue("new data")
}

LiveData 多线程频繁 postValue 会出现丢失部分数据

  • 因为更新和分发值分别在两个不同的线程,如果生产者更新值速率过快,消费者分发至速率过低,就会导致上一次更新的值,还没有被分发出去就被新的值更新了,导致丢失了部分中途更新的值(生产者和消费者速度不匹配)
  • 不过 LiveData 一定能接收到 postValue 的最终值
  • LiveData 本身是推荐作为驱动 UI 的数据流的(不建议使用在过于频繁刷新值的场景,可以考虑改用 Kotlin 的 Flow)

数据倒灌

  • 作者 KunMinX 定义的 Data backflow 数据倒灌是专指在页面通信(事件回调)的场景下,通过 SharedViewModel 的 LiveData 给当前页通知过一次,并返回上一页,下次再进入当前页时重复收到旧数据推送的情况
  • 就是通常情况下当 Activity 或 Fragment 在因配置更改(比如屏幕旋转)后被销毁并重建的时候,由于 ViewModel 保留了内部的 LiveData,而导致可能会收到之前旧的观察者已经处理过的数据,就好像数据 “倒流、倒灌” 回来了一样
  • 数据倒灌可以理解成在粘性事件前提条件的基础上,当第二次调用 observe 时候,如果还能收到第一次调用 observe 旧的观察者已经处理过的数据的情形,所以只要将 LiveData 变为非粘性事件的也能避免出现数据倒灌的问题,通常有以下几种解决方案:修改干涉版本号、SingleLiveEvent、Event Wrapper 事件包装器、UnPeekLiveData 和 SharedFlow 等

修改干涉版本号

  • 利用反射等手段修改版本号,在 observe 方法之前将 mVersion 赋值给 mLastVersion 使得 mLastVersion = mVersion 以解决问题
  • 通过实现非粘性事件以解决数据倒灌的问题

SingleLiveEvent

  • Google 提供的 SingleLiveEvent 是一个只会触发一次数据更新的 LiveData(使得 LiveData#observe 方法只会去触发一次 onChanged 方法回调)
  • 利用 AtomicBoolean 标记位,初始化时默认 false,然后在给 LiveData 设置值的时候,把 AtomicBoolean 设置成 true,当 onChanged 方法回调触发后通过 AtomicBoolean#compareAndSet 方法进行比较并设置操作,将 AtomicBoolean 设置成 false,后续如果再次触发了 onChanged 方法回调,由于此时 AtomicBoolean 已经为 false ,但此时 compareAndSet 条件已经不再满足,所以就不会继续触发 onChanged 方法回调
  • SingleLiveEvent 直接解决了数据倒灌的问题(仍旧是粘性事件),但是对于多个观察者监听就不能工作了(如果是存在多个观察者,只有一个能够正常收到通知),适用于只需要一个观察者接收一次更新的场景,所以局限性较大
public class SingleLiveEvent<T> extends MutableLiveData<T> {
    private static final String TAG = "SingleLiveEvent";
    //
    private final AtomicBoolean mPending = new AtomicBoolean(false);
    @MainThread
    public void observe(LifecycleOwner owner, final Observer<? super T> observer) {
        if (hasActiveObservers()) {
            Log.w(TAG, "Multiple observers registered but only one will be notified of changes.");
        }
        // Observe the internal MutableLiveData
        super.observe(owner, new Observer<T>() {
            @Override
            public void onChanged(@Nullable T t) {
                //expectedValue 预期值 true,最终设置成 false
                if (mPending.compareAndSet(true, false)) {
                    //compareAndSet 操作成功
                    observer.onChanged(t);
                }
            }
        });
    }
    @MainThread
    public void setValue(@Nullable T t) {
        //设置成 true
        mPending.set(true);
        super.setValue(t);
    }
    @MainThread
    public void call() {
        setValue(null);
    }
}

Event Wrapper 事件包装器

  • 理念就是把 Event 事件设计为一种 State 状态,通过包装事件让事件带处理状态,形成了一种把事件视为一种状态(Consumed 已消费或者 Not Consumed 未消费)的概念
  • 直接解决了数据倒灌的问题(仍旧是粘性事件),不过相对于 SingleLiveEvent 来说 Event Wrapper 可以将多个观察者添加到一次性事件中
open class Event<out T>(private val content: T) {

    var hasBeenHandled = false
        private set // Allow external read but not write

    //返回数据并防止再次使用
    fun getContentIfNotHandled(): T? {
        return if (hasBeenHandled) {
            null
        } else {
            //改变 hasBeenHandled 的值
            hasBeenHandled = true
            content
        }
    }

    //返回数据,即使它已被处理
    fun peekContent(): T = content
}
class ListViewModel : ViewModel {
    private val _navigateToDetails = MutableLiveData<Event<String>>()

    val navigateToDetails : LiveData<Event<String>>
        get() = _navigateToDetails


    fun userClicksOnButton(itemId: String) {
        //通过将新 Event 设置为新值来触发事件
        _navigateToDetails.value = Event(itemId)
    }
}

myViewModel.navigateToDetails.observe(this, Observer {
    //需要通过调用 getContentIfNotHandled 方法来将事件与观察者关联起来
    it.getContentIfNotHandled()?.let { 
        //仅当从未处理过事件时才进行处理
        startActivity(DetailsActivity...)
    }
})

UnPeekLiveData

  • KunMinX 提供的 UnPeekLiveData 维护了 UnPeekLiveData#mCurrentVersion 和 UnPeekLiveData$ObserverWrapper#mVersion 两个版本号,相当于外部自行维护一套版本号进行判断处理
  • 通过实现非粘性事件以解决数据倒灌的问题

SharedFlow

  • 可以通过用 MutableSharedFlow 构造将 replay 参数设置成 0 以使得重放 0 个之前发射的值,来替代 LiveData 的功能
  • 通过实现非粘性事件以解决数据倒灌的问题