自学内容网 自学内容网

Android Jetpack 之再谈 ViewModel

引言

好几年前,当时是刚开始接触 ViewModel 和 LiveData 以及协程,当时已经写了一篇关于 ViewModel 源码理解的文章)。几年后的今天,这些组件库已经被广泛应用到实际项目中去。今天,我想对这篇文章做一些补充,补充一些之前没有提到或者一笔带过的部分,之所以会有这样的一篇补充文章,是因为一个测试问题,我们在文章最后再提。主要部分包括两方面:ViewModel 的数据恢复和创建。

角色概述

分析 ViewModel 的源码,一般会涉及到以下一些角色(类):

  • ViewModelProvider:仅仅用于获取 ViewModel 的实例,直接面向开发者的,它不会存储 ViewModel 的实例,也不参与 ViewModel 的创建,仅仅是一个获取实例的入口。
  • ViewModelStore:只负责存储 ViewModel,内部维护了一个 map<String, ViewModel>。
  • ViewModelFactory:只负责创 ViewModel 实例的创建工作。

每个类的角色和职能都很清晰,扩展性很强。

ViewModel 的 Activity 配置改变数据恢复

我们从一个例子入手:一个 Activity,上面一个 TextView,一个 Button。点击 Button 进行加1操作,TextView 用于显示当前值。
在这里插入图片描述

我们贴出 Activity 里的代码:

class ViewModelTestActivity : AppCompatActivity() {

    private lateinit var binding: ActivityViewModelTestBinding
    private val viewModel by viewModels<MyViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityViewModelTestBinding.inflate(layoutInflater)
        setContentView(binding.root)

       println("ViewModelTestActivity onCreate this = ${this.hashCode()}, viewmodel = ${viewModel.hashCode()}")

        with(viewModel) {
            countLiveData.observe(this@ViewModelTestActivity, ::onTextChanged)
        }

        binding.btnAdd.setOnClickListener {
            viewModel.increase()
        }
    }

    private fun onTextChanged(count: Int) {
        binding.textCount.text = "$count"
    }

    companion object {

        fun launch(context: Context) {
            context.startActivity(Intent(context, ViewModelTestActivity::class.java))
        }
    }
}

再贴出 ViewModel 的代码:

class MyViewModel :ViewModel() {
val countLiveData = MutableLiveData<Int>()

 fun increase() {
        val count = countLiveData.value.orZero()
        countLiveData.value = count + 1
    }
}

代码都很简单,我们现在旋转屏幕,使得 Activity 重建(AndroidManifest.xml 中不要配置 configChanges 以及 orientation)。先看屏幕上的显示情况:旋转前的值再旋转后恢复;我们再观察一下我们在 onCreate() 方法里写的日志结果:

ViewModelTestActivity onCreate this = 119218793, viewmodel = 155317131

ViewModelTestActivity onCreate this = 237186625, viewmodel = 155317131

我们可以看出,屏幕旋转前后,Activity 的对象发生了改变,ViewModel 的对象保持不变(注:用 hashCode 来判断对象不是那么精确,但是这里足以说明问题)。

通过以上现象,我们可以总结:可以使用 ViewModel 来实现对于 Activity 发生旋转等配置变化导致的重建的数据恢复,且这过程中 ViewModel 的实例不会创建。其实后半句实际上可以作为前半句的原因。那么后半句的原因又是什么呢?我们可以有如下猜测:

  • ViewModelLazy (viewModels() 返回的对象)不变,因为他会有一个 cached 成员
  • 在「角色概述」里我们提到,ViewModelStore 是用来缓存 ViewModel 对象的, 所以在这个过程中 ViewModelStore 不变

下面我们来结合源码分析一下。

private val viewModel by viewModels<MyViewModel>()
@MainThread
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
    noinline extrasProducer: (() -> CreationExtras)? = null,
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
    val factoryPromise = factoryProducer ?: {
        defaultViewModelProviderFactory
    }

    return ViewModelLazy(
        VM::class,
        { viewModelStore },
        factoryPromise,
        { extrasProducer?.invoke() ?: this.defaultViewModelCreationExtras }
    )
}
public class ViewModelLazy<VM : ViewModel> @JvmOverloads constructor(
    private val viewModelClass: KClass<VM>,
    private val storeProducer: () -> ViewModelStore,
    private val factoryProducer: () -> ViewModelProvider.Factory,
    private val extrasProducer: () -> CreationExtras = { CreationExtras.Empty }
) : 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,
                    extrasProducer()
                ).get(viewModelClass.java).also {
                    cached = it
                }
            } else {
                viewModel
            }
        }

    override fun isInitialized(): Boolean = cached != null
}

