上一篇,写了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一起使用






























