前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《Android编程权威指南》之UI状态的保存与恢复篇

《Android编程权威指南》之UI状态的保存与恢复篇

作者头像
用户8928967
发布2021-09-22 10:25:56
6220
发布2021-09-22 10:25:56
举报
文章被收录于专栏:用户8928967的专栏

本章主要学习使用ViewModel保存UI数据,修复GeoQuiz应用的UI状态丢失缺陷。

一、引入 ViewModel 依赖

ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。

它来自lifecycle-extensions的Android Jetpack库,目前 lifecycle-extensions 中的 API 已弃用。您可以为特定 Lifecycle 工件添加所需的依赖项。参考:「https://developer.android.com/jetpack/androidx/releases/lifecycle#declaring_dependencies」

代码语言:javascript
复制
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0-alpha03'

然后点击 Sync Now。

二、添加 ViewModel

创建 QuizViewModel

代码语言:javascript
复制
private const val TAG = "QuizViewModel"

class QuizViewModel : ViewModel() {

  init {
      Log.d(TAG, "ViewModel instance created")
  }

  /**
   * On cleared
   * onCleared()函数的调用恰好在ViewModel被销毁之前。适合做一些善后清理工作,比如解绑某个数据源。
   */
  override fun onCleared() {
      super.onCleared()
      Log.d(TAG, "ViewModel instance about to destroyed")
  }

}

访问ViewModel

书中访问ViewModel的方法已经被弃用了,正如前面所说,我的实践并非引入 lifecycle-extensions,因此实际代码有所小改动。

在MainActivity.class 的 onCeate()方法中加入:

代码语言:javascript
复制
override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
      ...
      val quizViewModel by lazy { ViewModelProvider(this).get(QuizViewModel::class.java) }

      ...
}

只是换了api去拿quizViewModel的实例而已。

在MainActivity首次访问QuizViewModel时,ViewModelProvider会创建并返回一 个QuizViewModel新实例。在设备配置改变之后,MainActivity再次访问QuizViewModel对象时,它返回的是之前创建的QuizViewModel。在MainActivity完成使命销毁时(比如用户按了回退键),ViewModel-Activity这对好朋友也就从内存里抹掉了。

2.1 ViewModel生命周期与ViewModelProvider

上述代码意味着,一个ViewModel实例和一个activity生命周期已经关联,不管关联activity处于什么状态,该ViewModel会一直保留在内存里,直到关联activity被销毁。

QuizViewModel和MainActivity步调一致

设备旋转时,ViewModel 也留在了内存里。

MainActivity和QuizViewModel经历设备旋转

运行GeoQuiz应用日志:

初次打开

旋转设备日志:(可以看出viewmodel并未重建,而是从内存中直接取第一次创建的)

旋转后

退出应用日志:(viewmodel才销毁)

退出app

小总结:QuizViewModel 和 MainActivity 的关系是单向的。某个activity会引用其关联ViewModel,反过来则不行。一个ViewModel绝不能引用activity或view,否则会引发内存泄漏。

当某个对象强引用另一个要被销毁的对象时,内存泄漏就会发生。这样的强引用会阻止垃圾回收器从内存里清理对象。设备配置改变带来的内存泄漏是常见问题。

2.2 向ViewModel添加数据

ViewModel 会保存关联用户界面所需数据,并整理格式化这些数据,以方便其他对象取用。这样就可以把屏幕展现逻辑从activity里删除,让其“瘦身”了。

代码语言:javascript
复制
class QuizViewModel : ViewModel() {

    var currentIndex = 0

    private val questionBank = listOf(
        Question(R.string.question_australia, true),
        Question(R.string.question_oceans, true),
        Question(R.string.question_mideast, false),
        Question(R.string.question_africa, false),
        Question(R.string.question_americas, true),
        Question(R.string.question_asia, true)
    )

    val currentQuestionAnswer: Boolean get() = questionBank[currentIndex].answer

    val currentQuestionText: Int get() = questionBank[currentIndex].textResId

