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

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

作者: 夜远曦白 | 来源:发表于2021-08-23 15:37 被阅读0次

    本章主要学习使用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

    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0-alpha03'
    

    然后点击 Sync Now。

    二、添加 ViewModel

    • 创建 QuizViewModel

      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()方法中加入:

      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里删除,让其“瘦身”了。

    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

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

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/pnfclltx.html