前言

在 Android 中,ViewModel 的作用就是在 UI 控制器( 如 Activity、Fragment)的生命周期中保存和管理 UI 相关的数据。ViewModel 保存的数据在配置更改(如屏幕旋转)后会依然存在,不会丢失。

在屏幕旋转的时候,Activity 会重建,为了不让数据丢失,我们通常的做法是在 onSaveInstanceState() 方法中通过 bundle 保存数据,然后在 onCreate()onRestoreInstanceState() 方法中取出 bundle 来恢复数据。然而,这种方式有一定的局限性,它只适用于可序列化然后反序列化的少量数据,对于 Bitmap 等比较大的数据就不适用了。

另一方面,UI 控制器通常需要做一些耗时的异步调用操作,并且需要去管理这些调用。UI 控制器需要确保系统在销毁后去清理掉这些异步调用,以避免潜在的内存泄漏,这种管理方式需要大量的维护工作。而且,在配置更改后重建对象是很浪费资源的,因为该对象可能必须重新发出之前已经发出过的调用。

UI 控制器一般只负责显示和处理用户操作,加载数据库数据或网络数据的工作应该委托给其它类,这样会让测试工作更加容易地进行。因此,将视图数据相关操作从 UI 控制器逻辑中分离出来是很有必要。

ViewModel 使用

比如,一个 ViewModelActivity 需要展示一个 User 的列表数据,那么可以定义一个 UserViewModel 来获取数据,然后在 ViewModelActivity 中创建一个 UserViewModel 对象来获取到 User 的列表数据。

class UserViewModel : ViewModel() {private lateinit var users: MutableLiveData<List<User>>fun getUsers(): LiveData<List<User>> {if (!::users.isInitialized) {users = MutableLiveData()loadUsers()}return users}private fun loadUsers() {// Do an asynchronous operation to fetch users .Thread(Runnable {Thread.sleep(3000)// 在子线程发送值用 postValue , 否则用 setValue .users.postValue(listOf(User("1", "AA"), User("2", "BB")))}).start()}
}
复制代码
class ViewModelActivity : AppCompatActivity() {private val TAG = "ViewModelActivity"override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_view_model)// 就算配置更改(如屏幕旋转)了,获取到的 userViewModel 对象还会是上一次的 UserViewModel 对象val userViewModel = ViewModelProviders.of(this).get(UserViewModel::class.java)// 这里的 this 需要用实现了 LifecycleOwner 的类的 this . 如 AppCompatActivity、FragmentActivityuserViewModel.getUsers().observe(this, Observer {Log.e(TAG, it.toString())// 打印结果:[User(id=1, name=AA), User(id=2, name=BB)]})}
}
复制代码

查看源码可知,ViewModelProviders.of(this) 获取了一个全新的 ViewModelProvider 对象,

public static ViewModelProvider of(@NonNull FragmentActivity activity,@Nullable Factory factory) {Application application = checkApplication(activity);if (factory == null) {factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);}return new ViewModelProvider(ViewModelStores.of(activity), factory);}
复制代码

ViewModelProvider 对象调用 get() 方法获取到我们需要的 ViewModel 对象。追踪一下 get() 方法可以知道,ViewModel 对象是存储在一个 ViewModelStore 类的对象中的,该类里面使用 HashMap 来保存和获取 ViewModel .

ViewModel viewModel = mViewModelStore.get(key);
复制代码

获取 ViewModel 使用的 key 相对具体的 ViewModel 类是不会变化的,因此从 ViewModelStore 中取出的 ViewModel 对象也不会变。包括在配置更改后也可以获取到之前的 ViewModel .

当宿主 Activity 调用了 finish() 方法,系统会调用 ViewModel 对象的 onCleared() 方法来让它清理掉资源,到这里之后 ViewModel 才会被释放掉。

ViewModel 里面不要引用 View、或者任何持有 Activity 类的 context , 否则会引发内存泄漏问题。

当 ViewModel 需要 Application 类的 context 来获取资源、查找系统服务等,可以继承 AndroidViewModel 类。

class MyAndroidViewModel(application: Application) : AndroidViewModel(application) {private val appget() = getApplication<Application>()fun getStatus(code: Int): String {return when (code) {1 -> app.resources.getString(R.string.be_late) // 迟到2 -> app.resources.getString(R.string.leave_early) // 早退else -> app.resources.getString(R.string.absenteeism) // 旷工}}
}
复制代码
val myAndroidViewModel = ViewModelProviders.of(this).get(MyAndroidViewModel::class.java)
Log.e(TAG, myAndroidViewModel.getStatus(2))
// 打印结果:早退
复制代码

ViewModel 的生命周期

ViewModel 会一直保留在内存中,直到 Activity / Fragment 在以下情况下才会销毁:

