【Android Jetpack】LiveData-观察数据的容器

文章目录

LiveData

ViewModel的主要作用是存放页面所需要的各种数据。我们在示例代码中定义了接口,当数据发生变化时,采用接口的方式实现对页面的通知。对此前面已经做了相关说明,通过接口的方式对页面进行通知是可行的,但如果要观察的数据很多,则需要定义大量的接口,代码会显得十分冗余。为此,Jetpack提供了LiveData组件。

LiveData是一个可被观察的数据容器类。具体说来,可以将LiveData理解为++一个数据的容器,它将数据包装起来,使数据成为被观察者,当该数据发生变化时,观察者能够获得通知++。我们不需要自己去实现观察者模式,LiveData内部已经默认实现好了,我们只要使用就可以了。

LiveData取代了上一章中所定义的接口,帮助我们完成ViewModel与页面之间的通信。

LiveData是一个可观察的数据持有者类。与常规的可观察对象不同,LiveData具有生命周期意识,这意味着它尊重其他应用程序组件的生命周期,如活动、片段或服务。这种意识确保LiveData只更新处于活动生命周期状态的应用程序组件观察员。//翻译于官方文档

LiveData是一种持有可被观察数据的类。和其他可被观察的类不同的是,LiveData是有生命周期感知能力的,这意味着它可以在activities,fragments,或者services生命周期是活跃状态时更新这些组件。那么什么是活跃状态呢?ViewModel中的**STARTEDRESUMED就是活跃状态,只有在这两个状态下LiveData是会通知数据变化的。**

要想使用LiveData(或者这种有可被观察数据能力的类)就必须配合实现了LifecycleOwner的对象使用。在这种情况下,当对应的生命周期对象DESTROYED时,才能移除观察者。这对Activity或者Fragment来说显得尤为重要,因为他们可以在生命周期结束的时候立刻解除对数据的订阅,从而避免内存泄漏等问题。

使用LiveData的优点:

  • UI和实时数据保持一致 因为LiveData采用的是观察者模式,这样一来就可以在数据发生改变时获得通知,更新UI
  • 避免内存泄漏,观察者被绑定到组件的生命周期上,当被绑定的组件销毁(destory)时,观察者会立刻自动清理自身的数据。
  • 不会再产生由于Activity处于stop状态而引起的崩溃 例如:当Activity处于后台状态时,是不会收到LiveData的任何事件的。
  • 不需要再解决生命周期带来的问题LiveData可以感知被绑定的组件的生命周期,只有在活跃状态才会通知数据变化。
  • 实时数据刷新,当组件处于活跃状态或者从不活跃状态到活跃状态时总是能收到最新的数据
  • 解决Configuration Change问题,在屏幕发生旋转或者被回收再次启动,立刻就能收到最新的数据。
  • 数据共享,如果对应的LiveData是单例的话,就能在app的组件间分享数据。

LiveData与ViewModel

ViewModel用于存放页面所需要的各种数据,不仅如此,我们还可以在其中放一些与数据相关的业务逻辑。例如,我们可以在ViewModel中进行数据的加工、获取等操作。因此,ViewModel中的数据可能会随着业务的变化而变化。对页面来说,它并不关心ViewModel中的业务逻辑,它只关心需要展示的数据是什么,并且希望在数据发生变化时,能及时得到通知并做出更新。LiveData的作用就是,在ViewModel中的数据发生变化时通知页面。因此,LiveData通常被放在ViewModel中

使用LiveData:

  • 创建一个持有某种数据类型的LiveData(通常是在ViewModel中)
  • 创建一个定义了onChange()方法的观察者。这个方法是控制LiveData中数据发生变化时,采取什么措施 (比如更新界面)。通常是在UI Controller(Activity/Fragment)中创建这个观察者。
  • 通过LiveData实例的observe()方法连接观察者和LiveDataobserve()方法需要携带一个LifecycleOwner类。这样就可以让观察者订阅LiveData中的数据,实现实时更新。

创建LiveData对象

LiveData是一个抽象类,不能直接使用。通常我们使用的是它的直接子类MutableLiveData。

LiveData是一个数据的包装。具体的包装对象可以是任何数据,包括集合(比如List)。LiveData通常在ViewModel中创建,然后通过getter方法获取。

kotlin 复制代码
class TimerViewModel : ViewModel() {
    private val timer: Timer = Timer()
    private var currentSecond: MutableLiveData<Int> = MutableLiveData() //创建LiveData对象
    fun start() {
        timer.schedule(timerTask {
            var value = currentSecond.value
            val plus = value?.plus(1)
            currentSecond.postValue(plus)
        }, 1000, 1000)
    }

    fun getCurrentSecond() : LiveData<Int> {
        return currentSecond
    }
    override fun onCleared() {
        super.onCleared()
        timer.cancel()
    }
}

