ViewModel随笔小记

393 阅读4分钟

ViewModel原理分析

  • 使用:维护数据的稳定性

  • 使用场景:优化用户体验感

    • Activity重建(横竖屏切换,声明周期的问题)

      • 解决:将数据放到ViewModel中
  • 使用原理:除非Activity执行onDestroy销毁ViewModel才脱离Activity

使用ViewModel:解决横竖屏切换后数据丢失问题:

  • 问题描述:当页面进行横竖屏切换时,生命周期改变,导致数据丢失

  • 实例:点击计数按钮可观察到数字不断增加,一旦发生横竖屏切换后,数据就归零

  • 运行截图:

    • 竖屏状态下进行点击

      image-20211224100150118

    • 将手机横放:切换成横屏

      image-20211224100242802

    • 可以观察到数据被立即置空了

  • 代码示例:

    • 工程结构:

      MainActivity代码

       package com.derry.viewmodel
       ​
       import android.os.Bundle
       import android.util.Log
       import androidx.appcompat.app.AppCompatActivity
       import androidx.lifecycle.ViewModelProvider
       ​
       // 绑定机制
       import kotlinx.android.synthetic.main.activity_main.*
       ​
       class MainActivity : AppCompatActivity() {
       ​
           // 300个字段
            var number : Int = 0;
       ​
       ​
           override fun onCreate(savedInstanceState: Bundle?) {
               super.onCreate(savedInstanceState)
               setContentView(R.layout.activity_main)
       ​
               // myViewModel = MyViewModel()  不能直接实例化,因为如果能这样写,系统不可控了
           //当横竖屏切换时,没有这一句,数据会消失,但是不会丢失,只是隐形了
               text_number.text = number.toString()
               // 点击事件 lambda
               btn_plus.setOnClickListener {
                   text_number.text= (++number).toString()
       ​
               }
       ​
           }
       }
      
  • 解决思路:将数据交由ViewModel进行托管即可

    • MainActivity代码:

       package com.derry.viewmodel
       ​
       import android.os.Bundle
       import android.util.Log
       import androidx.appcompat.app.AppCompatActivity
       import androidx.lifecycle.ViewModelProvider
       ​
       // 绑定机制
       import kotlinx.android.synthetic.main.activity_main.*
       ​
       class MainActivity : AppCompatActivity() {
       ​
           private lateinit var myViewModel: MyViewModel
       ​
           override fun onCreate(savedInstanceState: Bundle?) {
               super.onCreate(savedInstanceState)
               setContentView(R.layout.activity_main)
       ​
       //        Log.d("DDD", "onCreate: ")
       ​
               // myViewModel = MyViewModel()  不能直接实例化,因为如果能这样写,系统不可控了
       ​
               // 旧版本的写法,更新特别快(扩展性不强)
               // ViewModelProviders.of()
       ​
               // this == ViewModelStoreOwner,
               myViewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())
                   .get(MyViewModel::class.java)
       ​
               // findv  text_number
               text_number.text = "${myViewModel.number}"//当横竖屏切换时,没有这一句,数据会消失
       ​
               // 点击事件 lambda
               btn_plus.setOnClickListener {
                   text_number.text = "${++myViewModel.number}"
               }
       }
      

如何使用组件库对Activity简化

  • activity过去的责任:拆分以后变成管理者,只做绑定的维护;体现出单一职责

    • 横竖屏切换
    • 逻辑代码
    • UI的刷新
    • UI初始化
    • activity依赖布局控件
  • 拆分:

    • ViewModel:

      • 逻辑
      • 横竖屏切换
    • DataBinding:是可以绑定很多东西

      • UI的刷新,DataBinding+LiveData
      • UI初始化
      • 依赖布局文件
  • 异常:

    • 提示信息:

      AndroidStudio报错:Unable to load class 'javax.xml.bind.JAXBException'. This is an unexpected error

    • 解决办法:调整项目配置版本

      image-20211224111159990

  • 长按的话还是需要findViewById

  • 概述:

    • MVC在后端是比较好的,但是在Android端比较难

