Jetpack架构组件 — LiveData与ViewModel
为什么要使用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")
}
}
写在最后
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在IT学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多程序员朋友无法获得正确的资料得到学习提升,故此将并将重要的Android进阶资料包括自定义view、性能优化、MVC与MVP与MVVM三大框架的区别、NDK技术、阿里面试题精编汇总、常见源码分析等学习资料免费分享出来。
知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。包含知识脉络 + 诸多细节,由于篇幅有限,下面只是以图片的形式给大家展示一部分。
【Android学习PDF+学习视频+面试文档+知识点笔记】
【Android高级架构视频学习资源】
Android部分精讲视频领取学习后更加是如虎添翼!进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
【Android进阶学习视频】、【全套Android面试秘籍】可以简信我【学习】查看免费领取方式!
原文链接:https://juejin.im/post/6863031989155889166