观察LiveData中的数据

通常情况下都是在组件的onCreate()方法中开始观察数据,原因有以下两点:

  • 系统会多次调用onResume()方法
  • 确保Activity/Fragment在处于活跃状态时立刻可以展示数据。
kotlin 复制代码
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val tv = findViewById<TextView>(R.id.tv)
        val model = ViewModelProvider(this).get(TimerViewModel::class.java)
        // 得到ViewModel
        val liveData = model.getCurrentSecond() as MutableLiveData<Int>
        //获取LiveData,并调用LiveData实例的observe()方法
        liveData.observe(this, object : Observer<Int> {
            override fun onChanged(t: Int?) {
                tv.text = t.toString()
            }
        })
        // 重置
        liveData.postValue(0)
        model.start()
    }
}

更新LiveData对象

如果想要在UI Controller中改变LiveData中的值呢?(比如点击某个Button把性别从男设置成女)。LiveData并没有提供这样的功能,但是Architecture Component提供了MutableLiveData这样一个类,可以通过setValue(T)postValue(T)方法来修改存储在LiveData中的数据。MutableLiveDataLiveData的一个子类,从名称上也能看出这个类的作用。举个直观点的例子:

java 复制代码
mButton.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        String anotherName = "John Doe";
        mModel.getCurrentName().setValue(anotherName);
    }
})

调用setValue()方法就可以把LiveData中的值改为John Doe。同样通过这种方法修改LiveData中的值同样会触发所有对这个数据感兴趣的类。那么setValue()postValue()有什么不同呢?区别就是setValue()只能在主线程中调用,而postValue()可以在子线程中调用。

observeForever()

LiveData还提供了一个名为observeForever()的方法,使用起来与observe()没有太大差别。区别在于,当LiveData所包装的数据发生变化时,无论页面处于什么状态,observeForever()都能收到通知。(正所谓永远观察)因此,在用完之后,一定要记得调用removeObserver()方法来停止对LiveData的观察,否则LiveData会一直处于激活状态,Activity则永远不会被系统自动回收,这就造成了内存泄漏。

源码

还有两个重要的方法,分别是observe()方法和setValue()方法:

java 复制代码
    @MainThread
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        assertMainThread("observe");
        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;
        }
        // 将Observer与Activity的生命周期关联在一起。因此,LiveData能够感知页面的生命周期。它可以检测页面当前的状态是否为激活状态,或者页面是否被销毁。只有在页面处于激活状态(Lifecycle.State.ON_STARTED或Lifecycle.State.ON_RESUME)时,页面才能收到来自LiveData的通知,若页面被销毁(Lifecycle.State.ON_DESTROY),那么LiveData会自动清除与页面的关联,从而避免可能引发的内存泄漏问题。
        owner.getLifecycle().addObserver(wrapper);
    }
java 复制代码
    /**
     * Sets the value. If there are active observers, the value will be dispatched to them.
     * 设置值。如果存在活动的观察者,则值将被分派给他们。
     * <p>
     * This method must be called from the main thread. If you need set a value from a background
     * 必须从主线程调用此方法。如果需要从后台线程设置值
     * thread, you can use {@link #postValue(Object)}
     *
     * @param value The new value
     */
    @MainThread
    protected void setValue(T value) {
        assertMainThread("setValue");
        mVersion++;
        mData = value;
        dispatchingValue(null);
    }

    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(); ) {
                    // 遍历mObservers,调用considerNotify()更新数据
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }
java 复制代码
// 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.
    	//我们仍然首先检查observer.active,将其作为活动的入口。
        //因此,即使观察者移动到活动状态,如果我们没有收到该事件,我们最好不要通知更可预测的通知顺序。
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        //回调onChanged()方法
        observer.mObserver.onChanged((T) mData);
    }

Room和LiveData配合使用

Room可以返回LiveData的数据类型。这样对数据库中的任何改动都会被传递出去。这样修改完数据库就能获取最新的数据,减少了主动获取数据的代码。

//我还没学完就不展示了

继承LiveData扩展功能

LiveData的活跃状态包括:STARTED或者RESUMED两种状态。那么如何在活跃状态下把数据传递出去呢?下面是示例代码:

kotlin 复制代码
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)
    }
}

上面有三个重要的方法:

  • onActive(): 当LiveData对象具有活动观察者时调用该方法
  • onInactive(): 当LiveData对象没有任何活动观察者时调用该方法
  • setValue(T): 更新LiveData实例的值,并通知任何活动的观察者有关更改的信息

可以像下面这样使用StockLiveData:

kotlin 复制代码
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的实例,该fragment实现了LifecycleOwner接口。这样做是为了将observerLifecycle对象绑定到一起,这意味着:

  • 如果当前的Lifecycle对象不是处于活跃期,就算value值有改变也不会回调到observer
  • Lifecycle对象销毁后,observer对象也会自动移除,防止内存泄漏

