android Livedata最详尽的使用场景分析,让你爱上Livedata

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: android Livedata最详尽的使用场景分析,让你爱上Livedata

git地址


github.com/ananananzhu…

很大一部分文字内容描述是直接翻译官网的


Livedata 概览


LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力

如果观察者(由 Observer 类表示)的生命周期处于 STARTED 或 RESUMED 状态,则 LiveData 会认为该观察者处于活跃状态。。LiveData 只会将更新通知给活跃的观察者。为观察 LiveData 对象而注册的非活跃观察者不会收到更改通知。

您可以注册与实现 LifecycleOwner 接口的对象配对的观察者。有了这种关系,当相应的 Lifecycle 对象的状态变为 DESTROYED 时,便可移除此观察者。这对于 Activity 和 Fragment 特别有用,因为它们可以放心地观察 LiveData 对象,而不必担心泄露


LiveData 优势


  1. 数据符合页面状态
  2. 不会发生内存泄露
  3. 不会因 activity 停止而导致崩溃
  4. 不再需要手动处理生命周期
  5. 数据始终保持最新状态
  6. 可以用来做资源共享


Livedata 使用


一般来说我们会在 ViewModel 中创建 Livedata 对象,然后再 Activity/Fragment 的 onCreate 中注册 Livedata 监听(因为在 onStart 和 onResume 中进行监听可能会有冗余调用)


Livedata 简单使用

仍然还是用我们倒计时的例子,在 Viewmodel 中开始一个 2000s 的倒计时,然后通过 Livedata 回调给 Activity 进行更新界面,代码:


  1. viewmodel 代码
class CountDownModel : ViewModel() {
    val countDownLivedata = MutableLiveData<String>()
    private var remainSecond = 2000//剩余秒数
    init {
        val countDown = object : CountDownTimer(2000 * 1000, 1000) {
            override fun onTick(millisUntilFinished: Long) {
                remainSecond--
                countDownLivedata.postValue("剩余:${remainSecond} 秒")
            }
            override fun onFinish() {
                countDownLivedata.postValue("倒计时结束")
            }
        }
        countDown.start()
    }
}
复制代码


  1. activity 中观察数据更新 ui 代码
val countDownModel: CountDownModel by viewModels<CountDownModel> {
        ViewModelProvider.NewInstanceFactory()
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_count_down)
        countDownModel.countDownLivedata.observe(this, object : Observer<String> {
            override fun onChanged(value: String?) {
                value?.let {
                    tv_countdown_remainsecond.text = it
                }
            }
        })
    }
复制代码


  1. 效果图

image.png


使用全局 Livedata 在多个视图监听状态

本例实现的 demo 效果是,创建一个全局的倒计时,然后在 Activity 中添加两个按钮,点击后可以切换 FragmentA 和 FragmentB。然后我们通过全局的自定义 LiveData 单例实现数据监听,切换 Fragment 后 Fragment 页面上会展示倒计时的剩余秒数

代码:


  1. 全局自定义 Livedata 代码
class GlobalLivedata : LiveData<String>() {
    val coundManager = CountDownManager()
    val listener = object : OnDataChangeListener {
        override fun change(data: String) {
           postValue(data)
        }
    }
    override fun onActive() {
        super.onActive()
        coundManager.setListener(listener)
    }
    override fun onInactive() {
        super.onInactive()
        coundManager.removeListener(listener)
    }
    companion object {
        private lateinit var globalData: GlobalLivedata
        fun getInstance(): GlobalLivedata {
            globalData = if (::globalData.isInitialized) globalData else GlobalLivedata()
            return globalData
        }
    }
}
复制代码


  1. 倒计时器代码较长只粘贴一部分,有兴趣可以到 github 去查看完整代码
