LiveData一个具有生命周期的对象。他可以和Activity的生命生命周期绑定,可以监听对象的数据变化。
可监听数据的变化
没有内存泄露:
LiveData绑定Lifecycle(AndroidX的Activity和Fragment都实现了Lifecycle)对象,并在其相关生命周期被破坏后自行清理。没有因停止活动而崩溃:
当界面失去焦点或销毁,此时异步网络请求回调完成。LiveData不必回调观察者始终保持最新数据:
这个要和第2条有关系,例如异步网络请求,返回数据设置给LiveData。在网络请求期间,界面切换到了后台,此时注册的LiveData观察值,并不会回调,当界面恢复到活动状态,立刻收到回调。可以和Data Binding一起使用:
这里有个坑,Data Binding Compiler V1编译器,是不支持的LiveData配合使用的(只可以使用ObservableField来实现双绑定)。Data Binding Compiler V2后面已经支持和LiveData双向绑定功能class StockLiveData(symbol: String) : LiveData<String>() {
init {
// 设置默认值 这个value是LiveData的参数,记录他的值
value = symbol
}
override fun onActive() {
// 绑定生命周期
}
override fun onInactive() {
// 结束回调,也就是Activity或Fragment调用了onDestroy
}
}
注册LiveData数据观察者,可以发现observe传了一个this,这个this是实现了LifecycleOwner接口
// this 是一个Activity
StockLiveData("value").observe(this) {
Log.d("wyz","收到修改的值:$it")
}
setValue
方法,但是这个方法是通过protected
修饰,最好暴露一个修改方法。(注意setValue
方法必须在主线程调用,不然会报错,具体原因)setValue
只能在主线程调用,那如果在子线程呢?LiveData已经帮们想好了postValue
方法LiveData的setValue
和postValue
方法,没有完全暴露出来,每次还要写暴露方法,是不是很麻烦。可以使用提供的子类。
val title: MutableLiveData<String> by lazy {
MutableLiveData<String>("默认标题")
}
MediatorLiveData是MutableLiveData子类,他和MutableLiveData的区别在于,他内部维护了个Map,他可以将多了个LiveData的合并在一起。举个例子:
考虑以下情形:我们有两个实例LiveData,让他们的名字 liveData1和liveData2,我们希望合并其排放量一个对象: mediatorLiveData。然后,liveData1并且liveData2将成为它的源,MediatorLiveData mediatorLiveData并且每次onChanged为它们中的任何一个调用回调时,我们在其中设置新值mediatorLiveData。
val liveData1 = MutableLiveData<String>()
val liveData2 = MutableLiveData<String>()
// 创建mediatorLiveData
val mediatorLiveData = MediatorLiveData<String>()
private fun testLiveData() {
// 做关联1
mediatorLiveData.addSource(liveData1) {
mediatorLiveData.value = it
}
// 做关联2
mediatorLiveData.addSource(liveData2) {
mediatorLiveData.value = it
}
mediatorLiveData.observe(this) {
// 这样不管是liveData1和liveData2数据发生改变,都会调用mediatorLiveData的数据改变
}
}
还有一种用途,例如上面的我只想liveData1数据改变10次后,就不在通知mediatorLiveData数据改变了,要怎么写呢?
mediatorLiveData.addSource(liveData1, object : Observer<String> {
var count = 0
override fun onChanged(t: String?) {
if (count++>10){
mediatorLiveData.removeSource(liveData1)
}else{
mediatorLiveData.value = t
}
}
})
和RxJava的map操作符原理一样
// 数据做转化,mutableLiveData收到数据,转化成其他数据
Transformations.map(mutableLiveData) { value: String ->
// 不可以延迟操作,因为本质还在一条流水线上
value.toInt()
}.observe(this, Observer {
log("收到map转化:$it")
})
对比RxJava的faltMap操作符
// switchMap转化
Transformations.switchMap(mutableLiveData) {
val temp: MutableLiveData<String> = MutableLiveData()
// 可以做延迟操作
Thread(Runnable {
Thread.sleep(4000)
temp.postValue("121")
}).start()
temp
}.observe(this) {
log("收到数据变换了:$it")
}
不知道大家又没和我和一样,刚学RxJava的时候map操作和flatMap操作符,傻傻分不清楚呢?
其实很容易理解:
map操作符好比一个饼干加工流水线,这个流水线,将圆形的饼干加工成方形的流水线,然后饼干就加工完成了。这就是map操作符。
flatMap操作符,相当于流水线1加工好的饼干,给了流水线2,流水线1完成任务。此时流水的的加工过程和流水线1就没有关系了,只和流水线2有关系了。
再简单粗暴点理解,flatMap可以返回另外一个Observable,这个Observable多久弹射数据和原先的Observable没任何关系了,还不懂?看下面的例子。
项目场景:例如:有一个ID值,只要这个ID变化,便会请求数据。要怎么处理呢?
class MyViewModel() : ViewModel() {
。。。。。
// 监听id变化
val id: MutableLiveData<Int> = MutableLiveData()
// 接受变换的数据的LiveData
var result: LiveData<List<MyData>>
init {
// 通过switchMap转换
result = Transformations.switchMap(id) {
// 模拟延迟加载数据
val liveData = MutableLiveData<List<MyData>>()
Thread {
SystemClock.sleep(5000)
val datas = ArrayList<MyData>()
for (i in 1..100) {
datas.add(MyData("awen$i"))
}
//得到数据,将数据发送
liveData.postValue(datas)
}.start()
liveData
}
}
}
// 代码中只需注册result的监听
myViewModel.result.observe(this, Observer {
log("收到数据:刷新")
myViewModel.notify(it)
})
// 直接修改id的值就可以了
myViewModel.id.value = 1
ViewModel数据MVVM的vm层。他的生命周期比较特殊(后面会说)。我们只需要把业务逻辑,都放在这层。
永远记住一句话ViewModel驱动View变换
,完美方案就是View收到控制,得到数据->调用ViewModel的业务逻辑->ViewModel获取返回数据->将数据设置给LiveData->数据发生改变,界面发生变化
从生命周期可以发现,当Activity的旋转屏幕,重新被创建的时候。
ViewModel
不会被重新创建
说实话ViewModel提供的功能不是很多,但是它在MVVM设置中占据了很重要的责任,我觉得有点像MVP的P层。总之记住一句话ViewModel驱动View变化
总结下我知道作用:
ViewModelProviders.of()
,需要传入Activity或者Fragment,如果一个Activity的FragmentManager管理的多个Fragment,这些Frarment的ViewModel是共享的,前提是ViewModelProviders.of()传入的是同一个Acticity对象
MasterFragment 和 DetailFragment都属于一个Activity,此时
ViewModelProviders.of(this)[SharedViewModel::class.java]
返回的对象是同一个ViewModel(注意代码中this代表的是谁,具体原理开源,一下就出来了)
class SharedViewModel : ViewModel() {
val selected = MutableLiveData<Item>()
fun select(item: Item) {
selected.value = item
}
}
class MasterFragment : Fragment() {
private lateinit var itemSelector: Selector
private lateinit var model: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
// 注意这里的this是Activity
ViewModelProviders.of(this)[SharedViewModel::class.java]
} ?: throw Exception("Invalid Activity")
itemSelector.setOnClickListener { item ->
// Update the UI
}
}
}
class DetailFragment : Fragment() {
private lateinit var model: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
// 注意这里的this是Activity
ViewModelProviders.of(this)[SharedViewModel::class.java]
} ?: throw Exception("Invalid Activity")
model.selected.observe(this, Observer<Item> { item ->
// Update the UI
})
}
}
如果使用的3.2已上的Android Studio,可以无视这个问题,LiveData已经兼容Data Binding
注意:如果要使用LiveData于Data Binding需要对应生成的binding对象设置下LifecycleOwner
binding.setLifecycleOwner(this);
偷个懒看别人的吧
这里有必要说一下,当时没有考虑的协程(什么是协程,这里就不说了,kotlin一大特色)我们知道ViewModel的作用,类似我们的P层,获取数据,驱动View变化。如果我们要用协程呢?
我当时的想法是这样的。
class DemoViewModel() : ViewModel() {
// 判断加载状态是否展示的LiveData
val progressBarIsShow = MutableLiveData<Boolean>()
// 创建协程域
val scope = CoroutineScope(Dispatchers.Main)
/**
* 当ViewModel销毁
*/
override fun onCleared() {
super.onCleared()
// 取消郁的所有协程
scope.cancel()
}
/**
* 暴露加载数据数据的方法
*/
fun load() {
// 执行
launch {
// 切换线程异步执行
val data = withContext(Dispatchers.IO) {
// 延迟加载数据
"得到返回数据"
}
// 再通过data,改变LiveData的数据,驱动View
}
}
/**
* 封装下启动协程
*/
fun launch(call: suspend () -> Unit) = scope.launch {
try {// 展示加载中(通过liveData驱动View变化)
progressBarIsShow.value = true
call()
// 异步执行完毕,隐藏加载中
progressBarIsShow.value = false
} catch (e: Exception) {
// 出错逻辑自行处理
}
}
}
其实上面的过程麻烦了,Kotlin为我们想到了。ViewModel使用协程的问题,直接分析源码
每个ViewModel有一个扩展函数
private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"
val ViewModel.viewModelScope: CoroutineScope
get() {
// 注意这里getTag通过一个key判断协程域是否创建。JOB_KEY固定
val scope: CoroutineScope? = this.getTag(JOB_KEY)
if (scope != null) {
return scope
}
return setTagIfAbsent(JOB_KEY,
CloseableCoroutineScope(Job() + Dispatchers.Main))
}
getTag方法没啥讲的就是ViewModel有一个集合 mBagOfTags
<T> T getTag(String key) {
//noinspection unchecked
return (T) mBagOfTags.get(key);
}
接下来就剩下一个问题,如果ViewModel销毁的时候,协程如何取消呢?看下clear方法。这个方法当ViewModel销毁的时候调用
final void clear() {
mCleared = true;
if (mBagOfTags != null) {
// 可以看到,遍历mBagOfTags,挨个关闭了
for (Object value : mBagOfTags.values()) {
// see comment for the similar call in setTagIfAbsent
closeWithRuntimeException(value);
}
}
onCleared();
}
通过以下结论,我们直接用就行了
class DemoViewModel() : ViewModel() {
// 判断加载状态是否展示的LiveData
val progressBarIsShow = MutableLiveData<Boolean>()
/**
* 暴露加载数据数据的方法
*/
fun load() {
// 执行
launch {
val data = withContext(Dispatchers.IO) {
// 延迟加载数据
"得到返回数据"
}
// 再通过data,改变LiveData的数据,驱动View
}
}
/**
* 封装下启动协程
*/
fun launch(call: suspend () -> Unit) = viewModelScope.launch {
try {// 展示加载中(通过liveData驱动View变化)
progressBarIsShow.value = true
call()
// 异步执行完毕,隐藏加载中
progressBarIsShow.value = false
} catch (e: Exception) {
// 出错逻辑自行处理
}
}
}