Jetpack - LiveData

查看最新版本

一、概念

一个可以被观察的数据持有类,LiveData是只读版本,MutableLiveData是可读可写版本,。

  • 用来更新UI:setValue()和postValue()更新值都是在主线程执行,因此观察者的onChange()回调在主线程。
  • 生命周期感知:可以感知 Activity、Fragment或Service 等组件的生命周期,无需手动处理,UI层只需要对数据进行监听。
  • 单个最新值:更新UI需要使用最新数据,过时的数据应该被忽略。并且只会在界面处于活跃状态(onStart和OnResume)收到通知(非活跃状态更新UI无意义浪费资源)。
  • 自动取消订阅:Observers 是绑定到 Lifecycle 对象上的,当与其关联的 lifecycle 被销毁的时候,它们会自动被清理。开发者无需手写模板代码,降低内存泄漏风险。
  • 粘性事件:当按下Button弹出Snackerbar,此时设备配置改变(例如屏幕旋转),之前的View会销毁然后重建,新的View会重新订阅,那么会再次消费事件弹出Snackerbar。
  • 默认不防抖:更新的值和上次相同,onChange()依然会再次调用。可以使用Transformations的distinctUntilChanger()、SingleLiveEvent解决。

二、简单使用

2.1 添加依赖

最新版本

implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"

2.2 创建、更新

只能创建读写版本的 MutableLiveData,可以转换成只读版本的 LiveData,根据实际需求暴露不同类型收窄功能。

MutableLiveData()

public MutableLiveData()
public MutableLiveData(T value)

创建读写版本,是否设置默认值,默认为null。

postValue()

public void postValue(T value)

更新值,子线程调用。

setValue()

public void setValue(T value)

更新值,主线程调用。

getValue()

public @Nullable T getValue()

获取值。

ViewModel {
    private val _num = MutableLiveData(0)    //私有化可读可写版本
    val num: LiveData<Int> = _num    //对外暴露只读版本

    fun fetchData() {
        _num.value = 1  //读写
    }
}

2.3 观察

observe()

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

具有生命周期感知,处于活跃状态即 START 、RESUME 时才会接收事件,处于 DESTROYED 时观察者会被移除。

Activity {
    onCreate() {
        observeLiveData()
    }

    //聚合多个 LiveData 一起观察
    fun observeLiveData() {
        viewModel.num.observe(this) { num ->
            textView.text = num
        }
    }
}

三、进阶

3.1 转换 Transformations

通过 Transformations 类调用太麻烦,推荐使用 LiveData 的扩展函数版本。

map

public fun <X, Y> LiveData<X>.map(
    transform: (X) -> (Y)        //执行在主线程
): LiveData<Y> 

对数据源中的每个值应用转换函数,将得出的新值再派发给观察者。

switchMap

public fun <X, Y> LiveData<X>.switchMap(
    transform: (X) -> (LiveData<Y>)?        //执行在主线程
): LiveData<Y>

不要对一个返回LiveData的函数进行观察,每次调用该函数都会返回新的实例,UI组件需要取消原先的注册,再对新实例重新注册。应该创建一个LivaData用来存储输入值,通过 switchMap 调用该函数返回新的LiveData。

distinctUntilChanged更新的值和当前值相同则不会回调onChange()。

map

//把原值 User 改成 User.name
val userLiveData: LiveData<User> = MutableLiveData(User())
val nameLiveData: LiveData<String> = Transformations.map(userLiveData) { user ->
    user.name
}

switchMap

//不要这样做
fun getPostCode(address: String): LiveData<String> {}
viewModel.getPostCode("").observe(this) {...}

//而是这样做
private val addressLiveData = MatableLiveData("")    //私有化输入源
val postCodeLiveData = addressLiveData.switchMap { address ->
    repository.getPostCode(address)    //传值给返回LiveData的函数
}
fun setAddress(address: String) {    //外部调用
    addressLiveData.value = address
}

3.2 合并 MediatorLiveData

用来合并多个LivaData数据源,任何一个数据源发生变化都会触发。例如分别为本地和网络两个数据创建LiveData,通过MutableLiveData调用addSource()将两个LiveData数据源添加进去,最后订阅即可。

addSource()

public <S> void addSource(

    @NonNull LiveData<S> source,        //数据源 LiveData

    @NonNull Observer<? super S> onChanged        //数据源变化时回调

)

添加 LiveData 源。

removeSource()

public <S> void removeSource(

    @NonNull LiveData<S> toRemote

)

移除 LiveData 源。

//ViewModel
private val local = MutableLiveData<String>()
private val remote = MutableLiveData<String>()
private val _combine = MediatorLiveData<String>().apply {
    addSource(local) { value = it }
    addSource(remote) { value = it }
}
val combine: LiveData<String> = _combine
//UI
viewModel.combine.observe(this) { value -> }

2.4 异步生产

LifeCycle v2.20 版本后,可以在创建 LiveData 的同时,使用协程定义值的异步生产方式(由于只读无法后期更新,适用于一次性数据),就像 Flow 构建器一样只是没有那么多操作符可用。省去了对只读限制的封装,UI中直接观察ViewModel的该属性。

