本文目的
谷歌推出以lifecycle viewmodel livedata 为基础的mvvm框架已经有一段时间了,笔者最近也准备在团队中正式尝试使用,学习的过程中也是走了一些弯路,这里留做文档 帮助后人理解。
重点内容
这三个东西 官方文档写的不尽人意,导致上手难度有一些,更多人是不敢尝试,不敢尝试的原因主要是 不知道这些东西到底能为实际开发者 带来多少利益 ,比如 能不能 节约工作量,提高代码质量 ,能解决mvc 和 mvp 的弊端吗? 本文将重点从这几方面入手阐述。
准备工作
本文代码全部以kotlin语言实现。 需要更新build文件
dependencies {
def lifecycle_version = "2.3.0"
def arch_version = "2.1.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
// Lifecycles only (without ViewModel or LiveData)
implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
// Saved state module for ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
// Annotation processor
annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
// alternately - if using Java8, use the following instead of lifecycle-compiler
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
// optional - helpers for implementing LifecycleOwner in a Service
implementation "androidx.lifecycle:lifecycle-service:$lifecycle_version"
// optional - ProcessLifecycleOwner provides a lifecycle for the whole application process
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
// optional - ReactiveStreams support for LiveData
implementation "androidx.lifecycle:lifecycle-reactivestreams:$lifecycle_version"
// optional - Test helpers for LiveData
testImplementation "androidx.arch.core:core-testing:$arch_version"
//重点是下面这2个依赖不要遗漏 可以省很多代码
implementation "androidx.activity:activity-ktx:1.2.2"
implementation "androidx.fragment:fragment-ktx:1.3.2"
}
LifeCycle
严格来说 ,LifeCycle在android mvvm 这套框架中 我们并不会经常直接使用它,大多数时候是和viewmodel和livedata打交道 即可。 但是LifeCycle 是前两者实现的基础。且在很多场景下,我们即使不使用android mvvm 这套东西,单独使用LifeCycle这个组件也可以让我们的代码变的简单
比如说,我们有时候会写一个不停的获取地理位置信息的类,作为一个基础的api 供大家调用,伪代码如下:
class MyLocationListener(val context: Context, val callback: (Int) -> Unit) {
fun start() {
//开始查找地理位置信息 查找到以后 调用callBack 回调 activity界面
}
fun stop() {
//停止查找地理位置信息
}
}
对于调用方来说,我们肯定要在activity的onStart方法中 调用这个listener的start方法 来开启查找 在onStop方法中 手动调用stop方法 来停止查找否则 一直查找会很耗电,你老板也会diss你。
但是这就带来一个问题,作为一个基础类,你每次还要要求你的调用者 手动的在onStart onStop 里面 增加一些代码 这是不是很麻烦? 而且万一有的人忘记调用 少写了一个stop方法怎么办?
或者说再考虑一下 稍微复杂的场景,对于未登录的用户 我们不查信息,那么代码就演变成:
override fun onStart() {
super.onStart()
//checkLoginStatus 是个io查询工作 会有一些耗时
checkLoginStatus { result ->
//操作
if (result) {
listener?.start()
}
}
}
看下这个代码有啥问题? 因为查询操作是个耗时操作,当查询到结果以后回调过来, **你怎么保证 回调的时候这个页面还在,而不是用户点进来马上就退出,当你回调的时候界面都结束了? **
当然你要解决上述的问题 可以加一些判断activity状态的代码,但是这样一来, 别人接入你的组件的成本又提高了
那要怎么解决上述的问题?
让我的组件可以自己感知到使用者(activity fragment service) 的生命周期 不就可以了?
这就是lifecycle的最主要的作用了
我们来看看 lifecycle 针对此场景的 具体用法
其实最主要就是要实现 LifecycleObserver 这个接口,其次就是你想要你的方法在哪个生命周期内 被调用 那你就用 OnLifecycleEvent 注解来标记即可
class MyLocationListener(val context: Context, val callback: (Int) -> Unit) : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun start() {
//开始查找地理位置信息 查找到以后 调用callBack 回调 activity界面
Log.v("wuyue", "listerner start")
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stop() {
//停止查找地理位置信息
Log.v("wuyue", "listerner stop")
}
}
然后看下调用者:
class MainActivity : AppCompatActivity() {
private var listener: MyLocationListener? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
listener = MyLocationListener(this) { locationResult ->
}
//调用方 最重要的就是增加这个观察者即可
lifecycle.addObserver(listener!!)
}
}
如此一来,接入方的成本就仅仅是一句话 lifecycle.addObserver(), 其余所有的生命周期相关的操作 都可以写在你自己的组件内,且不会出错。
lifecycle可以保证 你指定过的函数,只会在你规定的生命周期内得到执行, 否则一定不执行。
数据驱动ui
通常而言,我们写android的代码的流程都是:
- 查网络io或者本地io 获取数据 data
- 找到ui组件 通过类似于 setText(data) 的方式 来手动更新ui
此外,只要data 发生了变化,不管是什么原因 在哪里发生了变化,都要重复步骤2,一旦遗漏 就会造成bug, 其实对于 这种 ui 为主的系统,目前主流的写法 都是数据驱动ui变化了, 也就是说:
只要绑定好data和 ui组件,那么后续data发生变化的时候,ui组件可以根据data最新的值 自动更新ui的状态
这种数据驱动ui变化的写法 就是mvvm框架的核心
我们来写个例子 感受一下
假设我们有个字段name初始值是vivo, 另外有个按钮 每次点击一下 就把这个值 后面加上test ,并且更新到某个textview中 显示出来
正常写 大家都会,我们看下 用mvvm的方式 如何来写。
我们首先定义一个ViewModel 并且将我们关注的name这个字段 用LiveData来指定。
绝大多数场景下,livedata 都是定义在viewmodel中的
class NameViewModel : ViewModel() {
val currentName: MutableLiveData<String> by lazy {
MutableLiveData<String>("vivo")
}
}
然后看下activity的代码怎么写:
class MainActivity : AppCompatActivity() {
//viewModels activity-ktx的一个扩展 可以不用特别关心
private val model: NameViewModel by viewModels()
private val nameTv: TextView by lazy {
findViewById(R.id.tv1)
}
private val nameChangeTv: TextView by lazy {
findViewById(R.id.tv2)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
nameChangeTv.setOnClickListener {
model.currentName.value = model.currentName.value + "test"
}
//定义一个观察者,注意这个Observer 一定是androidx.lifecycle包下的注意这个Observer
val nameObserver = Observer<String> { newName ->
nameTv.text = newName
}
//手动的将我们的观察者 绑定到 viewmodel中的 currentName 这个liveData中 即可
model.currentName.observe(this, nameObserver)
}
}
这都是一些固定的写法,大可不必现在就了解其中的实现细节。 那么这么写 到底有什么好处?
我们假设场景稍微复杂一点,这个currentname 可以在多处被更改,那么如果不用mvvm的写法,
不是数据驱动ui变化,那么每次修改curentName的地方,我们都要手动的 新增代码 nameTv.text = newName
容易遗漏,麻烦不说,而且万一后面你要有更改 比如你要改变字体颜色之类的 那修改的地方就太多了,
而用这种类似于 mvvm的写法,你只要绑定好 ui观察者 和 数据源livedata 的关系 定义好观察者内部的
行为,也就是接收到数据改变时 ui变化的规则 就可以了。
一处编写,处处生效,真是爽到了
并且,前面提到我们的Observer是androix lifecycle包下的Observer,所以聪明一点的人肯定能想到
这么写还有一个好处 就是 他可以保证你 操作ui的时机是正确的, 绝对不会出现activity被销毁的时候 你更新ui的代码被执行
fragment与activity的 数据共享
我们经常会碰到这种场景,一个activity中包含多个fragment,然后这些fragment 和activity还要共享一部分数据,一旦这个数据 发生了改变 所有fragment对应的 ui 都要发生变化。
以前我们处理对应的场景 都是通过各种耦合在一起的interface 或者 通过eventbus 来处理。 很麻烦, 耦合度很高,而且还要考虑 fragment的生命周期是否正确 是否可以刷新ui。
但是有了viewmodel 处理这种情况更加简单了。
class MasterFragment : Fragment() {
//注意这里是 activityViewModels 就代表这个viewmodel是整个activity的所有组件共享
private val model: NameViewModel by activityViewModels()
private var nameTv: TextView? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//注意这里的写法
model.currentName.observe(viewLifecycleOwner, Observer<String> {
nameTv?.text = it
})
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.master_layout, null).apply {
nameTv = findViewById(R.id.tv1)
}
}
}
真的很简单,稍微改几个参数 就可以在activity的 scope内 全局共享 数据了。 而且不管在哪处修改data的值,都是全局生效刷新ui的。
总结
lifecycle viewmodel以及 livedata 这三个东西组成的 android mvvm框架,最大的优点:
- 数据驱动ui 省事
- 可以安全的刷新ui,不需要考虑任何回调回来以后activity的生命周期问题