ViewModel简介
视图与数据模型之间的桥梁ViewModel
ViewModel是JetPack中的重要概念,用来分离页面逻辑,简化页面生命周期处理,而且可以让数据在页面发生旋转后继续保留。
ViewModel生命周期
ViewModel的生命周期会比创建它的Activity、Fragment的生命周期都要长。即ViewModel中的数据会一直存活在Activity/Fragment中。
众所周知,由于Android平台的特殊性,若应用程序发生屏幕旋转的时候会经历Activity的销毁与重建,这里就涉及到数据保存的问题。虽然Activity可以通过onSaveInstanceState()机制保存与恢复数据,但是onSaveInstanceState()方法只能存储少量的数据进行恢复,但是遇到大量的数据该怎么办呢?
幸运的是,ViewModel能完美的为我们解决这个问题,ViewModel有自己独立的生命周期,屏幕旋转所导致的Activity重建,并不会影响ViewModel的生命周期.
ViewModel 使用
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
ViewModel是一个抽象类,其中只有一个onCleared()方法。当ViewModel不再被需要,即与之相关的Activity都被销毁时,该方法会被系统调用。我们可以在该方法中执行一些资源释放的相关操作。注意: 当屏幕旋转而导致的Activity重建,并不会调用该方法。
我们可以在onCleared()对定时器资源的释放,防止造成内存泄露。
class MyViewModel : ViewModel() {
private val users: MutableLiveData<List<User>> by lazy {
MutableLiveData<List<User>>().also {
loadUsers()
}
}
fun getUsers(): LiveData<List<User>> {
return users
}
private fun loadUsers() {
// Do an asynchronous operation to fetch users.
}
override fun onCleared() {
super.onCleared()
//viewModel销毁时调用,可以做一些释放资源的操作
}
}
在Activity里使用
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val mainViewModel = ViewModelProvider(this).get(MyViewModel::class.java)
mainViewModel.getUsers()
//或者引入 extensions
//implementation "android.arch.lifecycle:extensions:1.1.1"
val mainViewModel = ViewModelProviders.of(this).get(MyViewModel::class.java);
}
}
在Fragment里使用
class ItemFragment : Fragment() {
var mainViewModel: MyViewModel? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainViewModel = ViewModelProvider(requireActivity()).get(MyViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_item_list, container, false)
return view
}
}
ktx 扩展获取ViewModel
ktx 扩展包在activity里扩展
ktx 扩展包在activity里ViewModel的依赖:
implementation "androidx.activity:activity-ktx:1.3.1"
ktx 扩展包在activity里获取ViewModel
在上面我们介绍了,在activity里默认获取 viewModel 实例的方法(比价繁琐),如下:
val mainViewModel = ViewModelProvider(this).get(MyViewModel::class.java)
ktx 扩展包在activity里获取ViewModel方法更简洁
class MainActivity : AppCompatActivity() {
val mainViewModel:MyViewModel by viewModels<MyViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainViewModel.name = "hhhh"
}
}
viewModels 扩展方法源码如下:
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: {
defaultViewModelProviderFactory
}
return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}
ktx 扩展包在Fragment里扩展
ktx 扩展包在Fragment里ViewModel的依赖:
implementation "androidx.fragment:fragment-ktx:1.3.6"
ktx 扩展包在Fragment里获取ViewModel
在 fragment 里我们以前默认获取ViewModel的方法比较繁琐
val mainViewModel = ViewModelProvider(requireActivity()).get(MyViewModel::class.java)
ktx 扩展包在Fragment里获取ViewModel方法更简洁
val mainViewModel: MyViewModel by activityViewModels()
class ItemFragment : Fragment() {
val mainViewModel: MyViewModel by activityViewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_item_list, container, false)
return view
}
}
AndroidViewModel
普通ViewModel使用注意事项:
使用ViewModel的时候,需要注意的是ViewModel不能够持有View、Lifecycle、Acitivity引用,而且不能够包含任何包含前面内容的类。因为这样很有可能会造成内存泄漏。
普通的 ViewModel 生命周期都很短,随着activity 销毁而销毁。如果我们要创建一个长生命周期的 ViewModel 怎么办? 其实Android 已经给我们提供了一个 AndroidViewModel
AndroidViewModel 持有了一个 Application ,所以它的生命周期会很长。
我们完全可以在 AndroidViewModel 中存储一些全局数据。
AndroidViewModel的源码:
public class AndroidViewModel extends ViewModel {
@SuppressLint("StaticFieldLeak")
private Application mApplication;
public AndroidViewModel(@NonNull Application application) {
mApplication = application;
}
/**
* Return the application.
*/
@SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
@NonNull
public <T extends Application> T getApplication() {
return (T) mApplication;
}
}
AndroidViewModel使用
让我的普通MyViewModel继承AndroidViewModel
class MyViewModel(val application: Application) : AndroidViewModel(application) {
override fun onCleared() {
super.onCleared()
//viewModel销毁时调用,可以做一些释放资源的操作
}
fun showToast(){
Toast.makeText(context,"toast",Toast.LENGTH_SHORT).show()
}
}
在项目的Activity的使用方式
class MainActivity : AppCompatActivity() {
private val viewModel: MyViewModelby viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
viewModel.showToast()
}
}
ViewModel onCleared 原理解析
你有没有想过,当 Activity
、Fragment
销毁的时候,ViewModel
的 onCleared
方法为什么会回调?
Activity 中的 ViewModel 原理
ComponentActivity
注册LifecycleEventObserver
, 在onDestory()
执行getViewModelStore().clear();
Fragment 中的 ViewModel 原理
因为 Activity 销毁导致 Fragment 销毁,对于 Fragment 来说,要复杂一下:
FragmentActivity
FragmentController
FragmentStateManager
FragmentManagerViewModel
因为 Fragment 替换导致 Fragment 销毁
比如在 Activity 先执行 , add 方法
supportFragmentManager.beginTransaction()
.add(R.id.fragc, MyFragmentA(), "sdsds")
.commitAllowingStateLoss()
再执行 replace 方法
supportFragmentManager.beginTransaction()
.replace(R.id.fragc, MyFragmentB(), "sdsds")
.commitAllowingStateLoss()
执行完 replace MyFragmentB , 那么 MyFragmentA 就会被销毁。对应的 MyFragmentA 中的 ViewModel 就会执行 onCleared 方法。
原理如下:
FragmentStateManager
FragmentManagerViewModel
这种情况和上一种不一样 。
本次情况 Activity 没销毁,是因为 fragment 执行 replace 导致前 fragment 销毁。
而上一种情况是因为 Activity 销毁,导致 fragment 销毁。
ViewModelProvider.Factory的使用
通常使用ViewModel的时候类似于以下方式
private val viewModel: BusScheduleViewModel by viewModels()
但是这种方式生成ViewModel的话,没有办法在构造函数中传递参数。所以需要使用到ViewModelProvider.Factory。
ViewModel保存状态的方式
虽然ViewModel已经帮我们处理了屏幕旋转导致的页面销毁重建导致的数据保留问题,但是对于由于内存不足等原因导致得页面销毁重建并没有做到数据保存。本篇主要根据官方文档和示例进行一个统一的完善和整理,辅以代码示例进行演示。
鸣谢:
ViewModel 概览 | Android Developers
【Kotlin】就几行代码?! 用SharedFlow写个FlowEventBus_mutablesharedflow 在单例中使用多次回调-CSDN博客