public fun <T> liveData(
    context: CoroutineContext = EmptyCoroutineContext,        //区块所执行的协程上下文,默认+Main.immediate
    timeoutInMs: Long = DEFAULT_TIMEOUT,        //非活跃状态时,多少毫秒后取消区块,默认5s
    block: suspend LiveDataScope<T>.() -> Unit        //存在活跃观察者时执行
): LiveData<T>

ViewModel {
    //直接调用挂起函数赋值
    val num1 = liveData { emit(getData()) }
    //还可以指定线程,单独写耗时操作
    val num2: LiveData<Int> = liveData(Dispatchers.IO) {
        emit(5)
        delay(1000)
        emit(3)
    }
    //从另一个LivaData获取更新结果
    val aa = MutableLiveData(10)
    val bb = liveData{ emitSource(aa) }
    suspend fun getData(): Int = withContext(Dispatchers.IO) { 3 }
    //与 Transformations 结合使用
    private val userId: LiveData<String> = MutableLiveData()
    val user = userId.switchMap { id ->
        liveData(Dispatchers.IO) {
            emit(database.loadUserById(id))
        }
    }
}

2.5 Flow 转 LiveData

public fun <T> Flow<T>.asLiveData(
    context: CoroutineContext = EmptyCoroutineContext,//区块所执行的协程上下文,默认+Main.immediate
    timeoutInMs: Long = DEFAULT_TIMEOUT        //没有观察者后,多少毫秒后取消区块,默认5s
): LiveData<T>
ViewModel {
    val num1: LiveData<Int> = liveData { DataSource().getDataFlow.collect { emit(it) } }
    val num2: LiveData<Int> = DataSource().getDataFlow.asLiveData()    //简写
}

DataSource {
    val getDataFlow: Flow<Int> = flow { repeat(3) { emit(it) } }
}

2.6 判断