private val listeners = mutableListOf<OnDataChangeListener>()
    init {
        val countDown = object : CountDownTimer(2000 * 1000, 1000) {
            override fun onTick(millisUntilFinished: Long) {
                remainSecond--
                callback("剩余:${remainSecond} 秒")
            }
            override fun onFinish() {
                callback("倒计时结束")
            }
        }
        countDown.start()
    }
    /**
     * 循环遍历回调消息
     */
    private fun callback(msg:String) {
        for (listener in listeners){
            listener.change(msg)
        }
    }
复制代码


  1. FragmentA、FragmentB 中监听倒计时状态
GlobalLivedata.getInstance().observe(viewLifecycleOwner,
            { t ->
                inflate.findViewById<TextView>(R.id.tv_fragmentA).text = "fragmenta:${t}"
            })
复制代码


GlobalLivedata.getInstance().observe(viewLifecycleOwner,
            { t ->
                inflate.findViewById<TextView>(R.id.tv_fragmentB).text = "fragmentb:${t}"
            })
复制代码


  1. 最终效果

image.png

最终效果,当我们切换 Fragment 的时候两个 Fragment 显示的秒数是一致的,其实即使我们马上启动一个新 activity 去查看剩余秒数也是一样的,有兴趣的朋友可以下载 git 代码自己尝试


对 Livedata 进行转换

map 和 switchMap 两个方法可以对已有的 Livedata 进行转换得到新的 Livedata


Transformation.map

在 activity 中观察 viewmodel 中的数据更新,当点击 activity 中按钮的时候会调用 viewmodel.sendData 方法发送数据,然后发送的数据会做一定的转换给 activity,然后 activity 打印日志展示


直接看代码吧:

  1. 创建 viewmodel,model 中创建 Livedata
class TransMapViewModel: ViewModel() {
    fun sendData() {
        userLivedata.value=User("李白",1200)//对userLivedata进行复制
    }
    val userLivedata =MutableLiveData<User>()
    val mapLiveData = Transformations.map(userLivedata){
        "${it.name} : ${it.age}"//这里可以返回任意类型的数据
    }
}
data class User(var name:String,var age:Int)
复制代码


代码中 mapLiveData 是对 userLivedata 进行转换得到的,所以当我们调用 sendData 方法更新 userLivedata 中的方法时,mapLiveData 的回调也会触发


  1. 在 activity 中观察 mapLiveData 并点击按钮发送小数据
mapViewModel.mapLiveData.observe(this,{
            logEE(it)
            tv_map.text=it
        })
        btn_map.setOnClickListener {
            mapViewModel.sendData()
        }
复制代码


Transformation.switchMap

本例中我们实现如下逻辑:

在 activity 中观察 viewmodel 中的数据更新,当点击 activity 中按钮的时候会调用 viewmodel.sendData 方法发送数据,然后发送的数据会做一定的转换给 activity,然后 activity 打印日志展示


  1. viewmodel 中代码
class SwitchMapViewModel : ViewModel() {
    fun sendData() {
        userLivedata.value = SwitchUser("李白", 1200)
    }
    private val userLivedata = MutableLiveData<SwitchUser>()
    val mapLiveData = Transformations.switchMap(userLivedata) {
        changeUser(it!!)
    }
    private fun changeUser(it: SwitchUser): LiveData<String> {
        return MutableLiveData("${it.name}  的名字杜甫知道")
    }
}
data class SwitchUser(var name: String, var age: Int)
复制代码


  1. 调用部分代码
model.mapLiveData.observe(this, {
            logEE(it)
        })
        btn_switchmap.setOnClickListener {
            model.sendData()
        }
复制代码


合并两个 Livedata(MediatorLiveData)

想象这样一个场景,您的 app 里面有一个评论列表的功能,可以对列表内容进行点赞。每一个点赞都是一个异步任误,你的产品需求并不想让用户点太多赞,比如一分钟点赞数量不能超过 10 次,这种场景就很适合用 Livedata 的合并功能

我们就不模拟这么复杂的场景了,我们的例子做这样一个事情:

