ViewModel原理初探

490 阅读7分钟

ViewModel基本使用

  • 概述

    • 为什么要使用ViewModel?

      • 当Activity重建时,会丢失数据,使用ViewModel托管的数据不会丢失
    • Activity重建场景:一般来讲,当重新绘制UI的时候,都会重建

  • 使用篇:Kotlin版本

    • 工程结构:

      image-20220306112544367

    • 实现效果:每点击一次按钮,可观察到TextView值加一;并且屏幕旋转前后数据不丢失

    • 实现思路:

      1. 环境准备:使用插件kotlin-android-extensions(不需要再findViewById)

         plugins {
             ……
             id 'kotlin-android-extensions'
         }
        
      2. 新建MyViewModel,继承ViewModel并维护数据

      3. 在MainActivity中声明MyViewModel变量,将变量与控件简单绑定即可

    • 实现细节:

      1. 新建MyViewModel,继承ViewModel并维护数据

         package com.wasbry.myapplication
         ​
         import androidx.lifecycle.ViewModel
         ​
         class MyViewModel : ViewModel() {
             //
             var number : Int = 0
         }
        
      2. 在MainActivity中

        1. 声明MyViewModel变量:在onCreate之外,并且不能直接实例化(直接实例化导致无法监听)

           private lateinit var myViewModel: MyViewModel
          
        2. 初始化ViewModel:在onCreate之内(这是新版本写法)

           //ViewModel init
           myViewModel = ViewModelProvider(this,ViewModelProvider.NewInstanceFactory())
               .get(MyViewModel::class.java)
          
        3. 将变量与控件简单绑定:依托插件,简化代码

           //textView
           textView.text = "${myViewModel.number}"
           ​
           button.setOnClickListener {
               textView.text = "${++myViewModel.number}"
           }
          

          这里有一个细节:++myViewModel.number,这个++ 只能写在前面不能写在后面;写在后面会导致一旦发生横竖屏切换,这个值会加一;但是后面随便怎么转值都是稳定的;

        4. MainActivity完整代码:

           package com.wasbry.myapplication
           ​
           import androidx.appcompat.app.AppCompatActivity
           import android.os.Bundle
           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)
           ​
                   //ViewModel init
                   myViewModel = ViewModelProvider(this,ViewModelProvider.NewInstanceFactory())
                       .get(MyViewModel::class.java)
           ​
                   //textView
                   textView.text = "${myViewModel.number}"
           ​
                   button.setOnClickListener {
                       textView.text = "${++myViewModel.number}"
                   }
               }
           }·
          
  • 未引入ViewModel时,Activity的缺点

    • Activity/Fragment代码臃肿,职责混乱不清

      • 处理业务逻辑
      • 处理Model层数据
      • 还需要实时刷新UI,此外当Activity重建时,数据丢失
  • 引入ViewModel后对项目的重构

    • 示意图:

      • 未引入:

        图片.png

      • 引入后:

      图片.png

    • 引入ViewModel:需要引入DataBinding+LiveData:实现双向绑定

    • 引入DataBinding+LiveData:

      • 剔除控件Id:使用LiveData 维护每条数据

      • 剔除控件监听

      • 剔除控件声明+findViewById

      • 解耦后的MainActivity

         public class MainActivity extends AppCompatActivity {
         ​
             //声明DataBinding与ViewModel实例
             private ActivityMainBinding binding;
             private MyViewModel myViewModel;
         ​
         ​
             @Override
             protected void onCreate(Bundle savedInstanceState) {
                 super.onCreate(savedInstanceState);
         ​
                 //初始化binding与myViewModel
                 binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
         //        //这个是继承ViewModel的,但是此时继承的是AnddroidViewModel
         //        myViewModel = new ViewModelProvider(this,
         //                new ViewModelProvider.NewInstanceFactory()).get(MyViewModel.class);
                 myViewModel = new ViewModelProvider(getViewModelStore(),
                         new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MyViewModel.class);
         ​
                 binding.setVm(myViewModel);
         ​
                 //将LiveData拿过来维护数据
                 binding.setLifecycleOwner(this);
             }
         }
        
    • 自定义ViewModel集成AndroidViewModel:需要上下文环境,加一个构造函数

      • 数据业务逻辑放到自定义ViewModel中去

      • 自定义ViewModel代码展示:

         public class MyViewModel extends AndroidViewModel {
         ​
             //定义上下文环境
             private Context context;
         ​
             //使用LiveData维护数据
             private MutableLiveData<String> info = new MutableLiveData<>();
         ​
             public MyViewModel(@NonNull Application application) {
                 super(application);
                 context = application;
             }
         ​
             //监听方法,在非Activity中启动Activity,打电话
             private void call(){
                 Intent intent = new Intent();
                 intent.setAction(Intent.ACTION_CALL);
                 intent.setData(Uri.parse("123"));
                 context.startActivity(intent);
             }
             //抽象出一系列监听器类方法
             public class OnClick{
                 public void action(){
         ​
                 }
             }
         }
        
    • 重构细节:

      • 在非Activity中启动Activity,需要添加标记