实际上LiveData对象是适应生命周期也就意味着你需要在多个activities,fragmentsservices中进行共享,所以通常我们会将LiveData的示例设计成单例的:

kotlin 复制代码
class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
    private val stockManager: StockManager = StockManager(symbol)

    private val listener = { price: BigDecimal ->
        value = price
    }

    override fun onActive() {
        stockManager.requestPriceUpdates(listener)
    }

    override fun onInactive() {
        stockManager.removeUpdates(listener)
    }

    companion object {
        private lateinit var sInstance: StockLiveData

        @MainThread
        fun get(symbol: String): StockLiveData {
            sInstance = if (::sInstance.isInitialized) sInstance else StockLiveData(symbol)
            return sInstance
        }
    }

这样就可以在fragment中像如下这样使用:

kotlin 复制代码
class MyFragment : Fragment() {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        StockLiveData.get(symbol).observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? ->
            // Update the UI.
        })
    }
}    

转换LiveData

有时在LiveData分发给observers之前想要修改一下存储在LiveData中的值,或者你想根据当前的值进行修改返回另一个值。Lifecycle提供了Transformations类来通过里面的helper方法解决这种问题。

  • Transformations.map()

可以将LiveData中的数据进行改变:

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

LiveData中的User数据转换成String

  • Transformations.switchMap()
java 复制代码
private fun getUser(id: String): LiveData<User> {
  ...
}
val userId: LiveData<String> = ...
val user = Transformations.switchMap(userId) { id -> getUser(id) }

和上面的map()方法很像。区别在于传递给switchMap()的函数必须返回LiveData对象。

LiveData一样,Transformation也可以在观察者的整个生命周期中存在。只有在观察者处于观察LiveData状态时,Transformation才会运算Transformation是延迟运算的(calculated lazily),而生命周期感知的能力确保不会因为延迟发生任何问题。

如果在ViewModel对象的内部需要一个Lifecycle对象,那么使用Transformation是一个不错的方法。举个例子:假如有个UI组件接受输入的地址,返回对应的邮政编码。那么可以 实现一个ViewModel和这个组件绑定:

java 复制代码
class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() {

    private fun getPostalCode(address: String): LiveData<String> {
        // DON'T DO THIS
        return repository.getPostCode(address)
    }
}

有个// DON'T DO THIS(不要这么干),这是为什么?有一种情况是如果UI组件被回收后又被重新创建,那么又会触发一次repository.getPostCode(address),而不是重用上次已经获取到的查询。那么应该怎样避免这个问题呢?看一下下面的代码:

java 复制代码
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.value = address
    }
}

postalCode变量的修饰符是publicfinal,因为这个变量的是不会改变的。那输入不同的地址还总返回相同邮编?当然不是,postalCode这个变量存在的作用是把输入的addressInput转换成邮编,那么只有在输入变化时才会调用repository.getPostCode()方法。这就好比你用final来修饰一个数组,虽然这个变量不能再指向其他数组,但是数组里面的内容是可以被修改的。绕来绕去就一点:当输入是相同的情况下,用了switchMap()可以减少没有必要的请求。并且同样,只有在观察者处于活跃状态时才会运算并将结果通知观察者。

合并多个LiveData中的数据

MediatorLiveDataLiveData的子类,可以通过MediatorLiveData合并多个LiveData来源的数据。同样任意一个来源的LiveData数据发生变化,MediatorLiveData都会通知观察他的对象。说的有点抽象,举个栗子。比如UI接收来自本地数据库和网络数据,并更新相应的UI。可以把下面两个LiveData加入到MeidatorLiveData中:

  • 关联数据库的LiveData

  • 关联联网请求的LiveData

    相应的UI只需要关注MediatorLiveData就可以在任意数据来源更新时收到通知。

相关推荐
风和先行24 分钟前
adb 命令查看设备存储占用情况
android·adb
AaVictory.1 小时前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list
似霰2 小时前
安卓智能指针sp、wp、RefBase浅析
android·c++·binder
大风起兮云飞扬丶2 小时前
Android——网络请求
android
干一行,爱一行2 小时前
android camera data -> surface 显示
android
断墨先生2 小时前
uniapp—android原生插件开发(3Android真机调试)
android·uni-app
无极程序员4 小时前
PHP常量
android·ide·android studio
萌面小侠Plus5 小时前
Android笔记(三十三):封装设备性能级别判断工具——低端机还是高端机
android·性能优化·kotlin·工具类·低端机
慢慢成长的码农5 小时前
Android Profiler 内存分析
android
大风起兮云飞扬丶5 小时前
Android——多线程、线程通信、handler机制
android