界面上有两个按钮,点一次相当于点赞一次,我们点击十次按钮就在界面上展示文字提示用户已经点击了十次数据。


代码展示:

1.model 代码

class MeditorLiveViewModel : ViewModel() {
    var count =0//计数字段
    fun setData1(name: String) {
        liveData1.value = name
    }
    fun setData2(age: Int) {
        liveData2.value = age
    }
    private val liveData1 = MutableLiveData<String>()
    private val liveData2 = MutableLiveData<Int>()
    val liveCombind = MediatorLiveData<String>()
    init {
        liveCombind.addSource(liveData1) {
            increase()
        }
        liveCombind.addSource(liveData2) {
           increase()
        }
    }
    private fun increase() {
        count++
        if(count==10){
            liveCombind.value="安安安安卓同学,您已经点击 ${count}次,再点我也不跟你玩了,收手吧。。。"
        }
    }
}
复制代码


model 中创建了三个 Livedata,其中两个分别是 livedata1 和 livedata2,分别对应其中两个按钮。

还有一个 liveCombind 用来回调超过十次调用的场景

init 方法中 liveCombind.addSource 调用就是表示用来中间拦截 livedata1 和 livedata2 的数据更新,处理 count 累加和是否回调 liveCombind 的功能


  1. activity 中代码
model.liveCombind.observe(this){
            logEE(it)
            tv_count.text=it
        }
        btn_livedata1.setOnClickListener {
            model.setData1("李白")
        }
        btn_livedata2.setOnClickListener {
            model.setData2(1000)
        }
复制代码


  1. 实现效果

image.png


observeForever

observeForever 方法也是注册 Livedata 监听的方法,表示即使应页面被覆盖处于不活跃状态也可以收到数据改变的回调


Livedata 和协程联合使用

emit 方式使用

  1. 引入依赖 有时候你可能需要处理异步任务,任务处理完成后刷新 ui

这种情况可以使用 Livedata 的扩展程序实现

本例我们实现下面的逻辑:

在 viewmodel 中阻塞 4s,然后通知 activity


代码:

  1. 引入依赖插件
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
复制代码


  1. 开启异步任务方法
/**
     * 开启异步任务
     */
    fun startAsyncWithSecond(second: Int): LiveData<String> = liveData<String> {
        delay(second * 1000L)
        emit("倒计时结束")//用来触发数据回调
    }
复制代码


当我们调用 startAsyncWithSecond 方法的时候会马上返回一个 Livedata 对象,供我们注册监听


  1. activity 中注册 livedata 监听
model.startAsyncWithSecond(3).observe(this){
                logEE(it)//model中delay  3s后会返回数据到这里
            }
复制代码

image.png


  1. 效果展示

emitSource 使用

使用 emitSource 的效果等同于 MediatorLiveData 的效果

我们本例实现如下的效果:

点击按钮开启一个 3s 的异步任务,然后通知 activity 打印日志。

然后再次开启一个 3s 的异步任务,结束后再次通知 activity 打印日志


代码:

  1. 创建异步任务方法
fun startAsyncEmitSource(second: Int)= liveData<String> {
        delay(second * 1000L)
        emit("${second} 秒阻塞完成,再阻塞三秒后通知你")
        val emitSourceLivedata = MutableLiveData<String>()
        emitSource(
            emitSourceLivedata
        )
        delay(second*1000L)
        emitSourceLivedata.value="再次阻塞 ${second}秒完成"
    }
复制代码


  1. activity 中注册监听
model.startAsyncEmitSource(3).observe(this){
                logEE(it)
            }
复制代码


  1. 效果

image.png



