一、LiveData
LiveData里面的东西,用来
这个东西,怎么说呢,google17年推出的,很RxJava有点像,但是又没RxJava那么强大。随着Kotlin协程推出了Flow,RxJava肯定会被Flow代替。连RxJava都会被代替,所以LiveData一定一慢慢被代替。
但是呢,LiveData是Jetpack里面的东西,多多少少,还是要知道一些比较好。
LiveData能干嘛?作用
LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。
这玩意,经常和ViewModel一起使用。
作用
数据共享 (比如Fragment和Activity之间)
-
- 数据共享:LiveData 可以用于在不同组件之间共享数据。传统方式中,你可能需要手动实现数据监听和更新的逻辑,而 LiveData 可以自动处理这些事情。例如,在一个 Fragment 中更新的数据可以立即在相关联的 Activity 中得到通知,而无需手动进行数据同步。
生命周期管理 (不用手动注册和注销)
-
- 生命周期管理:LiveData 可以感知组件的生命周期,并在组件处于活跃状态时通知观察者,而在非活跃状态时停止通知。这可以防止观察者在不需要更新的时候浪费资源或导致内存泄漏。传统方式中,你需要手动管理观察者的注册和取消注册,而 LiveData 可以自动处理这些事情。
避免内存泄露
-
- 避免内存泄漏:LiveData 的观察者在组件销毁时会自动取消注册,从而避免了传统方式中由于忘记取消注册而引起的内存泄漏问题。
结合viewModel使用
-
- 配合 ViewModel 使用:LiveData 常与 ViewModel 结合使用,用于在 UI 控制器(如 Activity 或 Fragment)和数据层(如 Repository)之间传递数据。LiveData 可以观察 ViewModel 中的数据变化,并通知 UI 控制器更新界面。这种方式可以使数据与界面的分离更加清晰,并且在屏幕旋转等配置更改时保持数据的一致性。
结合Room 数据库使用
-
- 配合 Room 数据库使用:LiveData 与 Room 数据库框架完美结合,可以将数据库查询结果作为 LiveData 对象返回。这样,当数据库中的数据发生变化时,相关的 UI 组件可以自动更新。
具体看看官方文档把——developer.android.com/topic/libra…
二、LiveData的简单使用
LiveData的使用离不开Viewmodel。
三部曲: 创建、观察、更新
我们去敲键盘吧
1、引入个livedata ktx
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.0-alpha03"
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0-alpha04'
implementation "androidx.fragment:fragment-ktx:1.3.3"
可以在google开发者官网搜索ktx,搜索得出如下地址 developer.android.com/kotlin/ktx
选择我们需要的ktx
2、Viewmodel
// 注意主线程、子线程更新数据的方式是不一样的
class LivedataVm : ViewModel() {
// Viewmodel 里面可观察的 LiveData
val name:MutableLiveData<String> by lazy { MutableLiveData<String>() }
fun updateName(newName:String){
// 主线程更新直接 setValue
name.value = newName
}
val threadValue:MutableLiveData<String> by lazy { MutableLiveData<String>() }
fun updateThreadValue(newV:String){
// 子线程更新用 postValue
threadValue.postValue(newV)
}
}
3、MainActivity
class MainActivity : AppCompatActivity() {
private val model: LivedataVm by viewModels()
private lateinit var tv1:TextView
private lateinit var tv2:TextView
private lateinit var btn1:Button
private lateinit var btn2:Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tv1 = findViewById(R.id.tv_value_1)
btn1 = findViewById(R.id.btn_1)
tv2 = findViewById(R.id.tv_value_2)
btn2 = findViewById(R.id.btn_2)
btn1.setOnClickListener(View.OnClickListener {
var changeVal = (0..100).random()
model.updateName(changeVal.toString())
})
model.name.observe(this, {
tv1.text = "主线程更新:{$it}"
})
btn2.setOnClickListener(View.OnClickListener {
thread {
var changeVal = (500..600).random()
model.updateThreadValue(changeVal.toString())
}
})
model.threadValue.observe(this, Observer {
tv2.text = "子线程更新:{$it}"
})
}
}
。
。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
>
<TextView
android:id="@+id/tv_value_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="主线程更新:0"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="主线程"
/>
<TextView
android:id="@+id/tv_value_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="主线程更新:0"
android:layout_marginTop="50dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="子线程"
/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
.
.
上面的code不复杂。
1、创建了一个 ViewModel,里面观察的数据是 LiveData2、MainActivity持有了这个 ViewModel 对象,并且利用这个对象进行取值和更新值的操作。
再来一个例子吧
- 1、数据共享和观察:LiveData 可以在应用程序的不同组件之间共享数据。例如,当数据发生变化时,可以将其更新到 LiveData 对象中,并且所有观察该对象的组件都会收到更新通知。这对于实时更新 UI 是非常有用的。
- 2、生命周期管理:LiveData 可以感知组件的生命周期状态,如活动(Activity)、片段(Fragment)或服务。这意味着 LiveData 会自动停止对已处于非活动状态的组件发送更新,从而避免了不必要的资源消耗。
- 3、避免内存泄漏:由于 LiveData 是与组件的生命周期相关联的,因此当组件销毁时,LiveData 会自动清理不再需要的观察者。这可以避免常见的
- 4、内存泄漏问题。 配合 ViewModel 使用:LiveData 通常与 ViewModel 一起使用,用于在 ViewModel 和 UI 之间进行通信。ViewModel 可以将数据存储在 LiveData 中,并且 UI 可以观察 LiveData 来获取最新的数据。
结合以上4点,我们来写一个例子
- 创建 ViewModel 类(例如:MyViewModel.kt):
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class MyViewModel : ViewModel() {
private val _data = MutableLiveData<String>() // 可变的 LiveData 对象
val data: LiveData<String> = _data // 对外暴露的只读 LiveData 对象
// 当数据发生变化时更新 LiveData
fun updateData(newData: String) {
_data.value = newData
}
}
- 在 Activity 或 Fragment 中观察 LiveData,并在适当的生命周期中开始和停止观察:
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MyViewModel
private lateinit var dataObserver: Observer<String>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
// 创建观察者来观察 LiveData 的变化
dataObserver = Observer<String> { newData ->
// 在这里更新 UI,例如更新 TextView 的文本
textView.text = newData
}
}
override fun onStart() {
super.onStart()
// 在合适的生命周期中开始观察 LiveData
viewModel.data.observe(this, dataObserver)
}
override fun onStop() {
super.onStop()
// 在合适的生命周期中停止观察 LiveData
viewModel.data.removeObserver(dataObserver)
}
}
在上述示例中,我们创建了一个名为 dataObserver 的观察者,并在 onCreate 方法中进行初始化。然后,在 onStart 生命周期方法中开始观察 LiveData,并在 onStop 生命周期方法中停止观察。
通过这种方式,我们确保只在 Activity 处于活动状态时才观察 LiveData,以避免不必要的资源消耗。当 Activity 停止时,我们手动从 LiveData 中移除观察者,以确保没有内存泄漏的发生。
此外,我们还将 LiveData 和 ViewModel 结合使用,将数据存储在 ViewModel 的 LiveData 对象中,并在 UI 中观察 LiveData,以获取最新的数据更新。这样,即使屏幕旋转或配置更改,也能确保数据在不同的生命周期中得到正确的保留和更新。
通过这两个例子,我想应该很清晰了。
三、map和switchmap
当使用 LiveData 时,LiveData Transformations 是一组用于转换和组合 LiveData 对象的工具类。其中包括 map 和 switchMap 方法,它们可以帮助我们对 LiveData 进行映射和转换。
- map —— 映射
- switchMap —— 转换
三.1、map 开讲
map:map 操作符允许我们对 LiveData 中的数据进行转换。它接收一个函数作为参数,该函数会将原始数据进行处理并返回转换后的结果。这样,每当原始数据发生变化时,转换后的数据也会相应地更新。换句话说,map 可以将一种数据类型转换为另一种数据类型。
来份代码吧
class MainActivity2 : AppCompatActivity() {
private val timestampLiveData: MutableLiveData<Long> = MutableLiveData()
private val textTimeLiveData: LiveData<String> = Transformations.map(timestampLiveData) { timestamp ->
// 传入一个int,映射成为String (是的看起来跟返回值差不多)
val sdf = SimpleDateFormat("yyyyMMdd", Locale.getDefault())
sdf.format(Date(timestamp))
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 设置观察者,当textTimeLiveData中的数据发生变化时,更新UI
textTimeLiveData.observe(this, Observer { textTime ->
// 在这里更新UI,显示文本时间
// textTime 包含转换后的时间字符串
})
// 模拟数据变化,将时间戳传递给timestampLiveData
val currentTimeMillis = System.currentTimeMillis()
timestampLiveData.value = currentTimeMillis
}
}
问题来了
看起来,所谓的map映射,看起来跟设置个返回值好像没什么区别。 那么,还要这么大费周章,作甚??????
实际上,使用map操作符的主要好处在于它与LiveData的结合,以及在响应式编程中的一些优势。
用map操作符的好处:
- 与LiveData结合:
LiveData是一种观察者模式的数据持有者,它可以在数据发生变化时通知观察者。使用map操作符,你可以对LiveData中的数据进行转换,而不需要在观察者中手动处理转换逻辑。这样,你可以将转换逻辑与观察逻辑分离,使代码更加模块化和可维护。 - 函数式编程:
map操作符是函数式编程的一种概念,它可以帮助你在数据流中进行转换和处理。与传统的函数调用不同,map操作符使转换逻辑更加声明式和可组合。它允许你将数据转换作为一个函数传递,并链式调用多个操作符,以便于处理复杂的数据转换场景。 - 可读性和可维护性:使用
map操作符可以提高代码的可读性和可维护性。通过将转换逻辑抽象为map操作符,你可以更清晰地表达你的意图,使代码更具可读性。此外,由于转换逻辑被封装在操作符中,你可以轻松地重用和组合操作符,从而减少代码重复和维护成本。
总而言之,虽然在某些情况下使用函数设置返回值可能足够简单和直接,但使用map操作符可以更好地与LiveData结合,提高代码的可读性、可维护性,并符合响应式编程的思想。它提供了一种一致的方式来进行数据转换,并将转换后的结果以LiveData的形式输出,方便在UI层进行观察和处理。
说人话,就是主要是跟Livedata结合。健康一些,解耦啊,函数式编程啊,可读可维护性好一些啊,不然的话,大差不差。
三.2、switchMap 转换
switchMap 方法允许我们根据原始 LiveData 对象的值动态地创建和切换到不同的 LiveData 对象。它接受一个函数作为参数,该函数返回一个新的 LiveData 对象,并在原始 LiveData 的值发生变化时自动切换到新的 LiveData。
来份代码吧
模拟输入内容,搜索出结果
将入原始输入的String转换成为结果的 List<String>
package com.example.myapplication
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.widget.EditText
import android.widget.TextView
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.Transformations
import androidx.lifecycle.liveData
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
private lateinit var searchKeywordLiveData: MutableLiveData<String>
private lateinit var searchResultsLiveData: LiveData<List<String>>
private lateinit var searchBox: EditText
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
searchBox = findViewById(R.id.searchBox)
searchKeywordLiveData = MutableLiveData()
val mediatorLiveData = MediatorLiveData<List<String>>()
searchResultsLiveData = mediatorLiveData
mediatorLiveData.addSource(searchKeywordLiveData) { keyword ->
// 模拟异步搜索请求
CoroutineScope(Dispatchers.Main).launch {
delay(500) // 模拟搜索延迟
mediatorLiveData.value = getSearchResults(keyword) // 发送搜索结果
}
}
// 观察searchResultsLiveData,更新UI上的搜索结果
searchResultsLiveData.observe(this, Observer { searchResults ->
val searchResultsText = findViewById<TextView>(R.id.searchResultsText)
searchResultsText.text = searchResults.joinToString("\n")
})
searchBox.addTextChangedListener(object : TextWatcher {
private var searchJob: Job? = null
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
searchJob?.cancel() // 取消之前的搜索请求
// 开启一个新的搜索请求
searchJob = CoroutineScope(Dispatchers.Main).launch {
delay(500) // 等待用户停止输入
searchKeywordLiveData.value = s?.toString()
}
}
override fun afterTextChanged(s: Editable?) {}
})
}
private suspend fun getSearchResults(keyword: String): List<String> {
// 执行搜索请求,返回搜索结果
return listOf("Result 1", "Result 2", "Result 3")
}
}
布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".MainActivity"
android:gravity="center"
>
<EditText
android:id="@+id/searchBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Search Keyword" />
<TextView
android:id="@+id/searchResultsText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Search Results" />
</LinearLayout>
引入
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.5.1'
implementation "androidx.lifecycle:lifecycle-livedata:2.5.1"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
implementation "androidx.fragment:fragment-ktx:1.5.7"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.7'
再来一份简单的例子
根据用户id,得到用户详情
假设有两个 LiveData 对象:一个表示用户的 ID,另一个表示与该用户相关的详细信息。当用户的 ID 发生变化时,您希望自动获取相应用户的详细信息。这时,switchMap 就派上了用场。
val userIdLiveData: LiveData<String> = ...
val userDetailLiveData: LiveData<UserDetail> = Transformations.switchMap(userIdLiveData) { userId ->
getUserDetailLiveData(userId)
}
map和switchmap的对比总结
-
map 用于将 LiveData 的数据从一种类型转换为另一种类型,
-
switchMap 用于根据 LiveData 的值自动切换到不同的 LiveData 对象。
这两个操作符都可以帮助我们轻松地处理和转换数据流,使我们能够更方便地更新 UI 或执行其他操作。
Livedata和Flow
Livedata和Flow区别
- Flow 是 Kotlin 提供的解决方案,与具体的 Android 架构无关,可以在任何 Kotlin 项目中使用。而 LiveData 则是 Android 架构组件的一部分,主要用于 Android 应用程序。
-
- Flow 和 LiveData 是一种数据流。Flow 是协程中的数据流,LiveData 是 Android 生命周期感知的数据流。
- Flow 和 LiveData 之间的主要区别是,LiveData 是生命周期感知的,而 Flow 不是。这意味着 LiveData 可以避免在不合适的生命周期状态下更新 UI,而 Flow 则没有这种内置功能。
直接替代?
- Flow 可以在某些场景下替代 LiveData。因为 Flow 是协程中的数据流,所以它有更强大和灵活的操作符。但是,如果你需要生命周期感知的数据流,那么 LiveData 仍然是一个很好的选择。
- LiveData 可以使用
asLiveData扩展函数转换为 Flow,这样你可以在 LiveData 中享受到 Flow 的操作符。
为什么说“LiveData,一个类似RxJava但终归会被Flow消灭的东西”,简单的,livedata足够。但是时间推移,还会是Flow的世界。我说的,也是我猜的。