Android Jetpack 之使用 livedata

Android Jetpack 之使用 livedata

LiveData 类是 Android Jetpack 的重要组成部分。将数据封装为 LiveData 后,数据变为了可以感知组件生命周期的可观察数据类。

LiveData 的优势

确保界面符合数据状态

使用观察者模式在数据源改变时自动更新界面。

不会发生内存泄漏

观察者会绑定到 Lifecycle 对象,在组件生命周期结束后自动清理 。

不会因为 Activity 停止而导致崩溃

如果观察者绑定的 Activity 处于非活跃状态,例如处于返回栈的 Activity,观察者不会收到 LiveData 事件。

不需要手动处理生命周期

界面组件不用停止或者恢复观察数据,LiveData 能感知生命周期的变化,自动管理这些操作。

数据始终保持最新状态

界面组件从非活跃状态到活跃状态后会立即接收到最新的数据。例如后台的 Activity 切换到前台后会立即收到最新的数据。

适当的配置变更

如果由于配置变更,比如旋转屏幕,重新创建了 Activity 或者 Fragment,它会接收到最新的可用数据。

共享资源

可以使用单例模式扩展 LiveData 以封装系统服务,以便在应用中共享它们。LiveData 对象连接到系统服务一次,然后需要共享资源的观察者只需观察 LiveData 对象。

LiveData 用法

创建 LiveData

LiveData 是抽象类,通常使用它的子类 MutableLiveData 封装原有的数据类,比如 MutableLiveData。

使用 by lazy 延迟初始化。

class NameViewModel : ViewModel() {
    val currentName: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }
}

观察 LiveData

使用 observe 方法观察 LiveData,传入两个参数,LifecycleOwner 和 Observer。

Observer 使用 lambda 的形式。

class NameActivity : AppCompatActivity() {
    companion object {
        private const val TAG = "NameActivity"
    }

    private val nameViewModel by viewModels<NameViewModel>()

    private lateinit var binding: ActivityNameBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityNameBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)

        nameViewModel.currentName.observe(this) { newName ->
            binding.tvName.text = newName
            Log.d(TAG, "new name:$newName")
        }

        binding.btnClick.setOnClickListener {
            nameViewModel.onUserClick()
        }
    }
}

更新 LiveData

通过 setValue 方法更新 LiveData。如果是在子线程,使用 postValue 方法更新 LiveData。postValue 方法使用 Handler 将 Runnable 消息转发到主线程处理。

class NameViewModel : ViewModel() {
    val currentName: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }

    fun onUserClick() {
        val cur: String? = currentName.value
        val next: Int = if (cur != null) {
            cur.toInt() + 1
        } else {
            1
        }
        // setValue
        currentName.value = next.toString()
    }
}

LiveData 进阶用法

除了基本的 observe 观察 和 setValue 更新,LiveData 还提供了一些进阶用法。

扩展 LiveData

onActive 和 onInActive

onActive 和 onInActive 是 LiveData 的两个生命周期方法。

Actvity onStart 之后会调用 LiveData 的 onActive。

Activity onStop 之前会调用 LiveData 的 onInactive。

class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
    companion object {
        private const val TAG = "StockLiveData"
    }

    private val stockManager = StockManager(symbol)

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

    override fun onActive() {
        Log.d(TAG, "onActive")
        stockManager.requestPriceUpdates(listener)
    }

    override fun onInactive() {
        Log.d(TAG, "onInactive")
        stockManager.removeUpdates(listener)
    }
}
单例

LiveData 也可以作为单例使用,比如监听网络状态的变化。

使用 ::sInstance.isInitialized 方法引用判断单例是否初始化过。

在 onActive 和 onInActive 方法分别注册和放注册网络广播接收器。

class NetworkWatchLiveData : LiveData<NetworkInfo?>() {
    companion object {
        private lateinit var sInstance: NetworkWatchLiveData

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

    private val mContext: Context = BaseApp.instance

    private val mNetworkReceiver: NetworkReceiver = NetworkReceiver()

    private val mIntentFilter: IntentFilter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)

    override fun onActive() {
        super.onActive()
        mContext.registerReceiver(mNetworkReceiver, mIntentFilter)
    }

    override fun onInactive() {
        super.onInactive()
        mContext.unregisterReceiver(mNetworkReceiver)
    }

    private class NetworkReceiver : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            if (context == null) {
                return
            }
            val manager =
                context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
            val activeNetworkInfo = manager.activeNetworkInfo
            get().postValue(activeNetworkInfo)
        }

    }
}

