jetpack-LiveData&ViewModel学习

LiveData

    • LiveData简介
    • LiveData的优势
    • LiveData使用
        • 自定义LiveData对象
        • 如何修改LiveData的值呢?
    • LiveData的子类
        • MutableLiveData
        • MediatorLiveData
    • LiveData操作符
        • map操作符
        • switchMap
        • 操作符项目的应用
    • ViewModel简介
        • ViewModel的生命周期
        • ViewModel的用途
    • LiveData和Data Binding
        • Data Binding和LiveData的兼容问题
    • ViewModel与协程

LiveData简介

LiveData一个具有生命周期的对象。他可以和Activity的生命生命周期绑定,可以监听对象的数据变化。

LiveData的优势

  1. 可监听数据的变化
  2. 没有内存泄露:LiveData绑定Lifecycle(AndroidX的Activity和Fragment都实现了Lifecycle)对象,并在其相关生命周期被破坏后自行清理。
  3. 没有因停止活动而崩溃:当界面失去焦点或销毁,此时异步网络请求回调完成。LiveData不必回调观察者
  4. 始终保持最新数据:这个要和第2条有关系,例如异步网络请求,返回数据设置给LiveData。在网络请求期间,界面切换到了后台,此时注册的LiveData观察值,并不会回调,当界面恢复到活动状态,立刻收到回调。
  5. 可以和Data Binding一起使用:这里有个坑,Data Binding Compiler V1编译器,是不支持的LiveData配合使用的(只可以使用ObservableField来实现双绑定)。Data Binding Compiler V2后面已经支持和LiveData双向绑定功能

LiveData使用

自定义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")
        }

如何修改LiveData的值呢?

  • 直接调用LiveData的setValue方法,但是这个方法是通过protected修饰,最好暴露一个修改方法。(注意setValue方法必须在主线程调用,不然会报错,具体原因)
  • 上面说了setValue只能在主线程调用,那如果在子线程呢?LiveData已经帮们想好了postValue方法

LiveData的子类

LiveData的setValuepostValue方法,没有完全暴露出来,每次还要写暴露方法,是不是很麻烦。可以使用提供的子类。

MutableLiveData

    val title: MutableLiveData<String> by lazy {
        MutableLiveData<String>("默认标题")
    }

MediatorLiveData

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
                }
            }
        })

LiveData操作符

map操作符

和RxJava的map操作符原理一样

   // 数据做转化,mutableLiveData收到数据,转化成其他数据
        Transformations.map(mutableLiveData) { value: String ->
            // 不可以延迟操作,因为本质还在一条流水线上
            value.toInt()
        }.observe(this, Observer {
            log("收到map转化:$it")
        })

switchMap

对比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简介

ViewModel数据MVVM的vm层。他的生命周期比较特殊(后面会说)。我们只需要把业务逻辑,都放在这层。永远记住一句话ViewModel驱动View变换,完美方案就是View收到控制,得到数据->调用ViewModel的业务逻辑->ViewModel获取返回数据->将数据设置给LiveData->数据发生改变,界面发生变化

ViewModel的生命周期

jetpack-LiveData&ViewModel学习_第1张图片

从生命周期可以发现,当Activity的旋转屏幕,重新被创建的时候。ViewModel不会被重新创建

ViewModel的用途

说实话ViewModel提供的功能不是很多,但是它在MVVM设置中占据了很重要的责任,我觉得有点像MVP的P层。总之记住一句话ViewModel驱动View变化
总结下我知道作用:

  1. 更长的生命周期,屏幕旋转,ViewModel不会重新创建(原理看源码)
  2. Fragment之间的数据共享,构建ViewModel的时候需要用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
        })
    }
}
  1. 可以配合LiveData于Data Binding搭配使用(下面会说)

LiveData和Data Binding

Data Binding和LiveData的兼容问题

如果使用的3.2已上的Android Studio,可以无视这个问题,LiveData已经兼容Data Binding

注意:如果要使用LiveData于Data Binding需要对应生成的binding对象设置下LifecycleOwner

binding.setLifecycleOwner(this); 

偷个懒看别人的吧

ViewModel与协程

这里有必要说一下,当时没有考虑的协程(什么是协程,这里就不说了,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) {
            // 出错逻辑自行处理
        }
    }
}

你可能感兴趣的:(学习笔记)