上一篇,写了Jetpack中ViewModel的最简单的使用。
写完后,发给公司的实习生看了一下,结果就问,实例化ViewModel时,如果不使用viewModels扩展函数怎么写呢?
class MainActivity : AppCompatActivity() {
//依赖 implementation 'androidx.activity:activity-ktx:1.1.0'
private val numberPlusViewModel by viewModels<NumberPlusViewModel>();
}
获取ViewModel的实例
根据官方文档:ViewModel 存在的时间范围是从您首次请求 ViewModel 直到 Activity 完成并销毁。
因此,我们不能直接在onCreate中通过普通方式创建,否则每次Activity重建时都会重新实例化。
我看了目前网上的教程,大部分都是通过如下方式实例化的(已经废弃了):
class MainActivity : AppCompatActivity() {
lateinit var numberPlusViewModel: NumberPlusViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 通过ViewModelProviders获取ViewModel实例
numberPlusViewModel = ViewModelProviders.of(this).get(NumberPlusViewModel::class.java)
}
}
但是,以上方式官网已经明确废弃了。因为lifecycle-extensions 中的 API 已弃用。
参考:https://developer.android.com/jetpack/androidx/releases/lifecycle#declaring_dependencies
自 2.1.0 以来的重要变更:
- 弃用 ViewModelProviders.of():已弃用 ViewModelProviders.of()。您可以将 Fragment 或 FragmentActivity 传递给新的 ViewModelProvider(ViewModelStoreOwner) 构造函数,以便在使用 Fragment 1.2.0 时实现相同的功能。
直接上代码:
class MainActivity : AppCompatActivity() {
lateinit var numberPlusViewModel: NumberPlusViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 通过ViewModelProvider获取ViewModel实例
numberPlusViewModel = ViewModelProvider(this)[NumberPlusViewModel::class.java]
}
}
ViewModelProvider类构造函数如下:
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
即可以接收一个ViewModelStoreOwner,ViewModelStoreOwner是一个接口。
而我们在第一节学习Lifecycle的时候就提到,在jetpack中,我们的 Activity/Fragment 都默认实现了LifecycleOwner、ViewModelStoreOwner接口。因此构造函数中传入this即可。
numberPlusViewModel = ViewModelProvider(this)[NumberPlusViewModel::class.java]
viewModels 源码解析
@MainThread
inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: {
defaultViewModelProviderFactory
}
return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}
/**
* An implementation of [Lazy] used by [androidx.fragment.app.Fragment.viewModels] and
* [androidx.activity.ComponentActivity.viewmodels].
*
* [storeProducer] is a lambda that will be called during initialization, [VM] will be created
* in the scope of returned [ViewModelStore].
*
* [factoryProducer] is a lambda that will be called during initialization,
* returned [ViewModelProvider.Factory] will be used for creation of [VM]
*/
class ViewModelLazy<VM : ViewModel> (
private val viewModelClass: KClass<VM>,
private val storeProducer: () -> ViewModelStore,
private val factoryProducer: () -> ViewModelProvider.Factory
) : Lazy<VM> {
private var cached: VM? = null
override val value: VM
get() {
val viewModel = cached
return if (viewModel == null) {
val factory = factoryProducer()
val store = storeProducer()
ViewModelProvider(store, factory).get(viewModelClass.java).also {
cached = it
}
} else {
viewModel
}
}
override fun isInitialized() = cached != null
}
查看注释可知,viewModels的底层使用到了ViewModelProvider.Factory 来创建一个VM。
那ViewModelProvider.Factory 是什么? 继续查看源码:
public class ViewModelProvider {
public interface Factory {
/**
* Creates a new instance of the given {@code Class}.
* @param modelClass a {@code Class} whose instance is requested
* @param <T> The type parameter for the ViewModel.
* @return a newly created ViewModel
*/
@NonNull
<T extends ViewModel> T create(@NonNull Class<T> modelClass);
}
}
注释不要太清楚了, ViewModelProvider.Factory 是一个接口,其create方法就可以返回一个 ViewModel 实例。
既然是接口,那我们是不是可以实现这个接口,重新create方法,这样就可以为所欲为,可以给ViewModel传参了呢?
ViewModelProvider.Factory创建ViewModel
下面我们就重写,上一节的 点击按钮++的实例,并且我们要求默认的初始化的值可以随意指定,甚至进程杀死以后,还可以恢复之前的值。
1)xml 文件不变
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.528"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.313" />
<Button
android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/plusbtn"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.547"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv" />
</androidx.constraintlayout.widget.ConstraintLayout>
2) 创建一个带参数的ViewModel
class NumberPlusViewModelWithArg(var number: Int): ViewModel() {
}
3) 实现ViewModelProvider.Factory
class MyViewModelFactory(var number: Int): ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return modelClass.getConstructor(Int::class.java).newInstance(number)
}
}
4)Activity或Fragment中使用
class MainActivity : AppCompatActivity() {
lateinit var numberPlusViewModelWithArg: NumberPlusViewModelWithArg
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 注意这里我们传入了参数
numberPlusViewModelWithArg = ViewModelProvider(this,MyViewModelFactory(10))[NumberPlusViewModelWithArg::class.java]
tv.text = numberPlusViewModelWithArg.number.toString()
btn1.setOnClickListener {
numberPlusViewModelWithArg.number++
tv.text = numberPlusViewModelWithArg.number.toString()
}
}
}
效果如下:
好了,这样就OK了,如果希望在ViewModel 的生命周期结束后也保存初始化的值,使用SharedPreferences处理一下就好了。
PS: 下一节开始学习LiveData了,而且可以结合ViewModel一起使用