阅读 1685

【Android Jetpack】LiveData 从入门到精通

image.png

背景

上一篇介绍了 Android Jetpack 的基础组件 Lifecycle,它是生命周期感知型组件,实现生命周期管理的一致性,在内部进行了统一的生命周期状态管理,可以很方便的提供给其他的组件(比如 LiveData,ViewModel)使用,同时其他类还能够通过实现 LifecycleObserver 接口和添加注解来监控组件的生命周期状态,达到一个解耦的目的。

LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 Activity/Fragment)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。

也就是说 LiveData 它既是一个观察者(观察生命周期),也是一个被观察者(数据变更进行通知,但是数据通知只发生在生命周期活跃状态 STARTED、RESUMED)

基础使用

导入依赖
// LiveData & ViewModel 因为这两者通常都一起使用
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
复制代码
LiveData 使用
  1. 创建 LiveData 的实例以存储某种类型的数据。这通常在 ViewModel 类中完成。
  2. 创建可定义 onChanged()方法的 Observer 对象,该方法可以控制当 LiveData 对象存储的数据更改时会发生什么。通常情况下,您可以在界面控制器(如 Activity 或 Fragment)中创建 Observer 对象。
  3. 使用 [observe()](developer.android.com/reference/a…, android.arch.lifecycle.Observer)) 方法将 Observer 对象附加到 LiveData 对象。observe() 方法会采用 LifecycleOwner 对象。这样会使 Observer 对象订阅 LiveData 对象,以使其收到有关更改的通知。通常情况下,您可以在界面控制器(如 Activity 或 Fragment)中附加 Observer 对象。

代码如下,在 ViewModel 中定义 LiveData

class NameViewModel : ViewModel() {

    // Create a LiveData with a String
    val currentName: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }

    // Rest of the ViewModel...
}
复制代码

在 NameActivity 中使用

class NameActivity : AppCompatActivity() {

    // Use the 'by viewModels()' Kotlin property delegate
    // from the activity-ktx artifact
    private val model: NameViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Other code to setup the activity...

        // Create the observer which updates the UI.
        val nameObserver = Observer<String> { newName ->
            // Update the UI, in this case, a TextView.
            nameTextView.text = newName
        }

        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
      	//观察数据
        model.currentName.observe(this, nameObserver)
    }
}
复制代码

更新数据

button.setOnClickListener {
    val anotherName = "John Doe"
    model.currentName.setValue(anotherName)
}
复制代码

您必须调用setValue(T)方法以从主线程更新 LiveData 对象。如果非主线程中执行代码,您可以改用 postValue(T)方法来更新 LiveData 对象。

将 LiveData 与 Room 一起使用

Room 持久性库支持返回 LiveData 对象的可观察查询。可观察查询属于数据库访问对象 (DAO) 的一部分。

当数据库更新时,Room 会生成更新 LiveData 对象所需的所有代码。在需要时,生成的代码会在后台线程上异步运行查询。此模式有助于使界面中显示的数据与存储在数据库中的数据保持同步。您可以在 Room 持久性库指南中详细了解 Room 和 DAO。

将协程与 LiveData 一起使用

LiveData 支持 Kotlin 协程。如需了解详情,请参阅将 Kotlin 协程与 Android 架构组件一起使用

其实 Google 推出的这一系列 Android Jetpack 库,在不同库之间的配合已经做的非常好了,比如上面的 LiveData 和 Room,协程一起使用,配合起来如丝般顺滑,非常好用。

进阶使用

扩展 LiveData

如果观察者的生命周期处于 STARTEDRESUMED 状态,则 LiveData 会认为该观察者处于活跃状态。以下示例代码说明了如何扩展 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)
    }
}
复制代码
  • LiveData 对象具有活跃观察者时,会调用 onActive() 方法。这意味着,您需要从此方法开始观察股价更新。
  • LiveData 对象没有任何活跃观察者时,会调用 onInactive() 方法。由于没有观察者在监听,因此没有理由与 StockManager 服务保持连接。
  • setValue(T) 方法将更新 LiveData 实例的值,并将更改告知活跃观察者。
