ViewModel基本使用
-
概述
-
为什么要使用ViewModel?
- 当Activity重建时,会丢失数据,使用ViewModel托管的数据不会丢失
-
Activity重建场景:一般来讲,当重新绘制UI的时候,都会重建
- 横竖屏切换
- 手机语言中英文切换
- 参考博客:www.jianshu.com/p/067209fd8…
-
-
使用篇:Kotlin版本
-
工程结构:
-
实现效果:每点击一次按钮,可观察到TextView值加一;并且屏幕旋转前后数据不丢失
-
实现思路:
-
环境准备:使用插件kotlin-android-extensions(不需要再findViewById)
plugins { …… id 'kotlin-android-extensions' } -
新建MyViewModel,继承ViewModel并维护数据
-
在MainActivity中声明MyViewModel变量,将变量与控件简单绑定即可
-
-
实现细节:
-
新建MyViewModel,继承ViewModel并维护数据
package com.wasbry.myapplication import androidx.lifecycle.ViewModel class MyViewModel : ViewModel() { // var number : Int = 0 } -
在MainActivity中
-
声明MyViewModel变量:在onCreate之外,并且不能直接实例化(直接实例化导致无法监听)
private lateinit var myViewModel: MyViewModel -
初始化ViewModel:在onCreate之内(这是新版本写法)
//ViewModel init myViewModel = ViewModelProvider(this,ViewModelProvider.NewInstanceFactory()) .get(MyViewModel::class.java) -
将变量与控件简单绑定:依托插件,简化代码
//textView textView.text = "${myViewModel.number}" button.setOnClickListener { textView.text = "${++myViewModel.number}" }这里有一个细节:++myViewModel.number,这个++ 只能写在前面不能写在后面;写在后面会导致一旦发生横竖屏切换,这个值会加一;但是后面随便怎么转值都是稳定的;
-
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后对项目的重构
-
示意图:
-
未引入:
-
引入后:
-
-
引入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源码分析
-
整体执行流程:
-
首先去调用接口,去拿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(); }
-
-
只要执行屏幕旋转,那么就会回调onRetainNonConfigurationInstance,内部就去拿旋转之前的数据(nc对象,HashMap集合),nc就只有一份
-
并且在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实现了这个接口
-
继承关系:
-
接口类属性展示:
-
-
为什么要搞这个接口:返回ViewModelStore
//系统定义接口源码: @SuppressWarnings("WeakerAccess") public interface ViewModelStoreOwner { /** * Returns owned {@link ViewModelStore} * * @return a {@code ViewModelStore} */ @NonNull ViewModelStore getViewModelStore(); } -
为什么要返回这个ViewModelStore:相当于一个存储类(HashMap实现的)
-
第二个参数:ViewModelProvider.NewInstanceFactory(),使用反射实例化ViewModel对象
-
还是一个接口,此时进入NewInstanceFactory源码
-
源码展示:
-
-
为什么要搞这样一个接口:依赖倒置原则,面向接口编程(提升程序可扩展性)
-
依赖倒置原则:面向接口编程
- 面向高层,不面向低层
- 面向抽象,不面向具体
-
这个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) -
执行流程:
-
MainActivity实例化自定义ViewModel时调用了get函数
myViewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()) .get(MyViewModel::class.java)//就是这个 -
进入get源码,跳转到ViewModelProvider中的if判断:调用了create函数
//ViewModelProvider中的,重点在(mFactory)).create(key, modelClass); if (mFactory instanceof KeyedFactory) { viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass); } -
进入create函数,通过ViewModelProvider中的create接口调用到下图最后一行
-
create接口代码:
@NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass); -
跳转关系:下图中选中的哪一行
-
-
进入NewInstanceFactory,反射执行代码

-
-
-
ViewModel是如何保证Activity重建时数据不丢失:分析横竖屏切换时Activity的重建流程
-
ams创建Activity时会将nc传给新的Activity
-
整体流程图:
-
具体流程分析:但是这个函数是过时了的,因为ViewModel出现了
-
AMS通过IBinder跨进程通信
-
调用Activity中的retainNonConfigurationInstances()
-
调用onRetainNonConfigurationInstance()
-
这是一个空函数:子类(ComponentActivity进行重写)
public Object onRetainCustomNonConfigurationInstance() { return null; }
-
-
调用ComponentActivity.onRetainCustomNonConfigurationInstance
-
此时调用ComponentActivity.onRetainCustomNonConfigurationInstance
-
还是一个空函数(由ComponentActivity的子类ainActivity进行重写)
public Object onRetainCustomNonConfigurationInstance() { return null; }
-
-
在MainActivity中重写:onRetainCustomNonConfigurationInstance
override fun onRetainCustomNonConfigurationInstance(): Any? { Log.d("MainActivity", "横竖屏切换了 ") return "" } -
证明分析流程的正确性:运行应用打印日志即可(每进行一次横竖屏切换都会打印一次日志)
-
-
Activity重建是谁来维护的?:AMS(ActivityManagerService)
- 重建时,会有IBinder跨进程通信
-
为什么会有两次空函数?:ViewModel出现之前,用户可以自定义数据保存逻辑
- 拿给LoginActivity等做临时数据保存
-
ComponentActivity.onRetainCustomNonConfigurationInstance分析
-
流程分析:在旋转之前去拿上一次的数据(就是nc里面的HasHMap)
-
想去拿ViewModel的缓存:
ViewModelStore viewModelStore = mViewModelStore;-
拿到了,将其保存在nc中
nci.viewModelStore = viewModelStore; -
拿不到,从nc对象里面去拿
viewModelStore = nc.viewModelStore;
-
-
-
源码展示:
-
-
-
框架细节:常见问题
-
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是如何构建对象的
- 两个构造函数分析
- 为什么是依托于反射机制的
- 反射的具体流程是什么
-
-