在 Activity 观察 NetworkWatchLiveData 单例。

    private fun initGlobalObserver() {
        NetworkWatchLiveData.get().observe(this) {
            Log.i(TAG, "network change: $it")
        }
    }

转换 LiveData

Transformations.map()

Transformations.map 用来将一个 LiveData 通过 map 函数转换为另一个 LiveData。

    var userLiveData: MutableLiveData<User> = UserLiveData()

    var userNameLiveData: LiveData<String> = Transformations.map(userLiveData) { user ->
        "${user.nickName} ${user.userName}"
    }

观察转换后的 LiveData。

        // observe transformations map user
        nameViewModel.userNameLiveData.observe(this) { userName ->
            Log.d(TAG, "name:$userName")
        }

改变源 LiveData 的 value。

    fun transformationsMap() {
        userLiveData.value = User(
            false,
            null,
            null,
            null,
            1000,
            "rito",
            "ritori",
            "long"
        )
    }

观察到 map 后的 LiveData 打出 log。

2021-12-08 12:36:46.234 17539-17539/io.github.caoshen.androidadvance.jetpack D/NameActivity: name:rito long
Transformations.map 的原理
    @MainThread
    @NonNull
    public static <X, Y> LiveData<Y> map(
            @NonNull LiveData<X> source,
            @NonNull final Function<X, Y> mapFunction) {
        final MediatorLiveData<Y> result = new MediatorLiveData<>();
        result.addSource(source, new Observer<X>() {
            @Override
            public void onChanged(@Nullable X x) {
                result.setValue(mapFunction.apply(x));
            }
        });
        return result;
    }

查看 Transformations.map 的实现,可以看出使用了 MediatorLiveData 增加 source。在 source LiveData 的 onChange 回调调用 map 函数,得到转换后的结果。

Transformations.switchMap()

和 map 方法类似,switchMap 将 LiveData 和 map 函数转换得到新的 LiveData。

不同的是,switchMap 的 map 函数返回值是 LiveData 类型。

    private val userIdLiveData: MutableLiveData<String> = MutableLiveData("1000")

    val userIdSwitch = Transformations.switchMap(userIdLiveData) { id ->
        getUser(id)
    }

    private fun getUser(id: String): LiveData<String> {
        return MutableLiveData<String>("${id}-001")
    }

以下是一个错误的写法:

class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() {

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

这种写法每次都会生成一个新的 LiveData,UI 界面需要每次反注册之前的 LiveData,再注册新的 LiveData。当 UI 重建时,它会再次调用 getPostCode,而不是复用之前的结果。

Transformations.switchMap 的原理

switchMap 方法也是使用 MediatorLiveData 实现的。

如果 switchMapFunction 转换后的 LiveData 和当前的 mSource 相同,说明此 source LiveData 已经存在,不需要转换。

否则从 MedaitorLiveData 删除之前的 LiveData,添加新的 source。

    @MainThread
    @NonNull
    public static <X, Y> LiveData<Y> switchMap(
            @NonNull LiveData<X> source,
            @NonNull final Function<X, LiveData<Y>> switchMapFunction) {
        final MediatorLiveData<Y> result = new MediatorLiveData<>();
        result.addSource(source, new Observer<X>() {
            LiveData<Y> mSource;

            @Override
            public void onChanged(@Nullable X x) {
                LiveData<Y> newLiveData = switchMapFunction.apply(x);
                if (mSource == newLiveData) {
                    return;
                }
                if (mSource != null) {
                    result.removeSource(mSource);
                }
                mSource = newLiveData;
                if (mSource != null) {
                    result.addSource(mSource, new Observer<Y>() {
                        @Override
                        public void onChanged(@Nullable Y y) {
                            result.setValue(y);
                        }
                    });
                }
            }
        });
        return result;
    }

合并 LiveData

可以使用 MediatorLiveData 类合并多个 LiveData。当其中一个 source 变化后,更新整个 MediatorLiveData。

例如,将网络请求和本地数据库查询的数据合并为一个 MediaLiveData,对外被观察。

class ApiViewModel : BaseViewModel() {

    private val dbLiveData = StateLiveData<List<WxArticleBean>>()

    private val apiLiveData = StateLiveData<List<WxArticleBean>>()

