Jetpack - ViewModel

544 阅读3分钟

什么是 ViewModel

Android 中,ViewModel 的作用就是在 UI 控制器(如 ActivityFragment)的生命周期中保存和管理 UI 相关的数据。ViewModel 保存的数据在配置更改(如屏幕旋转)后会依然存在,不会丢失。

ViewModel的使用场景

  • 防止因屏幕旋转导致 Activity 重建而丢失数据
  • 同一 Activity 中不同 Fragment 间数据共享
  • 防止销毁的 Activity 中未完成的异步回调

生命周期

image

ViewModel 使用

不配合 LiveData 使用

ShareViewModel.kt

class ShareViewModel: ViewModel() {
    val name = "张三"
}

MainActivity.kt

val model = ViewModelProvider(this).get(ShareViewModel::class.java)

tvContent.text = model.state

btClick.setOnClickListener {
    model.state = "newState"
    tvContent.text = model.state
}

配合 LiveData 使用

ShareViewModel.kt

class ShareViewModel : ViewModel() {

    val clickLiveData = MutableLiveData<String>()
}

MasterFragment.kt

val model = ViewModelProvider(this).get(ShareViewModel::class.java)
class MasterFragment: Fragment() {

    private lateinit var viewModel: ShareViewModel

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_master, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewModel = ViewModelProvider(activity!!).get(ShareViewModel::class.java)

        button.setOnClickListener {
            val value = viewModel.clickLiveData.value
            viewModel.clickLiveData.value = value?.plus(1) ?: 0
        }
    }
}

DetailsFragment.kt

class DetailsFragment: Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_details, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val viewModel = ViewModelProvider(activity!!).get(ShareViewModel::class.java)
        viewModel.clickLiveData.observe(this, Observer {
            textView.text = "${viewModel.clickLiveData.value}"
        })
    }
}

以上也是同一 Activity 中不同 Fragment 间数据共享的实现。

源码分析

为什么 ViewModel 可以防止因屏幕旋转导致 Activity 重建而丢失数据

获取 ViewModel 实例对象需要通过如下代码。

val model = ViewModelProvider(this).get(ShareViewModel::class.java)

ViewModelProvider 的构造函数中,主要是为 ViewModelStoreFactory 两个变量赋值。

先看下 ViewModelStore,其中包含一个 HashMap 存储 ViewModel,还有put()get()clear() 函数,putget 函数都是在 ViewModelProviderget 函数中调用,clear 函数是在 ComponentActivity 的构造函数中调用

public ComponentActivity() {

    getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    if (!isChangingConfigurations()) {
                        getViewModelStore().clear();
                    }
                }
            }
        });
}

这里看到调用 clear 函数的前提非改变配置导致的页面重建 。这就解释了为什么 ViewModel 可以防止因屏幕旋转导致 Activity 重建而丢失数据了。

同一 Activity 中不同 Fragment 间数据共享

不同 Fragment 间数据共享的重点是,获取他们所共同依赖的 Activity 中保存的 ViewModelStore,只有这样才可以得到 ViewModel 的同一实例对象,再配合 LiveData 即可实现数据变化的监听。代码在上方

防止销毁的 Activity 中未完成的异步回调

其实来说 ViewModel 自身的功能只有两个

  • 防止因屏幕旋转导致 Activity 重建而丢失数据
  • 同一 Activity 中不同 Fragment 间数据共享v

防止销毁的 Activity 中未完成的异步回调,这个功能是配合 LiveData 实现的。异步回调结果修改 LiveData 数据,通过观察者监听 LiveData 数据的修改更新 UI 显示,在 LiveData 篇章中可以知道当页面销毁时观察者删除,如此防止调用销毁页面的 UI

注意 !!!

ViewModel 绝不能引用视图、Lifecycle 或可能存储对 Activity 上下文的引用的任何类。

如果 ViewModel 需要 Application 上下文(例如,为了查找系统服务),它可以扩展 AndroidViewModel 类并设置用于接收 Application 的构造函数。

修改一下 ShareViewModel.kt 代码。

class ShareViewModel(app: Application): AndroidViewModel(app) {
    val clickLiveData = MutableLiveData<Int>()
}

直接运行会报错,提示无法创建 ViewModel。在上文中我们知道,在 ViewModelProvider 的构造函数中主要是为 ViewModelStoreFactory 两个变量赋值。ViewModelStore 我们已经知道它是用来保存 ViewModel 的,下面来看 Factory 的作用。

Factory 是一个接口,只定义了一个 create() 函数。

public interface Factory {
    @NonNull
    <T extends ViewModel> T create(@NonNull Class<T> modelClass);
}

很清楚能想到 Factory 的作用就是创建 ViewModelFactory 有一个子类 NewInstanceFactory,还有一个孙子类 AndroidViewModelFactory。他们都是通过反射获取 ViewModel 对象,区别在于 AndroidViewModelFactory 创建的是带 Application 参数的 ViewModel 对象。

那要获取 AndroidViewModel 的实例对象,需要使用 AndroidViewModelFactory, 如下:

ViewModelProvider(activity!!, ViewModelProvider.AndroidViewModelFactory(BaseApplication.instance!!)).get(ShareViewModel::class.java)

这样的话,如果想自定义更多参数的 ViewModel,还需要自定义 ViewModelFactory