相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
1月前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
149 4
|
1月前
|
安全 Android开发 数据安全/隐私保护
深入探讨iOS与Android系统安全性对比分析
在移动操作系统领域,iOS和Android无疑是两大巨头。本文从技术角度出发,对这两个系统的架构、安全机制以及用户隐私保护等方面进行了详细的比较分析。通过深入探讨,我们旨在揭示两个系统在安全性方面的差异,并为用户提供一些实用的安全建议。
|
3月前
|
开发工具 Android开发 Swift
安卓与iOS开发环境对比分析
在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统无疑是主角。它们各自拥有独特的特点和优势,为开发者提供了不同的开发环境和工具。本文将深入浅出地探讨安卓和iOS开发环境的主要差异,包括开发工具、编程语言、用户界面设计、性能优化以及市场覆盖等方面,旨在帮助初学者更好地理解两大平台的开发特点,并为他们选择合适的开发路径提供参考。通过比较分析,我们将揭示不同环境下的开发实践,以及如何根据项目需求和目标受众来选择最合适的开发平台。
55 2
|
24天前
|
Java 开发工具 Android开发
安卓与iOS开发环境对比分析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自占据半壁江山。本文深入探讨了这两个平台的开发环境,从编程语言、开发工具到用户界面设计等多个角度进行比较。通过实际案例分析和代码示例,我们旨在为开发者提供一个清晰的指南,帮助他们根据项目需求和个人偏好做出明智的选择。无论你是初涉移动开发领域的新手,还是寻求跨平台解决方案的资深开发者,这篇文章都将为你提供宝贵的信息和启示。
29 8
|
2月前
|
缓存 Java Shell
Android 系统缓存扫描与清理方法分析
Android 系统缓存从原理探索到实现。
81 15
Android 系统缓存扫描与清理方法分析
|
27天前
|
安全 Android开发 数据安全/隐私保护
深入探索Android与iOS系统安全性的对比分析
在当今数字化时代,移动操作系统的安全已成为用户和开发者共同关注的重点。本文旨在通过比较Android与iOS两大主流操作系统在安全性方面的差异,揭示两者在设计理念、权限管理、应用审核机制等方面的不同之处。我们将探讨这些差异如何影响用户的安全体验以及可能带来的风险。
34 1
|
2月前
|
存储 Linux Android开发
Android底层:通熟易懂分析binder:1.binder准备工作
本文详细介绍了Android Binder机制的准备工作,包括打开Binder驱动、内存映射(mmap)、启动Binder主线程等内容。通过分析系统调用和进程与驱动层的通信,解释了Binder如何实现进程间通信。文章还探讨了Binder主线程的启动流程及其在进程通信中的作用,最后总结了Binder准备工作的调用时机和重要性。
Android底层:通熟易懂分析binder:1.binder准备工作
|
3月前
|
安全 Android开发 数据安全/隐私保护
探索安卓与iOS的安全性差异:技术深度分析与实践建议
本文旨在深入探讨并比较Android和iOS两大移动操作系统在安全性方面的不同之处。通过详细的技术分析,揭示两者在架构设计、权限管理、应用生态及更新机制等方面的安全特性。同时,针对这些差异提出针对性的实践建议,旨在为开发者和用户提供增强移动设备安全性的参考。
154 3
|
2月前
|
开发工具 Android开发 Swift
安卓与iOS开发环境的差异性分析
【10月更文挑战第8天】 本文旨在探讨Android和iOS两大移动操作系统在开发环境上的不同,包括开发语言、工具、平台特性等方面。通过对这些差异性的分析,帮助开发者更好地理解两大平台,以便在项目开发中做出更合适的技术选择。
|
3月前
|
安全 Linux Android开发
探索安卓与iOS的安全性差异:技术深度分析
本文深入探讨了安卓(Android)和iOS两个主流操作系统平台在安全性方面的不同之处。通过比较它们在架构设计、系统更新机制、应用程序生态和隐私保护策略等方面的差异,揭示了每个平台独特的安全优势及潜在风险。此外,文章还讨论了用户在使用这些设备时可以采取的一些最佳实践,以增强个人数据的安全。