    val mediatorLiveData: MediatorLiveData<ApiResponse<List<WxArticleBean>>> =
        MediatorLiveData<ApiResponse<List<WxArticleBean>>>()
            .apply {
                addSource(apiLiveData) {
                    this.value = it
                }
                addSource(dbLiveData) {
                    this.value = it
                }
            }

}

LiveData 的原理

LiveData 本质上使用了观察者模式。当调用 observe 方法时,将第二个参数 observer put 到 LiveData 的 mObservers,mObservers 是一个 SafeIterableMap,保存了所有观察者。

最后一行将 Observer 和 LifecycleOwner,也就是 Activity 的生命周期关联到一起。因此 LiveData 可以感知页面的生命周期。

    @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;
        }
        owner.getLifecycle().addObserver(wrapper);
    }

当调用 setValue 或者 postValue 时,会将 LiveData 封装的数据分发给每个 Observer。

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

遍历 mObservers,分发 value。

    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;
    }

当 observer 不是 active 活跃状态,或者 observer 的最新版本大于等于 LiveData 版本时,都不会收到通知。

最后更新 observer 版本,回调 onChanged 方法。

    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);
    }

LiveData 存在的问题和解决方法

粘性事件

粘性事件是指被观察者先发送事件,观察者后订阅事件,也能收到事件通知。事件一直存在。

因为 LiveData 和 Activity 的生命周期绑定,Activity 在重新构建时立即收到一次 observe 通知,即使用户没有显示发送通知。

这在某些一次性使用事件的场景会存在问题,比如 Activity 跳转。

以下的例子中,点击按钮改变 LiveData 的值,然后收到 LiveData 通知,跳转到 DetailsActivity。这种场景是正常的。

但是,当旋转手机后,Activity 重建,再次收到通知,发生跳转。这种场景不是我们预期的,因为正常情况只有点击才发生跳转。

NameActivity

class NameActivity : AppCompatActivity() {
    companion object {
        private const val TAG = "NameActivity"
    }

    private val nameViewModel by viewModels<NameViewModel>()

    private lateinit var binding: ActivityNameBinding

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

        nameViewModel.navigateToDetails.observe(this) {
            Log.d(TAG, "is navigate:$it")
            if (it) {
                Log.d(TAG, "go to details")
                startActivity<DetailsActivity>()
            }
        }
    }
}

NameViewModel

class NameViewModel : ViewModel() {
    ...

    private val _navigateToDetails = MutableLiveData<Boolean>()

    val navigateToDetails: LiveData<Boolean>
        get() = _navigateToDetails

    fun userClickOnButton() {
        _navigateToDetails.value = true
    }
}

问题发生的日志如下。当 NameActivity 走到 onStart 之后立即收到了 is navigate 的通知,跳转到详情页。

D/NameActivity: onPause
D/NameActivity: onStop
D/NameActivity: onCreate
D/NameActivity: onStart
D/NameActivity: is navigate:true
D/NameActivity: go to details
D/NameActivity: onResume
D/NameActivity: onPause
D/NameActivity: onStop

SingleLiveEvent

为了解决 Activity 重建后收到 observe 通知的问题,使用 SingleLiveEvent 替换直接使用 MutableLiveData。

class NameViewModel : ViewModel() {
    ...

    private val _navigateToDetails = SingleLiveEvent<Boolean>()

    val navigateToDetails: SingleLiveEvent<Boolean>
        get() = _navigateToDetails

    fun userClickOnButton() {
        _navigateToDetails.value = true
    }
}

SingleLiveEvent 的实现如下。

它通过内部的 mPending 判断事件是否已经发送过。

如果调用了 setValue,mPending 会被设置为 true,observer 判断当前 mPending 为 true,让 mPending 重置为 false,然后回调 onChanged。

如果 LiveData 第二次触发了 observe,mPending 还是为 false,不会回调 onChanged。

class SingleLiveEvent<T> : MutableLiveData<T>() {
    companion object {
        const val TAG = "SingleLiveEvent"
    }

    private val mPending: AtomicBoolean = AtomicBoolean(false)

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        if (hasActiveObservers()) {
            Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
        }
        super.observe(owner) { t ->
            if (mPending.compareAndSet(true, false)) {
                observer.onChanged(t)
            }
        }
    }

    @MainThread
    override fun setValue(value: T?) {
        mPending.set(true)
        super.setValue(value)
    }

    @MainThread
    fun call() {
        value = null
    }
}

还有一些其他的方法也能解决 LiveData 存在的问题,可以看下这篇文章:

[译] 在 SnackBar,Navigation 和其他事件中使用 LiveData(SingleLiveEvent 案例)

总结

LiveData 将数据封装为了可以感知组件生命周期的可观察数据类,不需要再手动处理生命周期以及内存泄漏问题。但是在某些场景不需要收到 LiveData 的二次通知,可以通过封装为单次事件通知解决。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值