Android-LiveData

本文深入探讨了LiveData在Android开发中的应用,以及它如何提供一种通用的数据观察者模式,特别是其与传统回调方式的区别。LiveData能观察任意类型数据的变更,并内建生命周期监听,确保仅在组件处于活动状态时通知数据变化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传统回调方式

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

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

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

接口回调的困境

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

除了接口膨胀问题,由于接口的实现都是实类,因此很多时候这些实类创建出来的对象会有对其他类的直接引用或间接引用,这些接口的对象可能会被传至任意地方,导致其生命周期变长,从而被内部被引用的对象生命周期也变长,尤其像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线程。

<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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值