我们前面提过,Activity 对象在这个过程中对象已经重新创建了,因为 ViewModelLazy 对象实际上是 Activity 对象持有,所以 ViewModelLazy 对象肯定是重新创建的,所以我们的猜测一肯定是不对的。那么我们的重点分析就是关注 ViewModelStore 的变化。我们这里就以 Activity 为例。

class ComponentActivity {
private var _viewModelStore: ViewModelStore? = null
    override val viewModelStore: ViewModelStore
        get() {
            checkNotNull(application) {
                ("Your activity is not yet attached to the " +
                    "Application instance. You can't request ViewModel before onCreate call.")
            }
            ensureViewModelStore()
            return _viewModelStore!!
        }
        
   private fun ensureViewModelStore() {
        if (_viewModelStore == null) {
            val nc = lastNonConfigurationInstance as NonConfigurationInstances?
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                _viewModelStore = nc.viewModelStore
            }
            if (_viewModelStore == null) {
                _viewModelStore = ViewModelStore()
            }
        }
    }     
}

上面就是 ComponentActivity 关于 ViewModelStore 的相关源码。阅读 ensureViewModelStore() 的源码我们可知,ViewModeStore 有两处引用缓存:

  • ComponentActivity 的 _viewModelStore 实例成员
  • 由 lastNonConfigurationInstance 返回的 NonConfigurationInstances 对象中的 viewModelStore 实例成员

这两处都拿不到时才会创建新的对象。

由于 Activity 对象重建,所以 _viewModelStore 没有必要去跟踪。所以接下来的重点就是追踪 NonConfigurationInstances 的 viewModelStore 实例成员的初始化时机。可在 ComponentActivity 中通过「viewModelStore = 」进行检索,最后我们找到了这样一个方法 onRetainNonConfigurationInstance() :

final override fun onRetainNonConfigurationInstance(): Any? {
    // Maintain backward compatibility.
    val custom = onRetainCustomNonConfigurationInstance()
    var viewModelStore = _viewModelStore
  // 源码1
    if (viewModelStore == null) {
        // No one called getViewModelStore(), so see if there was an existing
        // ViewModelStore from our last NonConfigurationInstance
        val nc = lastNonConfigurationInstance as NonConfigurationInstances?
        if (nc != null) {
            viewModelStore = nc.viewModelStore
        }
    }
  // 源码2
    if (viewModelStore == null && custom == null) {
        return null
    }
  // 源码3
    val nci = NonConfigurationInstances()
    nci.custom = custom
    nci.viewModelStore = viewModelStore
    return nci
}

源码1和2处的 if 会进去吗?在我们的场景中,答案是不会。因为 _viewModelStore 在我们前面调用 viewModels<>() 返回 ViewModelLazy 时,就发生了对于 ComponentActivity.viewModelStore 属性的 get() 方法的调用,就会初始化 _viewModelStore 私有属性。所以会执行源码3之后的代码。可以看到,在 ComponentActivity.onRetainNonConfigurationInstance() 方法之中,完成了 NonConfigurationInstances 中 viewModelStore 实例成员的赋值,并且将 NonConfigurationInstances 作为方法返回值返回。

分析到这里,我们有两个环节没有搞清楚:

  • ensureViewModelStore() 方法中调用 lastNonConfigurationInstance 返回的 NonConfigurationInstances 对象的来源
  • ComponentActivity.onRetainNonConfigurationInstance() 返回的新创建的 NonConfigurationInstances 对象的去处。

这两个环节搞清楚,如果他们俩是同一个对象,我们整个过程就闭环了。

我们先来看看 lastNonConfigurationInstance 返回的对象来源:

它实际上调用的是 Activity.getLastNonConfigurationInstance() 方法:

public Object getLastNonConfigurationInstance() {
    return mLastNonConfigurationInstances != null
            ? mLastNonConfigurationInstances.activity : null;
}

通过查找源码,我们发现 mLastNonConfigurationInstances 是在 Activity.attach() 方法之中初始化的。一看到这个方法,我们就知道分析流程要来到 ActivityThread.performLaunchActivity() 方法中。
在这里插入图片描述