    fun moveToNext() {
        currentIndex = (currentIndex + 1) % questionBank.size
    }
}

使用by lazy关键字,可以确保quizViewModel属性是val类型,而不是var类型。只在activity实例对象被创建后,才需要获取和保存QuizViewModel,也就是说,quizViewModel一次只赋一个值。

三、进程销毁时保存数据

上面讲述的是发生屏幕旋转等配置更改的情况下,activity会被销毁和重启,这个时候可以用viewmodel来自动保存数据与获取数据。但是,如果是整个Android系统内存不够用的情况下,app又不在前台,系统是可能直接清除掉整个app的进程,这个时候,viewmodel 就不管用了,因为它也不在了。

3.1 覆盖onSaveInstanceState(Bundle)函数

通过覆盖Activity.onSaveInstanceState(Bundle)的方式,就可以解决上述问题,当应用进程在意外被系统“杀死”的时候,帮用户保存一些不是很大的关键数据,从而在再次加载app的时候恢复状态。

3.2 保留实例状态与activity记录

增加一个暂存状态(stashed state)到activity生命周期:

完整的activity生命周期

注意,activity进入暂存状态并不一定需要调用onDestroy()函数。不过,onStop()和onSaveInstanceState(Bundle)是两个可靠的函数(除非设备出现重大故障)。

通常,覆盖onSaveInstanceState(Bundle)函数,在Bundle对象中,保存当前activity小的或暂存状态的数据;覆盖onStop()函数,保存永久性数据,比如用户编辑的文字等。

要测试系统内存不够杀死应用,进入开发者选项,将不保留活动开启,那么在应用启动后,点击了home键,系统就是自动去杀死app了。如图设置:

不保留活动

四、ViewModel与保存实例状态

保留实例状态和ViewModel都不是长期存储解决方案。如果应用需要长久存储数据,且完全不担心activity状态,那么请考虑使用持久化存储方案。(后续会学)

ViewModel 始终还是对内存数据进行操作,所以速度上来说会占优势,加上书中的GeoQuiz应用例子,题目都是硬编码的,不是从网络获取,而且数据也不多,不需要数据库来存储,因此对于此应用来说,使用ViewModel是方案还是很合理的。

因此,要处理设备配置更改 加上 系统发起的进程终止 两种情况,就结合使用 ViewModel 和 onSaveInstanceState() 方式来保存数据状态。

五、深入学习:Jetpack、AndroidX与架构组件

Jetpack库分为四大类:foundation、architecture、behavior和UI。

architecture类Jetpack库还有一个常见名字叫architecture component。ViewModel就是一种架构组件。

参考:https://developer.android.com/jetpack

六、深入学习:解决问题要彻底

意思就是通过禁止应用屏旋转,以此解决设备配置改变带来的UI状态丢失问题的方式太粗暴,也不能从根本解决问题,这也解决不了决进程销毁问题,在开发过程中,还会遇到其他的跟生命周期有关的问题,我们得查到根本,然后多学一些知识技术点,来解决开发问题!

七、最后

本篇的实践代码地址:

https://github.com/visiongem/AndroidGuideApp/tree/master/GeoQuiz

由于会按照自己的意思实践练习题,因此跟书中示例代码有少许不一样,仅供参考。有兴趣就自行完善喽。实践出真知呢!给自己加个油!🆙

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-08-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 妮K妮K妮 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、引入 ViewModel 依赖
  • 二、添加 ViewModel
    • 2.1 ViewModel生命周期与ViewModelProvider
      • 2.2 向ViewModel添加数据
      • 三、进程销毁时保存数据
        • 3.1 覆盖onSaveInstanceState(Bundle)函数
          • 3.2 保留实例状态与activity记录
          • 四、ViewModel与保存实例状态
          • 五、深入学习:Jetpack、AndroidX与架构组件
          • 六、深入学习:解决问题要彻底
          • 七、最后
          相关产品与服务
          数据库
          云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档