public class MyFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val myPriceListener: LiveData<BigDecimal> = ...
        myPriceListener.observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? ->
            // Update the UI.
        })
    }
}
复制代码

observe() 方法将与 Fragment 视图关联的 LifecycleOwner 作为第一个参数传递。这样做表示此观察者已绑定到与所有者关联的 Lifecycle 对象,这意味着:

  • 如果 Lifecycle 对象未处于活跃状态,那么即使值发生更改,也不会调用观察者
  • 销毁 Lifecycle 对象后,会自动移除观察者。

LiveData具有生命周期感知能力,也就是说可以在多个 Activity、Fragment、Service 之间共享这些对象,所以可以将其设置为单例

转换 LiveData

如果你希望将 LiveData 分派给观察者之前对其值进行修改,或者说另一个 LiveData 需要根据它做进一步处理,Lifecycle 包中的 Transformation 类可以为我们提供帮助:

Transformations.map()

对存储在 LiveData 对象中的值应用函数,并将结果传播到下游。

val userLiveData: LiveData<User> = UserLiveData()
val userName: LiveData<String> = Transformations.map(userLiveData) {
    user -> "${user.name} ${user.lastName}"
}
复制代码
Transformations.switchMap()

如果想要根据某个值 切换观察不同LiveData数据,则可以使用Transformations.switchMap()方法。

	//两个liveData,由liveDataSwitch决定 返回哪个livaData数据
        MutableLiveData<String> liveData3 = new MutableLiveData<>();
        MutableLiveData<String> liveData4 = new MutableLiveData<>();
        
	//切换条件LiveData,liveDataSwitch的value 是切换条件
        MutableLiveData<Boolean> liveDataSwitch = new MutableLiveData<>();
        
	//liveDataSwitchMap由switchMap()方法生成,用于添加观察者
        LiveData<String> liveDataSwitchMap = Transformations.switchMap(liveDataSwitch, new Function<Boolean, LiveData<String>>() {
            @Override
            public LiveData<String> apply(Boolean input) {
            //这里是具体切换逻辑:根据liveDataSwitch的value返回哪个liveData
                if (input) {
                    return liveData3;
                }
                return liveData4;
            }
        });

        liveDataSwitchMap.observe(this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                Log.i(TAG, "onChanged2: " + s);
            }
        });

        boolean switchValue = true;
        liveDataSwitch.setValue(switchValue);//设置切换条件值

        liveData3.setValue("liveData3");
        liveData4.setValue("liveData4");

复制代码

这个官方的 demo 没看明白,引用的是胡飞洋大佬博客文章内容,上面的很多用例都是官方的,因为在我看来官方的用例是最好的。

合并多个 LiveData 源

MediatorLiveDataLiveData 的子类,允许您合并多个 LiveData 源。只要任何原始的 LiveData 源对象发生更改,就会触发 MediatorLiveData 对象的观察者。

比如一个常见的功能,app 内的通知常用小红点显示是否有新消息,这个时候,如果没有对红点的展示逻辑做一个统一的抽象和管理的话,就会感觉很复杂,后续也不太好维护。这个时候就可以需要MediatorLiveData进行对红点的统一管理,详细内容可以参考这篇文章

源码分析

我们知道LiveData是在Lifecycle 的帮助下,实现了生命周期管理的一致性。对于 LiveData 的数据修改流程,我们从setValue方法开始分析。

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

然后进入 dispatchingValue方法

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    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;
    }
复制代码

因为传入的dispatchingValue的参数为 null,所以我们进入considerNotify方法

    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;
        observer.mObserver.onChanged((T) mData);
    }
复制代码