  • 宿主 Activity 被 finish 后调用 onDestroy 方法。
  • 宿主 Fragment 被 detached 后调用 onDetach 方法。

下图展示了一个 Activity 经历了旋转然后调用 finish 的各种生命周期状态,同时展示了关联了该 Activity 的 ViewModel 的生命周期。(UI 控制器是 Fragment 的情况也类似。)

Fragment 之间共享数据

假设我们有这样的需求:在一个 MasterFragment 中有一个 User 列表,点击列表项后将点中的 User 对象传递给 DetailFragment 用于展示详细的 User 信息。

我们一般的做法是:在两个 Fragment 中定义一些通信接口,并且宿主 Activity 需要把它们绑定起来,这样做相当繁琐。并且两个 Fragment 还需要处理另外的 Fragment 尚未创建或者可见的场景。

为了避免以上繁琐的做法,我们可以通过两个 Fragment 之间共享一个 ViewModel 的方式来实现数据通信。

class SharedViewModel : ViewModel() {val selected = MutableLiveData<User>()fun select(user: User) {selected.value = user}
}
复制代码
class MasterFragment : Fragment() {private val dataList = listOf(User("1", "张三"), User("2", "李四"), User("3", "王五"))override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {return inflater.inflate(R.layout.fragment_master, container, false)}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)var model = activity?.run {ViewModelProviders.of(this).get(SharedViewModel::class.java)} ?: throw Exception("Invalid Activity")lvMaster.adapter = ArrayAdapter<User>(activity,android.R.layout.simple_expandable_list_item_1,dataList)lvMaster.setOnItemClickListener { _, _, position, _ ->model.select(dataList[position])}}
}
复制代码
class DetailFragment : Fragment() {override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {return inflater.inflate(R.layout.fragment_detail, container, false)}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)var model: SharedViewModel = activity?.run {ViewModelProviders.of(this).get(SharedViewModel::class.java)} ?: throw Exception("Invalid Activity")model.selected.observe(this, Observer<User> { item ->tvDetail.setText("${item?.id} : ${item?.name}")})}
}
复制代码

需要特别注意,两个 Fragment 都需要使用它们的宿主 Activty 的 this 来获取 ViewModelProviders , 这样才确保它们获取到的是同一个 ViewModel 对象。

这种数据通信的方式有以下几个好处:

  • 宿主 Activity 不需要做任何的事情,也完全不知道 Fragment 之间的通信;
  • 一个 Fragment 不需要知道另一个 Fragment 中除了 ViewModel 契约之外的其它事情,哪怕另一个 Fragment 消失了,它也继续保持正常工作;
  • 每个 Fragment 都有自己的生命周期,它们之间互不影响,哪怕某一个 Fragment 被其它 Fragment 替换了,UI 还是会继续工作,没有任何问题。

文中 Demo GitHub 地址

转载于:https://juejin.im/post/5bfc9d40e51d450ad42ab3c8

Android Jetpack 之 ViewModel相关推荐

  1. Android Jetpack组件ViewModel基本使用和原理分析

    本文整体流程:首先要知道什么是 ViewModel,然后演示一个例子,来看看 ViewModel 是怎么使用的,接着提出问题为什么是这样的,最后读源码来解释原因! 1.什么是ViewModel 1.1 ...

  2. Android Jetpack之DataBinding+ViewModel+LiveData+Room

    Android Jetpack之ViewModel.LiveData Android Jetpack之LifeCycle 前言 Jetpack是一个由多个技术库组成的套件,可帮助开发者遵循最佳做法,减 ...

  3. android jetpack ViewModel 报错解决方法

    android  jetpack 的viewmodel 部分,在activity里面写以下这行代码时会报错的解决方法: myViewModel = new ViewModelProvider(this ...

  4. Android Jetpack组件之ViewModel使用

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  5. Android Jetpack: ViewModel | 中文教学视频

    Android Jetpack 是一系列助力您更容易打造卓越 Android 应用的工具和组件.这些组件能帮助您遵循最佳实践.免除编写繁复的样板代码并简化复杂任务,从而使您可以专注于最核心的代码逻辑. ...

  6. Android Jetpack Components of ViewModel 学习笔记

    Android Jetpack Components of Lifecycle 学习笔记 Android Jetpack Components of LiveData 学习笔记 Android Jet ...

  7. Android Jetpack架构组件之 Room(使用、源码篇)

    2019独角兽企业重金招聘Python工程师标准>>> 1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发 ...

  8. Android Jetpack组件之Hilt使用

    前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. And ...

  9. Android Jetpack组件App Startup简析

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

最新文章

  1. rocketmq同步发送超时时间_RocketMQ系列(一)基本概念
  2. 马斯克的脑机接口能如愿以偿吗?
  3. 结构方程模型_简单易懂,结构方程模型思路总结
  4. 中国自研数据库打破西方垄断:阿里成绩刷新世界尘封9年纪录,性能2倍于第二名...
  5. Qt Creator使用版本控制系统
  6. 【Linux】一步一步学Linux——dirs命令(了解)(24)
  7. awk命令扩展使用操作
  8. Ajax+Node.js前后端交互最佳入门实践(01)
  9. P1855 榨取kkksc03
  10. powerdesigner导出mysql数据库表结构到Excel
  11. 除了敲代码,程序员还能有什么副业?
  12. [EMNLP18]用序列标注来进行成分句法分析
  13. AWS 创建 pem 文件
  14. React:网络工具库
  15. java版本对应jdk版本_jdk版本对应数字
  16. GNSS RTK 北斗GPS接收机多径环境测试接收机自主完好性监测实验
  17. html自动跳转页面,HTML实现页面自动跳转的五种方法
  18. 西电电子线路实验课程设计通关模板
  19. 动态规划-自底向上的 0-1 背包问题
  20. 中青杯数学建模竞赛是什么级别的比赛_2020年第三届中青杯全国大学生数学建模竞赛...

热门文章

  1. Linux(CentOS)下安装Elasticsearch5.0.0
  2. mac地址规范及算法
  3. mysql 半同步 5.6及5.7
  4. 解决 QtCreator 3.5(4.0)无法输入中文的问题
  5. Hibernate性能优化之EHCache缓存
  6. ISO8583报文协议(转)
  7. 求1+2+…+n变态问题的两个很棒的解法
  8. 微服务“新秀”之Service Mesh
  9. python http get 请求_Python:编写HTTP Server处理GET请求
  10. 通用模块 -- Dubbo 用户模块