removeObserve ( )移除当前所有观察者。
hasObserve ( )如果有观察者。
hasActiveObserve ( )如果有活跃的观察者(UI处于onStart或者onResume)。
<think>好的,我现在需要解决用户提到的关于lifecycle-livedata-core库与appcompat库中Toolbar资源冲突的问题。这个问题具体是系统错误地从lifecycle-livedata-core的R类中查找Toolbar_collapseIcon属性,而不是从appcompat的R类中查找。这通常会导致运行时错误,比如找不到资源或者属性。 首先,我应该理解这个问题的背景。在Android开发中,当不同的库模块或应用模块使用相同的资源名称时,可能会发生资源冲突。每个模块都会生成自己的R类,其中包含该模块的资源ID。但在构建过程中,这些资源会被合并到最终的APK中,并且资源ID可能会被重新分配。如果两个不同的库中的R类有相同的资源名称,可能会导致错误的资源被引用。 用户提到的lifecycle-livedata-core库错误地包含了R$styleable资源,这可能是因为该库在某个版本中错误地引入了本应由appcompat库提供的资源,比如Toolbar的collapseIcon属性。当应用运行时,系统可能错误地从lifecycle-livedata-core的R类中查找Toolbar_collapseIcon属性,而正确的应该是从appcompat的R类中查找。 接下来,我需要考虑可能的原因和解决方案。可能的原因包括依赖版本冲突、资源合并错误或库本身的bug。用户可能同时使用了lifecycle-livedata-core和appcompat库的不同版本,导致资源ID冲突。此外,构建工具在合并资源时可能没有正确处理,导致错误的R类被引用。 解决方案方面,首先应该检查依赖版本。确保使用的lifecycle-livedata-core和appcompat库版本是兼容的,并且没有已知的冲突。可能需要升级到较新的版本,因为库的维护者可能已经修复了这个问题。例如,检查AndroidX Lifecycle库的版本是否与appcompat库兼容。 其次,如果问题出现在特定版本的lifecycle-livedata-core中,可以尝试降级或升级该库到另一个版本,看看问题是否解决。同时,清理和重建项目可能有助于解决临时的构建问题,比如使用Gradle的clean任务,然后重新构建。 资源排除也是一种可能的解决方案。在Gradle配置中,可以排除lifecycle-livedata-core中的某些资源,如果确定这些资源确实不应该存在。例如: ```groovy implementation ("androidx.lifecycle:lifecycle-livedata-core:2.x.x") { exclude group: 'androidx.lifecycle', module: 'lifecycle-livedata-core' // 或者排除特定的资源 } ``` 不过,排除资源可能需要更深入的分析,确保排除后不会影响其他功能。 另一个可能性是命名空间冲突。在较新的Android Gradle插件版本中,支持使用命名空间来避免资源冲突。检查项目的build.gradle文件中是否启用了正确的命名空间设置,并确保每个模块都有唯一的命名空间。这有助于构建工具正确区分不同模块的资源。 此外,检查布局文件中是否正确引用了Toolbar_collapseIcon属性。确保在XML布局中使用的属性是来自appcompat库的命名空间,例如: ```xml <androidx.appcompat.widget.Toolbar xmlns:app="http://schemas.android.com/apk/res-auto" app:collapseIcon="@drawable/your_icon" /> ``` 而不是使用其他库的命名空间,或者省略命名空间导致默认查找错误的R类。 如果以上方法都无法解决问题,可能需要查看具体的错误日志和堆栈跟踪,确定资源引用的具体位置。检查是否有代码中直接引用了R类,而没有使用正确的包名。例如,在Java或Kotlin代码中,应该使用来自appcompat的R类,如`androidx.appcompat.R.styleable.Toolbar_collapseIcon`,而不是其他库的R类。 另外,考虑是否使用了过时的Gradle插件或构建工具。更新到最新版本的Android Gradle插件和构建工具可能解决资源合并的问题,因为新版本可能包含相关修复。 最后,如果问题仍然存在,可能需要向库的维护者报告该问题,特别是如果确认是lifecycle-livedata-core库中的一个bug。同时,可以寻找社区中是否有其他人遇到类似问题,是否有临时解决方案或补丁可用。 总结来说,解决步骤可能包括: 1. 检查并更新依赖库版本,确保兼容性。 2. 清理和重建项目,排除临时构建错误。 3. 使用命名空间正确引用资源。 4. 排除冲突的资源或模块。 5. 检查代码和布局文件中的资源引用是否正确。 6. 更新构建工具和插件。 7. 报告问题给库维护者,寻求官方修复。 可能需要逐步尝试这些方法,找到具体导致问题的原因,并应用相应的解决方案。如果用户提供了具体的错误日志或构建配置,可以进一步分析,但基于现有信息,以上是可能的解决路径。</think>### 问题分析 `lifecycle-livedata-core` 库错误地引入了 `R$styleable` 资源,导致与 `appcompat` 库的 `Toolbar_collapseIcon` 属性冲突。系统在解析资源时,错误地优先从 `lifecycle-livedata-core` 的 `R` 类中查找资源,而非从 `appcompat` 的 `R` 类。 --- ### 根本原因 1. **资源合并冲突** Android 构建过程中会将所有模块的 `R` 类合并到主模块的 `R` 类中。若不同库定义了同名资源(如 `Toolbar_collapseIcon`),会因优先级问题导致引用错误。 2. **依赖版本不兼容** `lifecycle-livedata-core` 和 `appcompat` 的版本可能存在兼容性问题,导致资源 ID 分配冲突。 --- ### 解决方案 #### 1. 强制使用正确版本的依赖库 在 `build.gradle` 中显式指定兼容的版本,避免自动解析冲突: ```groovy // 示例:强制使用 appcompat 的版本覆盖其他库的间接依赖 configurations.all { resolutionStrategy { force 'androidx.appcompat:appcompat:1.6.1' // 使用已知兼容的版本 force 'androidx.lifecycle:lifecycle-livedata-core:2.6.1' // 或升级到修复版本 } } ``` #### 2. 清理构建缓存 资源合并错误可能由残留的缓存导致: ```bash # 命令行执行 ./gradlew clean ./gradlew :app:assembleDebug --rerun-tasks # 强制重新生成资源 ``` #### 3. 排除冲突的资源 从 `lifecycle-livedata-core` 中排除 `R$styleable` 资源: ```groovy dependencies { implementation("androidx.lifecycle:lifecycle-livedata-core:2.6.1") { exclude group: 'androidx.lifecycle', module: 'lifecycle-livedata-core' } } ``` #### 4. 检查资源命名空间 在布局文件中,确保通过 `app` 命名空间引用 `Toolbar` 属性: ```xml <androidx.appcompat.widget.Toolbar xmlns:app="http://schemas.android.com/apk/res-auto" app:collapseIcon="@drawable/collapse_icon" /> ``` #### 5. 升级到修复版本 检查官方是否已发布修复版本。例如: ```groovy dependencies { implementation 'androidx.lifecycle:lifecycle-livedata-core:2.6.1' // 最低修复版本需验证 } ``` --- ### 验证步骤 1. 在代码中打印资源 ID,确认引用来源: ```kotlin Log.d("ResourceCheck", "Toolbar_collapseIcon ID: ${R.styleable.Toolbar_collapseIcon}") ``` 若输出结果指向 `androidx.appcompat` 包,则修复成功。 2. 检查 `app/build/generated/not_namespaced_r_class_sources` 目录,确认 `R` 类合并后的资源是否正常。 --- ### 技术原理 - **资源 ID 分配**:Android 通过 AAPT 为每个资源生成唯一 ID。若不同库定义了同名资源,最终合并时可能覆盖或冲突。 - **R 类合并**:构建工具会将所有模块的 `R` 类合并到主模块的 `R` 类中,依赖库的 `R` 类优先级可能低于主模块。 --- ### 参考 - [Android 官方资源冲突文档](https://developer.android.com/studio/build/dependencies#resolve_conflicts) - [Lifecycle 版本说明](https://developer.android.com/jetpack/androidx/releases/lifecycle)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值