Android-LiveData

传统回调方式

回调是一种动态调用方式,也就是调用的地方并不知道调用的代码具体有何作用,只是遵守了一个调用契约,在某种情况下就需要调用布局,最常见的回调就是观察者模式的监听器回调了。

这里所说的调用契约,具体一点其实就是一个接口,而回调的过程就是将这个接口存入调用类或者通过方法参数传入调用栈中,执行到某种情况时,就调用这个接口,因此是面向接口编程的,因此这个接口的实现只有在传入这个接口参数的地方才知道,但调用的地方是不知道接口实现中的具体逻辑的。

常用的面向对象高级语言中,直接提供了接口这种数据类型,一般来说我们定义的回调就是接口类型中的函数或者说方法。

接口回调的困境

做过重构的人都是程序员中最有耐心的那么一批人,因为他们不只是需要想办法完成需求,还需要在对业务逻辑无影响的情况下改变代码的结构,以求代码更多容易被读懂也更容易被修改。这里讲到了重构中一个非常重要的点——对业务逻辑无影响,而让代码更容易被修改。这需要让代码更具扩展性,而扩展性经常需要使用接口化来实现。面向接口是面向对象编程的一种默认规范,但重构时可以世界一片和平,业务再开发起来可能就像野马践踏小麦地了,经常会发生接口定义好之后,业务开发时可能需要新增一种场景或新增一种状态,就会在接口中新增一个方法,以此接口会急剧膨胀。由于接口实现的要求中,所有的接口实现类都需要实现接口中定义的所有方法,这将会导致接口实现中有非常多的空实现。有人说用抽象类不就好了?事实是如果抽象类这么好用的话,接口这个数据类型也就没有存在的必要了。使用抽象类是需要实现类继承这个抽象类的,而几乎所有的面向对象语言中都强制要求一个类有且只能有一个继承父类。在重构过程中,我们一般不会修改实现类的继承结构体系,只是经常用接口来扩展原有类。

除了接口膨胀问题,由于接口的实现都是实类,因此很多时候这些实类创建出来的对象会有对其他类的直接引用或间接引用,这些接口的对象可能会被传至任意地方,导致其生命周期变长,从而被内部被引用的对象生命周期也变长,尤其像Android中的Context系列的大对象,生命周期被回调对象变长后即会产生内存泄漏。

一个接口方法+多状态

状态变化场景是一种非常常见的回调场景,理论来说任何回调场景都可被广义地理解成状态变化回调,没有状态的场景能够被推广成只有一种状态的场景。传统接口回调是用多方法来区别不同状态的回调,比如多媒体播放的状态变化,用监听接口的多个方法分别表示播放器的状态变化。这样的设计就会导致前面说到的接口回调的困境,即是新增一种状态就需要更改接口。更好的设计应该是将状态作为接口方法的一个方法被传到监听实现来,这样可以让所有的状态回调方法收敛至同一个方法中,并且需要新增状态时就只需要在状态字段中新增一个常量,不关心这个新增状态的实现对这个新增也没有感知。

接口实现对外部类的引用

在面向对象编程中,内部类对外部类一般都强制持有了一个引用,就算没有类似Outer.this这种代码,内部类生成的字节码中,内部类中都有一个属性是外部类的引用。接口实现对外部类的引用一般的解决方式就是将内部类定义成单独的类或者静态内部类,将需要使用的外部类通过set或者构造器传进来,在相应的地方将引用属性设置为null,或者将引用属性定义成WeakReference类型的。

Lifecycle框架在事件注册分发时其实是需要Activity/Fragment的,但Lifecycle是没有持有对Activity/Fragment的强引用的,也没有设置null的地方,具体原因是LifecycleRegistry中对LifecycleOwner的引用是WeakReference类型的,而这个LifecycleOwner的实现类就是Activity/Fragment。

LiveData简单使用

import android.annotation.SuppressLint
import android.os.Bundle
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class LiveDataDemoActivity: AppCompatActivity() {

    @SuppressLint("SetTextI18n")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val contentView = LinearLayout(this)
        contentView.orientation = LinearLayout.VERTICAL
        val status = MutableLiveData<Int>()
        status.value = 10
        val text = TextView(this)
        text.text = "status = ${status.value}"
        contentView.addView(text)
        val btn1 = Button(this)
        status.observe(this, Observer {
            text.text = "status = ${status.value}"
        })
        btn1.setOnClickListener {
            GlobalScope.launch {
                delay(1000L)
                status.postValue(status.value!! + 10)
            }
        }
        btn1.text = "add 10"
        contentView.addView(btn1)
        val btn2 = Button(this)
        btn2.setOnClickListener {
            GlobalScope.launch {
                delay(2000L)
                status.postValue(status.value!! + 20)
            }
        }
        btn2.text = "add 20"
        contentView.addView(btn2)
        setContentView(contentView)
    }
}