ViewModel 源码分析:

  • 源码示意:

     myViewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())
         .get(MyViewModel::class.java)
    
    • 第一个参数:this,就代表了ViewModeStoreOwner代表了这个借口

      image-20211226195313014

    • 接口示意图:

      • 这个接口会返回ViewModelStore:一个存储类

      image-20211226195523270

    • ViewModelStore示例:

      • 这是一个存储类;存储了ViewModel
      • 通过遍历哈希值实现清空(clear等操作)

      image-20211226195811680

  • 第二个参数:ViewModelProvider.NewInstanceFactory()

    • 使用了依赖倒转原则(Factory factory):面向高层,面向抽象

      • 很多XXXFactory接口(NewFactory):有无数的子类,但他的父类就是Factory借口,后面我们只需要处理Factory
      • Factory:实例化ViewModel
    • 为了实现这个:就是去保存ViewModel

      image-20220102185836753

如何拿到这个ViewModel

  • 调用了getViewModelStore

    image-20220102190233485

  • 进入getViewModelStore()

    image-20220102190304807

  • nc 去拿到ViewModelStore对象

    image-20220102190407242

横竖屏切换执行流程(Activity重建与AMS通信,这个是重点)

  • 概述:横竖屏切换会引起Activity重建

  • Activity重建:由ActivityManagerService(AMS通信机制)通过IBinder进行处理

    • 核心:调用Activity.java中的retainNonConfigurationInstances()这个函数;

    • 这个是空函数

      image-20220102190918186

      为什么是空函数: 想让子类完成重写

      • Activity的子类是ComponentActivity,在ComponentActivity中也有一个叫retainNonConfigurationInstances()的空函数;
      • 那么开始套娃:想调用ComponentActivity的子类MainActivity中的这个函数
      • 但是MainActivity中是没有这个函数的--->在MainActivity中重写这个函数
    • 在MainActivity中重写这个函数:retainNonConfigurationInstances()并观察结果

           override fun onRetainCustomNonConfigurationInstance(): Any? {
               Log.d("MainActivity", "发生横竖屏切换了 ")
               return "DDDDDDD"
           }
      
    • 运行结果:发生很竖屏切换时,会打印出相应的日志信息

      image-20220102192411158

  • 这个函数的内在实现机理:保证MViewModelStore在Activity重建前后是同一份

    • 代码展示:

      image-20220102203757578

    • 执行流程:将上一次的数据拷贝到这一次

      • 当发生Activity重建(横竖屏切换等),就会调用这个函数

      • mViewModelStore的保存:

        • 本次ViewModelStore为空:将上一次的ViewModelStore 拿过来
        • 不为空,保存此时ViewModelStore(放在nc里面);拿给下一次用
    • ViewModelStore中装的是什么?

      • ViewModelStore装HashMap,HashMap装ViewModel,ViewModel装想保存的数据

      • 代码截图展示:

        image-20220102204419978

  • 此函数存在的意义:

    • 在ViewModel出现之前,可以去重写这个函数实现临时变量(LoginActivity)的保存;
    • ViewModel代替了这个函数的功能,所以为什么这个函数是过时的
  • nc如何进行保存:

    • AMS在创建Activity中会把nc拿给新创建的Activity;
    • nc 对象这个才是最重要的
  • Activity通信:跨进程

反射代码

  • 代码:

       myViewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())
                .get(MyViewModel::class.java)//反射实现实例化(决定用哪个Factory进行实例化)
    

实现细节:

  • ViewModel生命周期:这个是重点

    • 只有在Activity销毁是才会移除所有的ViewModel(依次移除),此时ViewModel的生命周期才结束
  • 重建时机:这里有个拦截

    • 代码截图:

      image-20220102210016021

  • 热启动:

    • 进程在,这个就在
    • 旋转的时候是不会清空ViewModel的