关注我的公众号 “安安安安卓” 免费学知识
大部分人更关心用法,所以我先讲用法, 再讲对 viewmodel 的理解,最后讲源码
来到 androidx,ViewModel 的创建方式与老版本有了很大的不同,所以这里还是要将 Viewmodel 的初始化讲一下
每次都会重新创建 model,并且不受 ViewModelStore 管控,所以无特殊需求禁止使用该种方式
使用 AndroidViewModelFactory 工厂创建
class AndroidFactoryViewModel(app:Application): AndroidViewModel(app) {
fun print(){
logEE("使用安卓默认工厂创建viewmodel")
}
}
val model = ViewModelProvider.AndroidViewModelFactory.getInstance(application)
.create(AndroidFactoryViewModel::class.java)
model.print()
每次都会重新创建 model,并且不受 ViewModelStore 管控,所以无特殊需求禁止使用该种方式
NewInstanceFactory
class SimpleFactoryViewModel: ViewModel() {
fun print(){
logEE("使用简单工厂创建viewmodel")
}
}
val model =ViewModelProvider.NewInstanceFactory().create(SimpleFactoryViewModel::class.java)
model.print()
多次创建可以复用 model,不会重新创建
默认的工厂只能创建带 Application 的 ViewModel 实例的,我们通过自定义工厂实现自定义构造参数的目的
class CustomAndroidViewModelFactory(val app: Application, private val data: String) :
ViewModelProvider.AndroidViewModelFactory(app) {
override fun create(modelClass: Class): T {
if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
return try {
modelClass.getConstructor(Application::class.java, String::class.java)
.newInstance(app, data)
} ...
}
return super.create(modelClass)
}
}
省略了 catch 中的代码,您可以到源码中查看
在我们的自定义工厂的构造中传入一个自定义的 data 参数,代表我们的自定义构造参数
viewmodel 类定义
必须是继承 AndroidViewModel 才可以
class CustomAndroidViewModel(private val app: Application, private val data: String) :
AndroidViewModel(app) {
fun print() {
logEE(data)
}
}
我们的 CustomAndroidViewModel 类中也有一个 data:String 参数,这个参数就是对应上一步中自定义工厂中的 data 参数
val model = ViewModelProvider(
viewModelStore,
CustomAndroidViewModelFactory(application, "自定义安卓工厂创建viewmodel")
).get(CustomAndroidViewModel::class.java)
model.print()
多次获取可以复用 model,不会重新创建
自定义简单工厂也是为了实现 viewmodel 构造传参的目的,废话不多说,直接上代码吧
class CustomSimpleViewModelFactory(app:Application,private val data:String) : ViewModelProvider.AndroidViewModelFactory(app) {
override fun create(modelClass: Class): T {
return try {
modelClass.getConstructor(String::class.java).newInstance(data)
} ......
}
}
class CustomSimpleViewModel(private val data: String) : ViewModel() {
fun print() {
logEE(data)
}
}
val model = ViewModelProvider(
viewModelStore,
CustomSimpleViewModelFactory(application, "自定义简单工厂创建viewmodel")
).get(CustomSimpleViewModel::class.java)
model.print()
多次创建可以复用 model,不会重新创建
google 官方给我们提供了 activity-ktx 库,通过它我们可以使用委托机制创建 viewmodel
实现方式如下:
implementation 'androidx.activity:activity-ktx:1.2.2'
class EnTrustModel : ViewModel() {
fun print(){
logEE("关注公众号 \"安安安安卓\" 免费学知识")
}
}
private val wtModel: EnTrustModel by viewModels {
ViewModelProvider.NewInstanceFactory()
}
wtModel.print()
ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。
我们知道 android 中的 Activity 和 Fragment 都是可以对生命周期进行管理的,如果 Activity/Fragment 被系统销毁,例如屏幕旋转就必须重新创建 Activity/Fragment,这个时候就涉及到一个数据恢复的问题。通常我们会使用 onSaveInstanceState 和 onRestoreSaveInstance 两个方法对数据进行保存和恢复,但是这两个方法只能处理数据量较小的情况。
如果是大数据量的情况那么用 viewmodel 就是不错的选择了
同时 viewmodel 还可以帮助我们将业务逻辑从 Activity/Fragment 中分离出来
同一个 activity 中出现多个 Fragment,并且这些 Fragment 之间需要进行通信,这样的需求是很常见的
以往我们可能会采用下列方式实现数据共享:
但是当我们学了 viewmodel 后就会发现,viewmodel 可以轻松实现 Fragment 之间的数据共享
我们可以让多个 Fragment 共用同一个 ViewModel 实现数据的共享。
本例中我们要实现下面的功能:
activity 中有两个 fragment,FragmentA 和 FragmentB,我们在 ViewModel 的构造方法中开始倒计时 2000s,在 FragmentA 中观察倒计时的数据并展示在页面上。然后再切换到 FragmentB,如果 FragmentB 中的倒计时的秒数没有重置为 2000,说明我们的数据共享成功了。
上代码:
class ShareDataModel: ViewModel() {
val liveData = MutableLiveData()
var total = 2000L
init {
/**
* 实现倒计时,一秒钟倒计时一次
*/
val countDownTimer = object :CountDownTimer(1000 * total, 1000) {
override fun onTick(millisUntilFinished: Long) {
liveData.postValue("剩余倒计时时间 ${--total}")
}
override fun onFinish() {
logEE("倒计时完成")
}
}
countDownTimer.start()
}
}
class FragmentA : Fragment() {
private val model: ShareDataModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_a, container, false).apply {
model.liveData.observe(viewLifecycleOwner,
{ value -> findViewById(R.id.tv_aresult).text = value })
}
}
}
class FragmentB : Fragment() {
private val model: ShareDataModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_b, container, false).apply {
model.liveData.observe(viewLifecycleOwner,
{ value -> findViewById(R.id.tv_bresult).text = value })
}
}
}
通过 gif 我们发现,即使 replace 后倒计时的数据仍然没有改变,说明我们成功实现了数据共享
先看一下创建 ViewModel 的代码:
val model = ViewModelProvider(
viewModelStore,
CustomSimpleViewModelFactory(application, "自定义简单工厂创建viewmodel")
).get(CustomSimpleViewModel::class.java)
那么就从 ViewModelProvider 构造方法和 get 方法说起吧,
ViewModelProvider 构造方法传入两个参数,ViewModelStore(用来存储管理 ViewModel)和 Factory(创建 ViewModel 的工厂)
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
public T get(@NonNull Class modelClass) {
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
}
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
getCanonicalName 方法可以获取一个能唯一标识 Class的字符串,最终会用作生成我们 ViewModelStore 中存储 ViewModel 的 key。
获取 key 后我们将 key 和 modelClass 做为参数调用重载 get 方法
方法的解读写在代码中了
public T get(@NonNull String key, @NonNull Class modelClass) {
ViewModel viewModel = mViewModelStore.get(key);//从ViewModelStore中根据key获取ViewModel,如果有就会返回
if (modelClass.isInstance(viewModel)) {//判断获取的ViewModel是否是传入的modelClass类型
if (mFactory instanceof OnRequeryFactory) {//不看这里
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
return (T) viewModel;//如果是就返回viewmodel
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) mFactory).create(key, modelClass);//一般不会用这个工厂
} else {
viewModel = mFactory.create(modelClass);//创建ViewModel
}
mViewModelStore.put(key, viewModel);//将ViewModel添加到mViewModelStore中
return (T) viewModel;
}
ViewModelStore 中有三个重要方法 get、put、clear,并且维护了一个 HashMap
final ViewModel get(String key) {
return mMap.get(key);
}
很简单就是根据key获取ViewModel
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);//oldViewModel指的是被覆盖的ViewModel
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
将一个 ViewModel 添加到 map 中,并且将被覆盖的 ViewModel 数据清理(因为同一个 activity 中只能存在一个同类型的 ViewModel)
下一节会讲
先看一下 clear 方法
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
clear 方法的调用位置在 ComponentActivity 的构造方法中,代码如下:
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
// Clear out the available context
mContextAwareHelper.clearAvailableContext();
// And clear the ViewModelStore
if (!isChangingConfigurations()) {//当有配置信息存在的时候返回:true(屏幕旋转,后台被杀死),当没有配置信息的时候返回:false(应用被主动杀死)。
getViewModelStore().clear();
}
}
}
});
恍然大悟,原来 ViewModel 通过 LifeCycle 观察 Activity 生命周期的方式来处理数据的清理操作
2021-06-20修订
之前写完后不久后发现ViewModelScope的不变性也是重要的一点,所以补充一下
我们知道我们的ViewModel是在ViewModelStore中存储的,所以当屏幕旋转或者被后台回收的时候其实ViewModelStore也是会被回收的,那么我们如何保证ViewModelStore在重新走onCreate方法的时候的不变性呢,
直接先晒代码:
ViewModelStore声明的位置如下:
静态内部类
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}
ComponendActivity中有如下代码:
@Override
@Nullable
@SuppressWarnings("deprecation")
public final Object onRetainNonConfigurationInstance() {
// Maintain backward compatibility.
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
// No one called getViewModelStore(), so see if there was an existing
// ViewModelStore from our last NonConfigurationInstance
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
if (viewModelStore == null && custom == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
onRetainNonConfigurationInstance方法解释:
Retain all appropriate non-config state. You can NOT override this yourself!
翻译:重新获取合适的配置状态,这个方法不可以被你自己重写
所以我们看完代码就清楚了,ViewModelStore在activity异常状态被销毁的时候会被保存,然后重新创建的时候会走ComponentActivity方法恢复配置。也就是说ViewModelStore被恢复成activity被异常销毁前的状态。当我们再调用ViewModeStore.get方法的时候获取的ViewModel还是原来的ViewModel而不会重新创建。
关注我的公众号 “安安安安卓” 免费学知识