为什么要使用ViewModel
与LiveData
,它有哪些优势?
ViewModel
将视图和逻辑进行了分离。Activity
或者Fragment
只负责UI显示部分。具体的网络请求或者数据库操作则有ViewModel
负责。类似于MVP
模式中的Presenter
层。ViewModel
类旨在以注重生命周期的方式存储和管理界面相关的数据。让数据可在发生屏幕旋转等配置更改后继续留存。我们知道类似旋转屏幕等配置项改变会导致我们的 Activity 被销毁并重建,此时 Activity 持有的数据就会跟随着丢失,而ViewModel
则并不会被销毁,从而能够帮助我们在这个过程中保存数据。并且ViewModel
不持有View
层的实例,通过LiveData
与Activity
或者Fragment
通讯,不用担心潜在的内存泄漏问题。
LiveData
是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 Activity
、Fragment
或 Service
)的生命周期。这种感知能力可确保LiveData
当数据源发生变化的时候,通知它的观察者更新UI界面。同时它只会通知处于Active状态的观察者更新界面,如果某个观察者的状态处于Paused
或Destroyed
时那么它将不会收到通知。所以不用担心内存泄漏问题。
简单使用
def lifecycle_version = "2.2.0"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
复制代码
class MyViewModel : ViewModel() {
private val currentName by lazy { MutableLiveData<String>().also { loadData() } }
fun getCurrentName(): LiveData<String> = currentName
fun loadData() {
viewModelScope.launch {
try {
var i = 0
while (isActive) {
delay(2000L)
currentName.value = "AAPL$i"
i++
}
} catch (e: Throwable) {
e.printStackTrace()
}
}
}
}
复制代码
private val myViewModel by lazy { ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(application)).get(MyViewModel::class.java) }
private fun initViewModel() {
myViewModel.getCurrentName().observe(this, Observer {
Log.i(TAG, it)
})
}
复制代码
LiveData的转换
LiveData
的map
和switchMap
方法来完成LiveData
的转换
map
只是对LiveData
里面的值进行转换,switchMap
是直接对LiveData
进行转化。类似于RxJava
的map
与flatMap
的区别
class MyViewModel : ViewModel() {
val currentName by lazy { MutableLiveData<String>() }
fun getCurrentName(): LiveData<String> = currentName.map {
it+ " adddd"
}
fun loadData() {
currentName.value = "AAPL DDD"
}
}
复制代码
switchMap
的使用
class MyViewModel : ViewModel() {
val itemId by lazy { MutableLiveData<String>() }
fun getCurrentName(): LiveData<String> = itemId.switchMap {
liveData { emit(getSymbol(it)) }
}
private suspend fun getSymbol(id: String): String {
delay(1000L)
if (id == "9131313131"){
return "AAPL "
}
return "Google"
}
}
复制代码
ViewModel中使用协程
引入依赖
// ViewModel中内置协程
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
复制代码
扩展了viewModelScope
属性,上下文同MainScope()
。
CloseableCoroutineScope
val ViewModel.viewModelScope: CoroutineScope
get() {
val scope: CoroutineScope? = this.getTag(JOB_KEY)
if (scope != null) {
return scope
}
return setTagIfAbsent(JOB_KEY,
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
}
internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
// 会在ViewModel的onCleard()方法中调用协程的cancel方法。也就是在Activity或Fragment的onDestory中调用
override fun close() {
coroutineContext.cancel()
}
}
复制代码
viewModelScope
会在ViewModel
的onCleard()
方法中调用协程的cancel
方法。也就是在Activity
或Fragment
的onDestory
中调用,不需要我们手动去cancel
协程
LiveData配合协程的使用
引入依赖
// LiveData中内置协程
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
复制代码
internal class LiveDataScopeImpl<T>(
internal var target: CoroutineLiveData<T>,
context: CoroutineContext
) : LiveDataScope<T> {
override val latestValue: T?
get() = target.value
// use `liveData` provided context + main dispatcher to communicate with the target
// LiveData. This gives us main thread safety as well as cancellation cooperation
// liveData构造的LiveData 观察者线程为主线程
private val coroutineContext = context + Dispatchers.Main.immediate
override suspend fun emitSource(source: LiveData<T>): DisposableHandle =
withContext(coroutineContext) {
return@withContext target.emitSource(source)
}
override suspend fun emit(value: T) = withContext(coroutineContext) {
target.clearSource()
target.value = value
}
}
复制代码
同样的操作可以用更精简的方法来完成。也就是使用liveData
协程构造方法。liveData
协程构造方法构造出来的LiveData
观察者直接是主线程
class MyViewModel : ViewModel() {
private val mutableCurrentName = liveData(Dispatchers.IO) {
emit(getSymbol())
}
val currentName: LiveData<String> = mutableCurrentName
private suspend fun getSymbol(): String {
delay(1000L)
return "AAPL"
}
}
复制代码
liveData
协程构造方法提供了一个协程代码块参数,当LiveData
被观察时,里面的操作就会执行。LiveData
协程构造方法还可以接收一个 Dispatcher
作为参数,这样就可以将这个协程移至另一个线程。
另外,还可以使用 emitSource()
方法从另一个 LiveData
获取更新的结果
liveData(Dispatchers.IO) {
emit(LOADING_STRING)
emitSource(dataSource.fetchWeather())
}
复制代码
一个正常的网络请求场景
class MyViewModel : ViewModel() {
private val repository by lazy { DataRepository() }
val currentName = liveData {
try {
emit(repository.sendNetworkRequestSuspend())
} catch (e: Throwable) {
e.printStackTrace()
}
}
}
interface SplashInterface {
// 协程的suspend
@GET("/repos/{owner}/{repo}")
suspend fun contributors(@Path("owner") owner: String,
@Path("repo") repo: String): Repository
}
class DataRepository {
private val apiService by lazy {
RetrofitHelper.getInstance().createService(SplashInterface::class.java)
}
suspend fun sendNetworkRequestSuspend(): Repository {
return apiService.contributors("square", "retrofit")
}
}
有用的话,点个赞吧ღ( ´・ᴗ・` )比心
网友评论