一、概述
LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意味着它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。
二、使用LiveData的优势
1、及时更新界面
LiveData 遵循观察者模式。当底层数据发生变化时,LiveData 会通知 Observer 对象。您可以将刷新界面的代码写在 Observer 对象的回调中,这样一来,您无需在每次应用数据发生变化时更新界面,因为观察者会替您完成更新。
2、不会发生内存泄漏
观察者会绑定到 Lifecycle 对象,并在其关联的生命周期遭到销毁后进行自我清理。
3、不会因 Activity 停止而导致崩溃
如果观察者的生命周期处于非活跃状态(如返回栈中的 Activity),则它不会接收任何 LiveData 事件。
4、不再需要手动处理生命周期
界面组件只是观察相关数据,不会停止或恢复观察。LiveData 将自动管理所有这些操作,因为它在观察时可以感知相关的生命周期状态变化。
5、数据始终保持最新状态
如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。
6、适当的配置更改
如果由于配置更改(如设备旋转)而重新创建了 Activity 或 Fragment,它会立即接收最新的可用数据。
7、共享资源
您可以使用单例模式扩展 LiveData 对象以封装系统服务,以便在应用中共享它们。LiveData 对象连接到系统服务一次,然后需要相应资源的任何观察者只需观察 LiveData 对象。
三、使用LiveData对象
请按照以下步骤使用 LiveData 对象:
- 创建
LiveData的实例以存储某种类型的数据。这通常在ViewModel类中完成。 - 创建可定义
onChanged()方法的Observer对象,该方法可以控制当LiveData对象存储的数据更改时会发生什么。通常情况下,您可以在界面控制器(如 Activity 或 Fragment)中创建Observer对象。 - 使用
observe()方法将Observer对象附加到LiveData对象。observe()方法会采用LifecycleOwner对象。这样会使Observer对象订阅LiveData对象,以使其收到有关更改的通知。通常情况下,您可以在界面控制器(如 Activity 或 Fragment)中附加Observer对象。
另外:
您可以使用 observeForever(Observer)方法在没有关联的 LifecycleOwner对象的情况下注册一个观察者。在这种情况下,观察者会被视为始终处于活跃状态,因此它始终会收到关于修改的通知。您可以通过调用 removeObserver(Observer) 方法来移除这些观察者。这个就是Github上的库LiveEventBus的基本实现原理。
1、创建LiveData对象
LiveData 是一种可用于任何数据的封装容器,其中包括可实现 Collections 的对象,如 List。LiveData对象通常存储在 ViewModel对象中,并可通过 getter 方法进行访问,如以下示例中所示:
class UserInfoViewModel :ViewModel() {
//延迟创建对象
val userInfoLiveData:MutableLiveData<UserInfo> by lazy {
//创建LiveData
MutableLiveData<UserInfo>()
}
}
最初,LiveData 对象中的数据并未经过设置。
注意:
请确保用于更新界面的 LiveData 对象存储在 ViewModel 对象中,而不是将其存储在 Activity 或 Fragment 中,原因如下:
- 避免 Activity 和 Fragment 过于庞大。现在,这些界面控制器负责显示数据,但不负责存储数据状态。
- 将
LiveData实例与特定的 Activity 或 Fragment 实例分离开,并使LiveData对象在配置更改后继续存在。
2、观察和更新LiveData对象
在大多数情况下,应用组件的 onCreate() 方法是开始观察 LiveData对象的正确着手点,原因如下:
- 确保系统不会从 Activity 或 Fragment 的
onResume()方法进行冗余调用。 - 确保 Activity 或 Fragment 变为活跃状态后具有可以立即显示的数据。一旦应用组件处于
STARTED状态,就会从它正在观察的LiveData对象接收最新值。只有在设置了要观察的LiveData对象时,才会发生这种情况。
通常,LiveData 仅在数据发生更改时才发送更新,并且仅发送给活跃观察者。此行为的一种例外情况是,观察者从非活跃状态更改为活跃状态时也会收到更新。此外,如果观察者第二次从非活跃状态更改为活跃状态,则只有在自上次变为活跃状态以来值发生了更改时,它才会收到更新。
以下示例代码说明了如何开始观察和更新 LiveData 对象:
class MainActivity : AppCompatActivity() {
//创建ViewModel对象
private val userInfoViewModel: UserInfoViewModel by lazy {
UserInfoViewModel()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var textview = findViewById<TextView>(R.id.tv_start)
//请求数据
userInfoViewModel.requestUserInfo()
//通知修改数据
userInfoViewModel.userInfoLiveData.observe(this, Observer<UserInfo> {
//拿到了请求到的UserInfo对象,通知修改数据
textview.text = it?.name
})
}
}
ViewModel中请求数据并设置给LiveData
class UserInfoViewModel : ViewModel() {
//延迟创建对象
val userInfoLiveData: MutableLiveData<UserInfo> by lazy {
//创建LiveData
MutableLiveData<UserInfo>()
}
/**
* 请求用户信息
*/
fun requestUserInfo() {
//伪代码:请求到了用户信息
var userInfo = UserInfo("zhangsan", 18)
//将请求到的值userInfo设置给LiveData,更新数据
userInfoLiveData.value = userInfo
}
}
注意:您必须调用 setValue(T) 方法以从主线程更新 LiveData 对象。如果在子线程中执行代码,您可以改用 postValue(T) 方法来更新 LiveData 对象。
四、将LiveData与Room一起使用
Room封装了返回LiveData的API,子线程查询,主线程返回LiveData。
五、将协程与LiveData一起使用
LiveData 支持 Kotlin 协程。可以使用 liveData 构建器函数调用 suspend 函数(挂起函数),并将结果作为 LiveData 对象传送。在以下示例中,loadUser() 是在其他位置声明的挂起函数。使用 liveData 构建器函数异步调用 loadUser(),然后使用 emit() 发出结果:
val user: LiveData<User> = liveData {
val data = database.loadUser() // loadUser是一个挂起函数
emit(data) //将数据发射出去并得到一个LiveData返回值
}
当 LiveData 变为活动状态时,代码块开始执行;当 LiveData 变为非活动状态时,代码块会在可配置的超时过后自动取消。如果代码块在完成前取消,则会在 LiveData 再次变为活动状态后重启;如果在上次运行中成功完成,则不会重启。请注意,代码块只有在自动取消的情况下才会重启。如果代码块由于任何其他原因(例如,抛出 CancellationException)而取消,则不会重启。
您还可以从代码块中发出多个值。每次 emit() 调用都会挂起代码块的执行,直到在主线程上设置 LiveData 值。
val user: LiveData<Result> = liveData {
//发射Loading
emit(Result.loading())
try {
//发射请求成功的结果
emit(Result.success(fetchUser()))
} catch(ioException: Exception) {
//发射异常事件
emit(Result.error(ioException))
}
}
更多这方面的内容见Android Developers官网
六、转换LiveData
将LiveData中存储的值进行转换,转换后返回不同的LiveData实例。Lifecycle依赖包中提供Transformations类可用于转换。
1、Transformations.map()
val userLiveData: LiveData<User> = UserLiveData()
val userName: LiveData<String> = Transformations.map(userLiveData) {
user -> user.name //对象转String
}
2、Transformations.switchMap()
//存储String的LiveData
private val userNameLiveData: MutableLiveData<String> = MutableLiveData<String>()
//存储UserInfo对象的LiveData
private val userInfoLiveData =
Transformations.switchMap(userNameLiveData, object : Function<String, LiveData<UserInfo>> {
override fun apply(input: String?): LiveData<UserInfo> {
return getUser(input)
}
})
//定义的二个LiveData的转换方法
private fun getUser(name: String?): LiveData<UserInfo> {
var userInfo = UserInfo(name, 18)
var userLiveData = MutableLiveData<UserInfo>()
userLiveData.value = userInfo
return userLiveData
}
3、转换中需要注意的情况
应该避免在ViewModel中创建方法并返回LiveData,如下:
fun transMap():LiveData<String> {
...
}
因为每次返回的是一个新的LiveData实例,LiveData的通知就不是同一个对象。
如果ViewModel对象中需要有Lifecycle对象,那么建议用LiveData的转换功能。因为ViewModel比Activity和Fragment生命周期长,所以不要持有任何它们的引用,否则就会造成内存泄漏。
4、LiveData通知的关联性
class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() {
private val addressInput = MutableLiveData<String>()
val postalCode: LiveData<String> = Transformations.switchMap(addressInput) {
//转换的方法
address -> repository.getPostCode(address) }
//用户手动输入
private fun setInput(address: String) {
//用户手动输入的值存储在了addressInput这个LiveData中
addressInput.value = address
}
}
postalCode 字段定义为 addressInput 的转换,只要 addressInput 发生更改,postalCode就会重新计算并检索该字段的值,也就会相应的有通知。
七、合并LiveData数据源
MediatorLiveData是 LiveData的子类,可以合并多个 LiveData 源,只要任何原始的 LiveData 源对象发生更改,就会触发 MediatorLiveData 对象的观察者,所以只需观察 MediatorLiveData 对象即可收到多个合并源头的更新。
八、扩展LiveData
因为ViewModel是没有生命周期感知能力的,但是LiveData有,有时候LiveData已经处于活跃状态,可能这时候需要做一些事情,但是这时候ViewModel没有生命周期回调并不能触发获取数据的方法,那么就可以扩展LiveData。看下官方的例子:
class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
//获取管理器
private val stockManager = StockManager(symbol)
//创建回调
private val listener = { price: BigDecimal ->
//给LiveData设置值
value = price
}
//重写被激活时的方法
override fun onActive() {
//请求价格
stockManager.requestPriceUpdates(listener)
}
//重写没有激活时的方法
override fun onInactive() {
//移除回调的监听器
stockManager.removeUpdates(listener)
}
}
直接在Fragment或者Activity中调用这个LiveData
public class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//创建LiveData
val myPriceLiveData: LiveData<BigDecimal> = ...
//观察这个LiveData
myPriceLiveData.observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? ->
// Update the UI.
})
}
}
如果观察者的生命周期处于 STARTED或 RESUMED状态,则 LiveData 会认为该观察者处于活跃状态。- 当 LiveData 对象具有活跃观察者时,会调用 onActive()方法。当 LiveData 对象没有任何活跃观察者时,会调用 onInactive()方法。
九、LiveData是如何和生命周期绑定并激活的
//回调在主线程,传入LifecycleOwner对象
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
assertMainThread("observe");
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
//创建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;
}
//添加了生命周期的新的观察wrapper
owner.getLifecycle().addObserver(wrapper);
}
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
@NonNull
final LifecycleOwner mOwner;
LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
super(observer);
mOwner = owner;
}
@Override
boolean shouldBeActive() {
//如果STARTed状态需要激活返回true,否则返回false
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}
@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;
//激活状态改变的回调,传入了一个boolean值
activeStateChanged(shouldBeActive());
currentState = mOwner.getLifecycle().getCurrentState();
}
}
@Override
boolean isAttachedTo(LifecycleOwner owner) {
return mOwner == owner;
}
@Override
void detachObserver() {
mOwner.getLifecycle().removeObserver(this);
}
}
void activeStateChanged(boolean newActive) {
if (newActive == mActive) {
return;
}
// immediately set active state, so we'd never dispatch anything to inactive
// owner
mActive = newActive;
//如果需要激活传入1,不需要激活传入-1
changeActiveCounter(mActive ? 1 : -1);
if (mActive) {
dispatchingValue(this);
}
}
@MainThread
void changeActiveCounter(int change) {
int previousActiveCount = mActiveCount;
//传入1或者-1会在这里计算
mActiveCount += change;
if (mChangingActiveState) {
return;
}
mChangingActiveState = true;
try {
while (previousActiveCount != mActiveCount) {
//通过计算结果判断是否需要激活或者不激活
boolean needToCallActive = previousActiveCount == 0 && mActiveCount > 0;
boolean needToCallInactive = previousActiveCount > 0 && mActiveCount == 0;
previousActiveCount = mActiveCount;
if (needToCallActive) {
//激活
onActive();
} else if (needToCallInactive) {
//不激活
onInactive();
}
}
} finally {
mChangingActiveState = false;
}
}
以上就是一些粗略的分析。
十、setValue和postValue
1、区别
postValue 与 setValue 一样都是用来更新 LiveData 数据的方法:
- setValue 只能在主线程调用,同步更新数据
- postValue 可在后台线程调用,其内部会切换到主线程调用 setValue
2、postValue收不到消息
看下源码:
/**
* Posts a task to a main thread to set the given value. So if you have a following code
* executed in the main thread:
* <pre class="prettyprint">
* liveData.postValue("a");
* liveData.setValue("b");
* </pre>
* The value "b" would be set at first and later the main thread would override it with
* the value "a".
* <p>
* If you called this method multiple times before a main thread executed a posted task, only
* the last value would be dispatched.
*
* @param value The new value
*/
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
postTask = mPendingData == NOT_SET;
//将新的值赋值给mPendingData
mPendingData = value;
}
if (!postTask) {
return;
}
//mPostValueRunnable回调到主线程执行
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
方法的注释上说的很清楚:连续调用 postValue 时,有可能只会收到最后一次数据更新通知。
那么原因是什么?
在调用postValue时会将新的值赋值给mPendingData(如上所示)。然后会将一个mPostValueRunnable回调到主线程执行(如上所示),那接下来看下mPostValueRunnable源码:
private final Runnable mPostValueRunnable = new Runnable() {
@SuppressWarnings("unchecked")
@Override
public void run() {
Object newValue;
synchronized (mDataLock) {
//将mPendingData赋值给newValue
newValue = mPendingData;
mPendingData = NOT_SET;
}
//最终将newValue传入了setValue中
setValue((T) newValue);
}
};
源码中将mPendingData赋值给newValue,然后调用setValue((T) newValue)方法,将newValue传入了setValue方法中。继续看setValue的源码:
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
static void assertMainThread(String methodName) {
//如果不是主线程直接抛异常
if (!ArchTaskExecutor.getInstance().isMainThread()) {
throw new IllegalStateException("Cannot invoke " + methodName + " on a background"
+ " thread");
}
}
重点在在于assertMainThread方法,如果不是在主线程会抛出异常,方法终止,所以也就不会更新UI,真正更新UI的地方是在LiveData激活时会再次调用postValue方法。
3、为什么Runnable 只 post 一次
这个问题其实很清晰了,不在主线程不可见,子线程多次postValue更新UI没有意义,目的就是为了节省线程开销。
4、替代方案
1)使用RxJava替换LiveData
如何避免在多线程环境下不漏掉任何一个通知? 比较好的思路是借助 RxJava 这样的流式框架,任何数据更新都以数据流的形式发射出来,这样就不会丢失了。
fun <T> Observable<T>.toLiveData(): LiveData<T> = RxLiveData(this)
class RxLiveData<T>(
private val observable: Observable<T>
) : LiveData<T>() {
private var disposable: Disposable? = null
override fun onActive() {
disposable = observable
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
setValue(it)
}, {
setValue(null)
})
}
override fun onInactive() {
disposable?.dispose()
}
}
2)kotlin的Flow
用Flow取代LiveData。
参考博客:
一道面试题:介绍一下 LiveData 的 postValue ?