也就是说 mLastNonConfigurationInstances 对象赋值来自于 performLaunchActivity 的 ActivityClientRecord 参数。

我们再来看 ComponentActivity.onRetainNonConfigurationInstance() 由谁来调用:
在这里插入图片描述

它实际上是对于 Activity 类中的 onRetainNonConfigurationInstance() 的覆写。我们最终可以在 Activity 的 retainNonConfigurationInstances() 方法里找到它的调用:
在这里插入图片描述

​ 再到 ActivityThread 类中查找对于 retainNonConfigurationInstances() 方法的调用(performDestroyActivity):

在这里插入图片描述

我们知道,在旋转等配置发生变化时,会调用 ActivityThread 中的 handleRelaunchActivity() 方法:

ActivityThread.handleRelaunchActivity() -> ActivityThread.handleRelaunchActivityInner()

在这里插入图片描述

可以看到调用 handleDestroyActivity() 和 handleLaunchActivity() 传入的 ActivityClientRecord 对象是同一个。至此,整个过程就闭环了。

我们可以简单总结:之所以能够恢复数据,是因为 ViewModel 对象保持不变;而 ViewModel 对象之所以保持不变,因为存储改 ViewModel 的 ViewModelStore 对象保持不变;而 ViewModelStore 对象保持不变,是因为在 Activity 的 destroy 时把它包装进 NonConfigurationInstances 对象中赋值给了一个 ActivityClientRecord 对象,而在 launch 时又把这个 ActivityClientRecord 对象中存储的 NonConfigurationInstances 对象中 ViewModelStore 对象重新恢复。

ViewModel 在 Activity 发生内存回收时的数据恢复

还是用我们上面的那个 demo,我们去开发者选项中把 「不保留活动打开」,然后熄屏再打开,模拟内存回收,会发现数据没有正常的恢复。也就是说,单纯 ViewModel 是无法实现内存回收时的数据恢复。那么我们有什么方法来实现这一目的呢?我们来修改 MyViewModel 的代码:

class MyViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {

    val countLiveData = savedStateHandle.getLiveData<Int>(COUNT_KEY)

    fun increase() {
        val count = countLiveData.value.orZero()
        countLiveData.value = count + 1
    }

    companion object {
        private const val COUNT_KEY = "com.intsig.agp7.viewmodel#MyViewModel#countLiveData"
    }
}

我们的改动有两处:

  • MyViewModel 在构造器中声明了一个参数
  • 将 countLiveData 的初始化改为了 savedStateHandle.getLiveData<Int>(COUNT_KEY)

再次运行,重复上述操作,会发现数据能够正常恢复了。这里我们就借助于 SavedStateHandle 而实现了因为 Activity 内存回收而导致的数据恢复。回想在没有 ViewModel 之前,我们是借助于 onSaveInstanceState() 等方法来完成该场景下数据的恢复的,这里 SavedStateHandle 实际上和他们是一样的作用,因为由于现在大家都会把数据的相关处理放置 ViewModel 中进行,这样回收数据恢复也能够在 ViewModel 中进行。SavedStateHandle 机制相关的源码大家去阅读,切入时机就是 ComponentActivity.onSaveInstanceState() 和 onCreate() 方法的实现。

ViewModel 的创建

回顾一下 ViewModel 与 SavedStateHandle 一起使用时,我们在获取 ViewModel 对象时没有任何代码变化,换句话说我们不需要自定义 ViewModelFactory,我们可以总结下哪些情况不需要自定义 ViewModelFactory:

  • ViewModel 只有无参构造
  • ViewModel 的构造器参数只有 SavedStateHandle
  • ViewModel 继承自 AndroidViewModel,需要一个构造器参数 Application
  • ViewModel 需要两个构造器参数:Application,SavedStateHandle。注意他们俩的顺序不能反。

除此之外,我们如果有其他参数情况是需要通过自定义 ViewModelFactory 来实现 ViewModel 对象的创建。由于通过 ComponentActivity.viewModels 扩展函数获取 ViewModel 实例时,默认的 ViewModelFactory 是 SavedStateViewModelFactory,我们来看它的 create() 方法:

在这里插入图片描述