可以看到在最后一行,被观察者通知观察者进行onChanged数据变更,而我们的订阅是在 observe方法中

    @MainThread
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        assertMainThread("observe");
        //如果 LifecycleOwner 的 DESTROYED,return
        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()`的真正实现是 Activity 中的 `LifecycleRegistry`(如果在Activity 中调用的话)。
        owner.getLifecycle().addObserver(wrapper); 
    }
复制代码

可以看到整个流程就是这么简单。就是简单的观察者模式的运用。不过 LiveData 还帮助我们自动处理的生命周期,防止内存泄漏。

要注意的是, LiveData#dispatchingValue除了在我们主动更新数据的时候会触发, 在我们的观察者状态变更(inactive->active)的时候, 也会通知到, 这就导致了LiveData必然支持粘性事件

setValue/postValue方法,是为了将数据通知到主线程,其中 postValue方法最终调用了setValue

    protected void postValue(T value) {
        boolean postTask;
        synchronized (mDataLock) {
            postTask = mPendingData == NOT_SET;
            mPendingData = value;
        }
        if (!postTask) {
            return;
        }
        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable); //发送到主线程
    }
复制代码

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);  //最终还是调用了 setValue方法
        }
    };
复制代码

使用 LiveData 的优势

确保界面符合数据状态

LiveData 遵循观察者模式。当底层数据发生变化时,LiveData 会通知 Observer 对象。您可以整合代码以在这些 Observer 对象中更新界面。这样一来,您无需在每次应用数据发生变化时更新界面,因为观察者会替您完成更新。

不会发生内存泄漏

观察者会绑定到 Lifecycle 对象,并在其关联的生命周期遭到销毁后进行自我清理。

不会因 Activity 停止而导致崩溃

如果观察者的生命周期处于非活跃状态(如返回栈中的 Activity),则它不会接收任何 LiveData 事件。

不再需要手动处理生命周期

界面组件只是观察相关数据,不会停止或恢复观察。LiveData 将自动管理所有这些操作,因为它在观察时可以感知相关的生命周期状态变化。

数据始终保持最新状态

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

适当的配置更改

如果由于配置更改(如设备旋转)而重新创建了 Activity 或 Fragment,它会立即接收最新的可用数据。

共享资源

您可以使用单例模式扩展 LiveData 对象以封装系统服务,以便在应用中共享它们。LiveData 对象连接到系统服务一次,然后需要相应资源的任何观察者只需观察 LiveData 对象。如需了解详情,请参阅扩展 LiveData

基于 LiveData 的事件总线的实现(LiveDataBus)

可以看出, LiveData本身就已经可观测数据更新, 我们通过维护一张 eventName-LiveData 的哈希表, 就可以得到一个基础的事件总线

class LiveDataBus {
    internal val liveDatas by lazy { mutableMapOf<String, LiveData<*>>() }

    @Synchronized
    private fun <T>bus(channel: String): LiveData<T>{
        return liveDatas.getOrPut(channel){
            LiveDataEvent<T>(channel)
        } as LiveData<T>
    }

    fun <T> with(channel: String): LiveData<T>{
        return bus(channel)
    }

    companion object{
        private val INSTANCE by lazy { LiveDataBus() }
        @JvmStatic
        fun get() = INSTANCE
    }
}
复制代码

除了粘性事件以外,我们还需要非粘性事件的支持, 这里有两种做法。反射获取ObserverWrapper.mLastVersion, 在订阅的时候使得初始化的ObserverWrapper.mLastVersion等于LiveData.mVersion, 使得粘性消息无法通过实现;这里可以参考美团的文章:[Android消息总线的演进之路:用LiveDataBus替代RxBus、EventBus]。

还有一种做法是粘性消息最终会调到Observer#onChanged, 那么我们就干脆将其再进行一层包装, 内部维护实际的订阅消息数, 来判断是否要触发真正的onChanged方法,详细内容可以参考这篇文章

LiveData 的建议写法

在我自己的项目中,我通常使用该方式来定义LiveData,严格控制LiveData的权限,确保视图 Activity/Fragment 中只有该数据的访问权限,而将数据的修改权限严格控制在ViewModel中,参考自 github 上的一些优秀项目的写法。

//ViewModel.java
private val _cartCountLD: MutableLiveData<Int> = MutableLiveData()
val cartCountLD: LiveData<Int>
    get() = _cartCountLD

private val _saveBirthdayLD: MutableLiveData<Boolean> = MutableLiveData()
val saveBirthdayLD: LiveData<Boolean>
		get() = _saveBirthdayLD
复制代码

灵魂拷问(在 Activity 的哪个几个生命周期修改 LiveData 值,能被观察者观察到)

前面说过数据变更的通知控制在生命周期活跃状态 STARTED、RESUMED,那么对应是哪几个生命周期呢?先来看看这段代码:

class MainActivity : AppCompatActivity() {
    private var jetpackLiveData: MutableLiveData<String> = MutableLiveData<String>()
    private val TAG = "================"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        jetpackLiveData.observe(this,Observer {
            Log.i(TAG, "observe value:"+it+" state:"+this.lifecycle.currentState)
        })
      	Log.i(TAG, "onCreate: "+" state:"+this.lifecycle.currentState)
        jetpackLiveData.value = "onCreate"
    }

    override fun onStart() {
        super.onStart()
        Log.i(TAG, "onStart: "+" state:"+this.lifecycle.currentState)
        jetpackLiveData.value = "onStart"
    }

    override fun onResume() {
        super.onResume()
        Log.i(TAG, "onResume: "+" state:"+this.lifecycle.currentState)
        jetpackLiveData.value = "onResume"
    }

    override fun onPause() {
        super.onPause()
        Log.i(TAG, "onPause: "+" state:"+this.lifecycle.currentState)
        jetpackLiveData.value = "onPause"
    }

    override fun onStop() {
        super.onStop()
        Log.i(TAG, "onStop: "+" state:"+this.lifecycle.currentState)
        jetpackLiveData.value = "onStop"
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.i(TAG, "onDestroy: "+" state:"+this.lifecycle.currentState)
        jetpackLiveData.value = "onDestroy"
    }
}
复制代码

启动界面并退出界面,观察到的日志是:

jetpackdemo I/================: onCreate:  state:INITIALIZED
jetpackdemo I/================: onStart:  state:CREATED
jetpackdemo I/================: observe value:onStart state:STARTED //STARTED 状态
jetpackdemo I/================: onResume:  state:STARTED
jetpackdemo I/================: observe value:onResume state:STARTED //STARTED 状态
jetpackdemo I/================: onPause:  state:STARTED
jetpackdemo I/================: observe value:onPause state:STARTED //STARTED 状态
jetpackdemo I/================: onStop:  state:CREATED
jetpackdemo I/================: onDestroy:  state:DESTROYED
复制代码

结果是我们只能在onStart()、onResume()、onPause()这三个方法中,而且观察者观察到的状态都是 STARTED状态,注意,onStart 执行时是CREATED 状态,但是收到时是 STARTED 状态。那么问题又来了,RESUMED 状态呢,为什么没有呢

那么 RESUMED 状态呢我们应该怎么获取到呢?我们可以在 500ms 之后获取

    override fun onResume() {
        super.onResume()
        Log.i(TAG, "onResume: "+" state:"+this.lifecycle.currentState)
        jetpackLiveData.value = "onResume"
        
        Handler().postDelayed(Runnable {
            Log.i(TAG, "onResume: ==========500 毫秒之后===="+this.lifecycle.currentState)
        },500)
    }
复制代码

获取到的日志如下:

jetpackdemo I/================: onResume: ==========500 毫秒之后====RESUMED
复制代码

总结

如果非要用一句话来总结,LiveData是在Lifecycle 的帮助下,实现了生命周期管理的一致性,将数据变更的通知控制在生命周期活跃状态 STARTED、RESUMED(注意这是Lifecycle 中的 State)时进行通知,该通知成为数据的唯一可信来源,也就是视图获取数据的唯一入口。

参考文章

developer.android.com/topic/libra…

yutiantina.github.io/2019/09/20/…

juejin.cn/post/694541…

juejin.cn/post/690314…

tech.meituan.com/2018/07/26/…

我的其他文章

【Android Jetpack】Lifecycle 从入门到精通

Android Bitmap 高效加载,那些你必须掌握的稀碎知识点

Android 屏幕适配,那些你必须掌握的稀碎知识点

Android 轻量级存储方案的前世今生

性能优化:为什么要使用SparseArray和ArrayMap替代HashMap?

Activity的初级,中级,高级问法,你都掌握了吗?

文章分类
Android
文章标签