在这个demo中,点击按钮时,延迟n ms后,将LiveData的值进行修改,LiveData的Observer就能收到修改的状态,可以看到修改的地方是在异步线程,而onChanged的监听回调是在main线程中的。

LiveData源码解析

LiveData的使用特别简单,这是因为其暴露出来的接口也较少。平时使用的也就四个接口,分别是setValue、getValue、postValue以及observer。

根据这四个方法的调用先后,查看其源码。首先是observer方法,其签名是

@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer)

也即这个方法需要在main线程中调用,传入参数分别是LifecycleOwner和Observer对象。在Lifecycle框架的源码分析中知道LifecycleOwner是生命周期拥有者,是一个接口,只有一个getLifecycle方法。返回的是一个LifecycleRegistry对象,这个LifecycleRegistry中用WeakReference来持有了Activity/Fragment的引用。这个observer方法的实现也比较简单,整体逻辑跟Lifecycle的observer方法比较相似,也是将传入的observer对象包装起来。这里是使用LifecycleBoundObserver将observer与Lifecycle生命周期回调绑定起来。

LifecycleBoundObserver继承于ObserverWrapper并且实现了GenericLifecycleObserver接口。LifecycleBoundObserver对GenericLifecycleObserver实现了onStateChanged方法,判断如果生命周期到达DESTROYED了,就将当前observer从监听列表中移除,否则调用activeStateChanged方法来通知状态的变化。activeStateChanged方法传入了一个newActive的boolean参数,这个值是用shouldBeActive方法返回的,shouldBeActive方法就是判断当前状态是否是STARTED及其之后的状态,这里也就是说LiveData是在active状态下才会通知变化,而这个active状态就是[STARTED,DESTORYED)区间中的状态。activeStateChanged方法调用dispatchingValue方法来通知LiveData的value变化。dispatchingValue方法中是调用considerNotify方法来具体调用通知方法onChanged的,在considerNotify方法中又做了几个状态判断,首先是状态是否是active的,然后状态observer的mLastVersion是否小于mVersion。mLastVerision是每次通知时将mVersion保存起来的,也就是mLastVersion是上一次修改通知的版本号,而mVersion只有在setValue中将其自增1,postValue中其实也是调用了setValue方法来更新值的。mLastVersion需要小于mVersion这个条件其实是说明每次值的修改只需要通知一次。而这里的Lifecycle状态变化时,也是需要通知value的变化的。也就是说当状态不是active的时候,就算修改了value,也不会通知onChanged,但这次修改会在状态变回active的时候回调通知方法。

再看看setValue这个方法。在setValue方法中,首先就判断了是否是在main线程中调用的。,然后将更新的value值存入mData变量中,再调用dispatchingValue方法通知observer的监听方法,这里的dispatchingValue就跟前面的Lifecycle通知一样了。

然后就是postValue方法了。

protected void postValue(T value) {
        boolean postTask;
        synchronized (mDataLock) {
            postTask = mPendingData == NOT_SET;
            mPendingData = value;
        }
        if (!postTask) {
            return;
        }
        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
    }

这里对mDataLock进行加锁,因为postValue是可以在异步线程中调用的,因此value的修改是有线程同步问题的。如果一个线程正在postValue,同时另一个线程也来postValue,那么第二个线程的postValue变会覆盖掉第一个线程的值。最后傅ArchTaskExecutor.getInstance().postToMainThread方法在main线程执行PostValueRunnable的run方法,这是使用ArchTaskExecutor来执行是因为LiveData框架希望我们可以重写这个postToMainThread。ArchTaskExecutor#postToMainThread方法中调用了mDelegate.postToMainThread方法,而mDelegate的默认值是DefaultTaskExecutor对象。DefaultTaskExecutor的postToMainThread方法就是直接调用Handler(Looper.getMainLooper())的post方法。ArchTaskExecutor有个setDelegate方法,可以替换掉默认的delegate,因此我们可以重写postToMainThread方法,来实现特定的在main线程中回调value修改的逻辑,比如将runnable post到MessageQueue的队首,让其能更快地执行。mPostValueRunnable就是个普通的Runnable对象,run方法中调用setValue方法更新值,并在setValue中去回调onChanged。

总结

LiveData提供了一种通用的数据类观察者模式,可能任意类型的数据进行变更观察,并内置了生命周期监听,只有在LiveData认为active的状态才会将value的change回调给observer。如果非active,则当Livecycle转换到active的时候,也会将change回调给observer。这也符合了Google的Android规范,只有在Activity/Fragment为active的时候,才通知数据的变化,进而触发UI的更新。并且LiveData是以变量的形式存在而非回调接口,这个LiveData变量可以跟随业务传至任意深的地方,还可以任意跨线程,因此LiveData内部就决定了onChanged的回调被post到了main线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值