针对 return 语句中的 if…else 的执行,我们给出结论:在默认情况下会走到 if 语句,为啥我们后面再说。if 语句里面所做的事可以归结为:查找是否存在相应构造器,如果存在,调用该构造器创建对象,如果不存在,调用 AndroidViewModelFactory.create() 方法创建 ViewModel 对象。我们重点看下查找构造器的实现:

 val constructor: Constructor<T>? = if (isAndroidViewModel && application != null) {
     findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE)
 } else {
     findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE)
  }
internal fun <T> findMatchingConstructor(
    modelClass: Class<T>,
    signature: List<Class<*>>
): Constructor<T>? {
    for (constructor in modelClass.constructors) {
        val parameterTypes = constructor.parameterTypes.toList()
        if (signature == parameterTypes) {
            @Suppress("UNCHECKED_CAST")
            return constructor as Constructor<T>
        }
        if (signature.size == parameterTypes.size && parameterTypes.containsAll(signature)) {
            throw UnsupportedOperationException(
                "Class ${modelClass.simpleName} must have parameters in the proper " +
                    "order: $signature"
            )
        }
    }
    return null
}

private val ANDROID_VIEWMODEL_SIGNATURE = listOf<Class<*>>(
    Application::class.java,
    SavedStateHandle::class.java
)
private val VIEWMODEL_SIGNATURE = listOf<Class<*>>(SavedStateHandle::class.java)

从这里我们就可以验证我们前面关于不需要自定义 factory 的结论。

刚刚我们还遗留了一个点,就是为啥默认情况下会进 if。

在这里插入图片描述

在这里插入图片描述

可以看到,默认情况下会往 CreationExtras 中存入相应的键值对,所以在 if 语句中取出时是满足条件的。

那么这里的 CreationExtras 又是个啥呢?

假设我们的 ViewModelTestActivity 在启动时需要一个 id 的参数:

companion object {
    const val EXTRA_KEY_ID = "id"

    fun launch(context: Context, id: Long) {
        context.startActivity(Intent(context, ViewModelTestActivity::class.java).apply {
            putExtras(bundleOf(EXTRA_KEY_ID to id))
        })
    }
}

然后我们的 MyViewModel 的相关逻辑依赖于这里的 id,在构造器中添加了一个 Bundle 参数

class MyViewModel(
    savedStateHandle: SavedStateHandle,
    arguments: Bundle?
) : ViewModel() {

    val countLiveData = savedStateHandle.getLiveData<Int>(COUNT_KEY)

    init {
        println("docId = ${arguments?.getLong(ViewModelTestActivity.EXTRA_KEY_ID)}")
    }

    fun increase() {
        val count = countLiveData.value.orZero()
        countLiveData.value = count + 1
    }

    companion object {
        private const val COUNT_KEY = "com.intsig.agp7.viewmodel#MyViewModel#countLiveData"
    }
}

这样我们就需要自定义 factory 了:

companion object {
    private const val COUNT_KEY = "com.intsig.agp7.viewmodel#MyViewModel#countLiveData"

    val factory = viewModelFactory {
        initializer {
            MyViewModel(createSavedStateHandle(), this[DEFAULT_ARGS_KEY])
        }
    }
}

class ViewModelTestActivity : AppCompatActivity() {
 private val viewModel by viewModels<MyViewModel> { MyViewModel.factory }  
}

运行项目,我们可以看到 MyViewModel init 代码块中关于 docId 的日志。CreationExtras,顾名思义,就是在我们创建 ViewModel 时提供一些额外的参数,比如这里的 intent 中的 bundle。

最后还有一个点,就是前面提到的线上问题:这个问题也很简单,之前入职新公司,在编写 ViewModel 时,需要传入额外参数,然后就自定义了 factory,在 factory 中用了反射创建对象。由于混淆规则中针对 ViewModel 的处理是:

-keep public class * extends androidx.lifecycle.ViewModel;

所以导致在打混淆包时把 ViewModel 带参的构造器移除了,使用反射创建对象时会抛出异常。我们这里改一下混淆规则:

-keep public class * extends androidx.lifecycle.ViewModel {
    <init>(...);
}

或者直接使用 new 的方式来实现即可。

参考链接:

https://www.wanandroid.com/wenda/show/18930

https://developer.android.com/topic/libraries/architecture/viewmodel/viewmodel-savedstate

https://developer.android.com/topic/libraries/architecture/viewmodel/viewmodel-factories


原文地址:https://blog.csdn.net/xlh1191860939/article/details/142989538

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!