ViewModel源码分析

  • 整体执行流程:

    1. 首先去调用接口,去拿ViewModelStore保存的ViewModel数据(nc对象,就是那个集合)

      • 这个ViewModelStore为屏幕旋转之前的数据

      • ViewModel的生命周期很长:除非Activity被销毁掉才移除ViewModel

         //查看ComponentActivity源码
         getLifecycle().addObserver(new LifecycleEventObserver() {
             @Override
             public void onStateChanged(@NonNull LifecycleOwner source,
                     @NonNull Lifecycle.Event event) {
                 if (event == Lifecycle.Event.ON_DESTROY) {
                     //检测机制,只有当Activity被销毁时,才会解绑ViewModel,但是旋转的时候是不会解绑ViewModel的,内部有机制,这玩意儿是一个boolean类型变量:
                     if (!isChangingConfigurations()) {
                         getViewModelStore().clear();
                     }
                 }
             }
         });
        
      • 在移除的时候是处理nc对象中的HashMap集合

      • ViewModel中的生命周期会很长,有点像热启动,一直常驻在进程中

                     //检测机制,只有当Activity被销毁时,才会解绑ViewModel,但是旋转的时候是不会解绑ViewModel的,内部有机制,这玩意儿是一个boolean类型变量,常驻进程,热启动
                     if (!isChangingConfigurations()) {
                         getViewModelStore().clear();
                     }
        
    2. 只要执行屏幕旋转,那么就会回调onRetainNonConfigurationInstance,内部就去拿旋转之前的数据(nc对象,HashMap集合),nc就只有一份

    3. 并且在Activity被销毁时,会将数据缓存到nc中去(旋转不会,旋转是直接去拿),内部是有检测机制

  • ViewModel是如何构建的:查看ViewModelProvider构造函数

    • 代码

       //初始化ViewModel
       myViewModel = ViewModelProvider(this,ViewModelProvider.NewInstanceFactory())
                   .get(MyViewModel::class.java)
       //进入系统源码:ViewModelProvider构造函数一
           public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
               this(owner.getViewModelStore(), factory);
           }
       ​
       //进入系统源码:ViewModelProvider构造函数二
          public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
              //最终的成果
               mFactory = factory;
               mViewModelStore = store;
           }
      
    • 第一个参数分析(this):ViewModelStore,就是一个存储类()

      • 传入了:ViewModelStoreOwner接口
      • 为什么可以传这个接口:因为ComponentActivity实现了这个接口

        • 继承关系:

          图片.png

        • 接口类属性展示:

          图片.png

      • 为什么要搞这个接口:返回ViewModelStore

         //系统定义接口源码:
         @SuppressWarnings("WeakerAccess")
         public interface ViewModelStoreOwner {
             /**
              * Returns owned {@link ViewModelStore}
              *
              * @return a {@code ViewModelStore}
              */
             @NonNull
             ViewModelStore getViewModelStore();
         }
        
      • 为什么要返回这个ViewModelStore:相当于一个存储类(HashMap实现的)

        图片.png

    • 第二个参数:ViewModelProvider.NewInstanceFactory(),使用反射实例化ViewModel对象

      • 还是一个接口,此时进入NewInstanceFactory源码

        • 源码展示:

          image-20220307233732670

      • 为什么要搞这样一个接口:依赖倒置原则,面向接口编程(提升程序可扩展性)

        • 依赖倒置原则:面向接口编程

          • 面向高层,不面向低层
          • 面向抽象,不面向具体
        • 这个Factory有什么用:实例化ViewModel(使用反射)

           myViewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())
                       //使用反射处理
                       .get(MyViewModel::class.java)
          
    • ViewModelProvider构造函数一内部分析:

       //下面的this,
                   //第一个参数:调用ComponentActivity方法拿到保存的mViewModelStore
                   //第二个参数:用户指定的工厂方法
       this(owner.getViewModelStore(), factory);
      
    • ViewModelProvider构造函数二内部分析:

       public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
              //最终的成果,因为构造函数一,调用爷爷类的ComponentActivity中的getViewModelStore()方法,拿到保存的mViewModelStore
               mFactory = factory;
               mViewModelStore = store;
           }
      
    • 最终成果是如何拿到的:ComponentActivity.getViewModelStore()细节

      • 最终成果:ViewModelProvider构造函数二中保存的数据

         public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
                //最终的成果,因为构造函数一,调用爷爷类的ComponentActivity中的getViewModelStore()方法,拿到保存的mViewModelStore
                 mFactory = factory;
                 mViewModelStore = store;
             }
        
      • 流程:getApplication判空,空就用nc对象去拿;不空就返回缓存
           public ViewModelStore getViewModelStore() {
               if (getApplication() == null) {
                   throw new IllegalStateException()
               }
               
               if (mViewModelStore == null) {
                   NonConfigurationInstances nc =
                           (NonConfigurationInstances) getLastNonConfigurationInstance();
                   if (nc != null) {
                       // Restore the ViewModelStore from NonConfigurationInstances
                       mViewModelStore = nc.viewModelStore;
                   }
                   if (mViewModelStore == null) {
                       mViewModelStore = new ViewModelStore();
                   }
               }
               return mViewModelStore;
           }
      
  • 反射是怎么玩的?:.get(MyViewModel::class.java)执行流程

    • get代码(get函数是由工厂进行指定的)

       //ViewModelProvider.get
       .get(MyViewModel::class.java)
      
    • 执行流程:

      1. MainActivity实例化自定义ViewModel时调用了get函数

         myViewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())
             .get(MyViewModel::class.java)//就是这个
        
      2. 进入get源码,跳转到ViewModelProvider中的if判断:调用了create函数

         //ViewModelProvider中的,重点在(mFactory)).create(key, modelClass);
         if (mFactory instanceof KeyedFactory) {
                     viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
                 }
        
      3. 进入create函数,通过ViewModelProvider中的create接口调用到下图最后一行

        • create接口代码:

           @NonNull
           <T extends ViewModel> T create(@NonNull Class<T> modelClass);
          
        • 跳转关系:下图中选中的哪一行

          image-20220308004333566

      4. 进入NewInstanceFactory,反射执行代码

        image-20220308004414019

  • ViewModel是如何保证Activity重建时数据不丢失:分析横竖屏切换时Activity的重建流程

    • ams创建Activity时会将nc传给新的Activity

    • 整体流程图:

      图片.png

    • 具体流程分析:但是这个函数是过时了的,因为ViewModel出现了

      1. AMS通过IBinder跨进程通信

      2. 调用Activity中的retainNonConfigurationInstances()

      3. 调用onRetainNonConfigurationInstance()

        • 这是一个空函数:子类(ComponentActivity进行重写)

           public Object onRetainCustomNonConfigurationInstance() {
               return null;
           }
          
      4. 调用ComponentActivity.onRetainCustomNonConfigurationInstance

      5. 此时调用ComponentActivity.onRetainCustomNonConfigurationInstance

        • 还是一个空函数(由ComponentActivity的子类ainActivity进行重写)

           public Object onRetainCustomNonConfigurationInstance() {
               return null;
           }
          
      6. 在MainActivity中重写:onRetainCustomNonConfigurationInstance

           override fun onRetainCustomNonConfigurationInstance(): Any? {
                 Log.d("MainActivity", "横竖屏切换了 ")
                 return ""
             }
        
      7. 证明分析流程的正确性:运行应用打印日志即可(每进行一次横竖屏切换都会打印一次日志)

        图片.png

    • Activity重建是谁来维护的?:AMS(ActivityManagerService)

      • 重建时,会有IBinder跨进程通信
    • 为什么会有两次空函数?:ViewModel出现之前,用户可以自定义数据保存逻辑

      • 拿给LoginActivity等做临时数据保存
    • ComponentActivity.onRetainCustomNonConfigurationInstance分析

      • 流程分析:在旋转之前去拿上一次的数据(就是nc里面的HasHMap)

        1. 想去拿ViewModel的缓存:

           ViewModelStore viewModelStore = mViewModelStore;
          
          • 拿到了,将其保存在nc中

             nci.viewModelStore = viewModelStore;
            
          • 拿不到,从nc对象里面去拿

             viewModelStore = nc.viewModelStore;
            
      • 源码展示:

        图片.png

  • 框架细节:常见问题

    • LifeCycle:

      • 为什么要使用Jetpack
      • JetPack拿来干什么
      • 六版使用
      • 如何将观察者与被观察者关联起来的
      • 被观察者重写的生命周期函数,是如何在观察者中激活的
      • 为什么宿主要继承ComponentActivity,而不是Activity
      • 状态机详解(事件与状态)
      • 为什么LiveData不会造成内存泄漏
      • 为什么可以在不同的生命周期中注册眼睛
    • LiveData:

      • 基本使用
      • 为什么要定义单例类
      • 为什么要抽象出MutableLiveData
      • 更新UI时为什么要调用postValue,postValue流程解析
      • 如何证明LiveData数据粘性
      • 粘性数据是怎么来的
      • 如何感知数据
      • 为什么LiveData不会造成内存泄漏
    • LiveDataBus:Kotlin版本

      • 为什么可以替换掉EventBus
      • 自定义粘性数据开关(kotlin + Java)
      • 为什么封装自己的总线
      • 为什么要用集合
      • 为什么要用懒加载
      • 懒加载背后原理
      • 如何使用Kotlin手动模拟懒加载
      • 为什么要对LiveData进行二次封装
      • 为什么要私有化构造方法
      • 为什么要重写observe方法
      • 为什么要动态修改LiveData源码
      • 继承关系是怎样的
    • DataBinding:Java版本的

      • Android常见架构模型详解;

      • DataBinding数据MVVM?

      • VM层与ViewModel的关系

      • 打开DataBinding开关的两种方式

      • 为什么要在get上加注解

      • 为什么要在set中进行分发

      • BR文件是什么

      • DataBinding布局文件拆分细节:

        • DataBinding是如何管理布局中的控件的
        • 分层后的存储位置在哪里
        • 为什么可以废除原有的setContentView 语句
        • DataBinding语法细节(布局文件中的那个等号)
        • DataBinding排错思路
        • DataBinding实现机制
    • DataBinding:Kotlin版本的

      • 为什么不能使用注解
      • 如何覆写Kotlin中的内置函数
      • V层与VM层之间是如何建立起双向绑定的
      • 废除setContentView时,参数选择细节
      • DataBinding语法细节(布局文件中的那个等号),在源码中的处理
    • ViewModel:

      • 基本使用

      • ViewModel是如何实例化的

      • Activity重建场景

      • Activity重建时在Android系统中的函数流程

      • ViewModel怎么保存数据(nc对象+HashMap),

      • ViewModel是如何构建对象的

        • 两个构造函数分析
        • 为什么是依托于反射机制的
        • 反射的具体流程是什么