【Android】那么多的ViewModel创建方式都有何不同 & 如何自定义ViewModel及范围?

ViewModel的各种创建方式都有什么不同,如何自定义ViewModel及范围

前言

如果说Android开发其他组件都有的选,例如Flow与LiveData、Room与GreenDao等,但是ViewModel是我们现在Android开发必不可少的一环,对比其他平台如鸿蒙或Flutter的控制器那是真YYLX。

今天我们会简单的回顾一下ViewModel的重要知识点,复习ViewModel的创建销毁流程,本文的重点是ViewModel的各种创建方式包括Hilt的创建方式,为什么那么简单,到底是什么魔法。

其次在多页面实例对应的多ViewModel实例环境下,一个页面对应多个ViewModel实例,以及自定义Veiw、自定义Shatter碎片关联ViewModel处理逻辑的方式的探讨。

Ok,那我们就开始吧。

一、ViewModel的简单介绍与源码解析

ViewModel 类旨在以注重生命周期的方式存储和管理界面相关数据,ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。

在ViewModel出现之前,一般通过 onSaveInstanceState() 保存页面中的轻量关键数据,在页面 onCreate() 时机再将数据取出恢复界面,但这种方案仍然面临很多问题:Bundle数据大小问题,异步操作管理,用户操作状态的保存等。

ViewModel :

正常情况的onDestroy,ViewModel会跟随Activity销毁而销毁;

异常情况下的onDestroy,ViewModel不会跟随Activity销毁而销毁,因为要在Activity异常销毁之后重建时用来根据ViewModel的数据恢复界面。

ViewModel的源码解析?这感觉也没什么说的,跟着源码跳进去追踪其实相对来说是比较简单的。

重点一,两个内部容器,一个是Tag容器,一个是会被关闭的容器

重点二,ViewModelStore、ViewModelStoreOwner

ViewModelStore是一个容器,是存放ViewModel的仓库类,通过Key来区分不同的ViewModel实例。这个类提供了一种存储和管理ViewModel的方式,以确保数据在配置更改期间不丢失。

ViewModelStoreOwner是一个接口,它标识一个类能够拥有并提供ViewModelStore。任何想要存储ViewModels的组件都应该实现这个接口。这个接口定义了一个方法getViewModelStore(),该方法返回与组件关联的ViewModelStore实例。

简单的说,ViewModelStore是用来存储ViewModel实例的容器。而ViewModelStoreOwner是一个提供ViewModelStore实例的接口,它标识了一个类可以拥有ViewModels。

重点三,ViewModelProvider、Factory

ViewModelStore只是一个存储ViewModel的容器,它并没有创建ViewModel的功能,而ViewModelProvider这个类负责实例化和管理ViewModel对象。它使用Factory接口来创建ViewModel的实例,并确保每个Activity或Fragment得到其唯一的实例。

Factory 是一个定义了如何创建ViewModel实例的接口。它允许开发者自定义ViewModel的实例化过程,尤其是当ViewModel的构造需要参数时。

简单的说,ViewModelProvider用于管理ViewModel的生命周期和实例,而Factory则定义了如何创建这些ViewModel实例的具体方法。

二、ViewModel的创建方式

为什么那么多ViewModel的创建方式,他们有什么区别?

2.1 ViewModel的基础创建

过时的方法:

kotlin 复制代码
val viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)

其实跟踪 ViewModelProviders.of() 方法实现,内部就是new了一个 ViewModelProvider 对象,本质上还是通过 ViewModelProvider 来操作,只是每次都new对象,这种方法逐渐被视为不够优雅和直接,所以废弃了,推荐直接使用 ViewModelProvider 的方式相对更直接。

推荐的:

kotlin 复制代码
val viewModel = ViewModelProvider(this).get(MyViewModel::class.java)

直接使用ViewModelProvider类减少了不必要的抽象层,使得代码更加直接和清晰,也可以使得开发者可以更容易地控制ViewModel实例的创建和提供过程。

咦?不是说需要使用 Factory 来创建ViewModel吗,为什么他们都没有使用Factory?

因为无构造参数的创建我们可以省略 Factory 因为ViewModel 持有者已经内置了默认Factory,如Activity/Fragment等,关于有参数的ViewModel如何创建我们在后面说。

为什么我看到别人初始化ViewModel很简单直接用viewModels就可以了?

2.2 ViewModel的委托方法创建

在Activity的扩展中,我们可以通过委托的方法快速创建ViewModel,更加的简化流程。

csharp 复制代码
val viewModel3 by viewModels<MyViewModel>()

难道它使用了什么魔法?No,其实它内部也是调用了ViewModelProvider相关创建逻辑。

scss 复制代码
 val factoryPromise = factoryProducer ?: {
        defaultViewModelProviderFactory
    }

 ViewModelProvider(store,factory,extrasProducer()).get(viewModelClass.java)

可以看到

ViewModelProvider.Factory 使用的也是 ComponentActivity 中的默认实现defaultViewModelProviderFactory,如果你的 ViewModel 是带构造参数的,直接这么初始化那么也是会报错误的,因为需要自定义Factory来创建。

那么到底带构造参数的ViewModel如何创建呢?

2.3 ViewModel的带构造参数创建

一般我们的ViewModel是不带构造参数的,我们使用默认的扩展方式 viewModels 或者 ViewModelProvider 来创建都是通过内置的 defaultViewModelProviderFactory 来创建。

但是如果我们的 ViewModel 是带构造参数的呢,会不会有什么不同?

kotlin 复制代码
class MyViewModel(
   val application:Application,
   val param:String,
)

class MyViewModelFactory(
    private val application: Application,
    private val param: String
) : ViewModelProvider.Factory {

    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return MyViewModel(application, param) as T
    }
}

val vm : MyViewModel by viewModels {
    MyViewModelFactory(application, "some data")
}

那么我们使用扩展的方法和默认的方式其实都是一样的效果:

kotlin 复制代码
val vm: MyViewModel = ViewModelProvider(this, MyViewModelFactory(application, "some data")).get(MyViewModel::class.java)

本质上没有区别,只是扩展方法封装了一些过程而已,如果是有参数的ViewModel构建那么都离不开自定义ViewModelFactory。

所以只要是ViewModel带构造,那么我们必然要自定义我们的ViewModelFactory。

咦?你这话不对!为什么我使用 Hilt 构造 ViewModel 的时候,我们的 ViewModel 都是带参数的,我也没有使用自定义的ViewModelFactory,为什么我还是能无感创建ViewModel?

2.4 Hilt的创建有什么不同?

看看ViewModel的Hilt注入的情况下,带参数的创建:

less 复制代码
@HiltViewModel
class ProfileViewModel @Inject constructor(
    private val repository: ProfileRepository,
    val savedState: SavedStateHandle
):ViewModel()

咦,带参数的不是应该自定义 Factory 吗?为什么省略了这一步?

我们在使用 Hilt 注入的时候我们的 ViewModel 初始化不需要我们创建ViewModelFactory,那是因为Hilt帮我们创建好了。

我们可以通过kapt在编译期间编译的对应的编译产物对应的Factory中查看:

build/source/katp/xxx/ProfileViewModel_Factory

kotlin 复制代码
package com.newki.profile.mvi.vm;

import androidx.lifecycle.SavedStateHandle;
import com.newki.profile_api.repository.ArticleUserCase;
import com.newki.profile_api.repository.ProfileRepository;
import dagger.internal.DaggerGenerated;
import dagger.internal.Factory;
import dagger.internal.QualifierMetadata;
import dagger.internal.ScopeMetadata;
import javax.annotation.processing.Generated;
import javax.inject.Provider;

@ScopeMetadata
@QualifierMetadata
@DaggerGenerated
@Generated(
    value = "dagger.internal.codegen.ComponentProcessor",
    comments = "https://dagger.dev"
)
@SuppressWarnings({
    "unchecked",
    "rawtypes",
    "KotlinInternal",
    "KotlinInternalInJava"
})
public final class ProfileViewModel_Factory implements Factory<ProfileViewModel> {
  private final Provider<ProfileRepository> repositoryProvider;

  private final Provider<SavedStateHandle> savedStateProvider;

  private final Provider<ArticleUserCase> articleUserCaseProvider;

  public ProfileViewModel_Factory(Provider<ProfileRepository> repositoryProvider,
      Provider<SavedStateHandle> savedStateProvider,
      Provider<ArticleUserCase> articleUserCaseProvider) {
    this.repositoryProvider = repositoryProvider;
    this.savedStateProvider = savedStateProvider;
    this.articleUserCaseProvider = articleUserCaseProvider;
  }

  @Override
  public ProfileViewModel get() {
    ProfileViewModel instance = newInstance(repositoryProvider.get(), savedStateProvider.get());
    ProfileViewModel_MembersInjector.injectArticleUserCase(instance, articleUserCaseProvider.get());
    return instance;
  }

  public static ProfileViewModel_Factory create(Provider<ProfileRepository> repositoryProvider,
      Provider<SavedStateHandle> savedStateProvider,
      Provider<ArticleUserCase> articleUserCaseProvider) {
    return new ProfileViewModel_Factory(repositoryProvider, savedStateProvider, articleUserCaseProvider);
  }

  public static ProfileViewModel newInstance(ProfileRepository repository,
      SavedStateHandle savedState) {
    return new ProfileViewModel(repository, savedState);
  }
}

我们就可以使用

kotlin 复制代码
ViewModelProvider(this).get(ProfileViewModel::class.java)

或者

csharp 复制代码
val viewModel: ProfileViewModel by viewModels()

就能正常初始化了,内部Hilt会从依赖注入池中找到对应的构造参数需要的对象初始化并且创建对应的ViewModel。

等等,使用默认的方法创建,你也没指定 ProviderFactory,那它不还是用的 defaultViewModelProviderFactory 吗?那它是怎么指向Hilt生成的Factory的呢?

好问题!我们现在知道 AppCompatActivity 中有一个 defaultViewModelProviderFactory ,如果我们没指定自己的 Factory 那么就是用的它,毫无疑问我们使用Hilt的方式创建 ViewModel 的时候肯定是用的 defaultViewModelProviderFactory,但是没道理啊,默认的 Factory 不是只能创建无构造参数的ViewModel吗?

当你在使用 @AndroidEntryPoint 注解的Activity或Fragment中请求 ViewModel 时,Hilt会确保使用的是Hilt生成的、已经包含了所有必要依赖的ViewModelProvider.Factory实例。不信你在看看Hilt生成的这个类:

scala 复制代码
/**
 * A generated base class to be extended by the @dagger.hilt.android.AndroidEntryPoint annotated class. If using the Gradle plugin, this is swapped as the base class via bytecode transformation.
 */
@Generated("dagger.hilt.android.processor.internal.androidentrypoint.ActivityGenerator")
public abstract class Hilt_ProfileActivity<VM extends BaseViewModel, VB extends ViewBinding> extends BaseVVDLoadingActivity<VM, VB> implements GeneratedComponentManagerHolder {
  private volatile ActivityComponentManager componentManager;

  private final Object componentManagerLock = new Object();

  private boolean injected = false;

  Hilt_ProfileActivity() {
    super();
    _initHiltInternal();
  }

  private void _initHiltInternal() {
    addOnContextAvailableListener(new OnContextAvailableListener() {
      @Override
      public void onContextAvailable(Context context) {
        inject();
      }
    });
  }

  @Override
  public final Object generatedComponent() {
    return this.componentManager().generatedComponent();
  }

  protected ActivityComponentManager createComponentManager() {
    return new ActivityComponentManager(this);
  }

  @Override
  public final ActivityComponentManager componentManager() {
    if (componentManager == null) {
      synchronized (componentManagerLock) {
        if (componentManager == null) {
          componentManager = createComponentManager();
        }
      }
    }
    return componentManager;
  }

  protected void inject() {
    if (!injected) {
      injected = true;
      ((ProfileActivity_GeneratedInjector) this.generatedComponent()).injectProfileActivity(UnsafeCasts.<ProfileActivity>unsafeCast(this));
    }
  }

  @Override
  public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
    return DefaultViewModelFactories.getActivityFactory(this, super.getDefaultViewModelProviderFactory());
  }
}

那么现在的流程就变为:

当Activity(例如ProfileActivity)首次创建并请求一个ViewModel时,系统会调用getDefaultViewModelProviderFactory方法来获取一个Factory。

Hilt提供的DefaultViewModelFactories.getActivityFactory方法返回一个工厂,这个工厂内部配置了如何创建并提供带有依赖的ViewModel。

这个工厂使用Dagger/Hilt生成的组件(例如ProfileViewModel_HiltModule),来实例化ViewModel对象并注入所需的依赖。

总之,通过重写getDefaultViewModelProviderFactory方法并利用Hilt提供的默认工厂,结合编译时生成的具体ViewModel的Factory,Hilt完成了ViewModel的自动依赖注入过程,使得开发者在使用ViewModel时可以避免手动注入依赖的复杂性。

三、多页面实例的ViewModel与自定义ViewModel

每个Activity实例绑定的 ViewModel 实例是唯一的,这意味着如果你启动了相同的 Activity 的第二个实例,那么这第二个实例会绑定一个新的 ViewModel 实例,与第一个 Activity 的 ViewModel 实例不同。这是因为 ViewModel 的生命周期是跟 Activity或Fragment 的生命周期紧密相关的。

当你通过ViewModelProvider(this)获取ViewModel时,这里的this引用的是当前的Activity实例。ViewModelProvider会为每个Activity或Fragment实例创建一个独立的ViewModel实例。因此,当你启动相同Activity的新实例时,即使是相同类型的Activity,也会为每个实例创建一个新的ViewModel。

ViewModelStoreOwner 与 LifecycleOwner 的区别。

ViewModelStoreOwner 通过其 getViewModelStore() 方法提供 ViewModelStore 的实例。ViewModelStore 用于存储和管理 ViewModel 对象,并且负责管理 ViewModel 的生命周期,并在相关的组件(如 Activity)销毁时清理其中的 ViewModel 对象,以避免内存泄漏。

LifecycleOwner 是另一种功能,它可以通过 getLifecycle() 方法获取。Lifecycle 对象跟踪组件(如 Activity、Fragment)的生命周期,也可以用来观察生命周期状态的变化,可以注册 LifecycleObserver 监听生命周期事件,以便在适当的时机执行相关操作。

一个Activity/Fragment 一般来说即是ViewModelStoreOwner又是LifecycleOwner,所以两者是可以强转的。

所以在自定义对象中,我们如果传入了Activity或Fragment的对象,那么可以直接使用this,就能获取到 ViewModelStoreOwner 去创建 ViewModel 了。

如果你传入的是 context,那么你可以

scss 复制代码
 if (context instanceof ViewModelStoreOwner) {
    viewModel = ViewModelProvider((ViewModelStoreOwner) context).get(MyViewModel.class);
 }

如果传入的是 LifecycleOwner 对象,那么我们可以直接强转。

例如我们之前讲到的 Shatter 碎片

kotlin 复制代码
    inner class ShatterButton : Shatter() {

        lateinit var viewModel: MyViewModel
        override fun getLayoutResId(): Int = R.layout.shatter_register_button

        override fun onCreate(intent: Intent?) {
            super.onCreate(intent)
            viewModel = ViewModelProvider(lifecycleOwner as ViewModelStoreOwner)[MyViewModel::class.java]
        }

总结

你对ViewModel是如何创建、如何关联,如何销毁、如何恢复的,和onSaveInstanceState有什么区别?SaveStateHandle 有什么用都了解吗?

像这种纯源码解析和原理的文章,搜索引擎上有太多,我没有过多的深入描述,本文也是基于ViewModel的创建方式的探索,以及如何在非UI页面中使用ViewModel,就算是在自定义Vie或者自定义Shatter中使用ViewModel,那么逃不过的就是Activity和Fragment,不管是lifecycleOwner 还是 viewModelStoreOwner 都是Activity和Fragment,包括我们的Shatter碎片也都是依附与Activity和Fragment,所以在Shatter中使用ViewModel来管理UI逻辑也是很好的选择。

这种设计使得每个 ViewModel 能够专注于管理自己的数据和业务逻辑,不同的组件(如 Activity、Fragment、自定义 View、Shatter 碎片)可以分别使用不同的 ViewModel 来管理各自需要的数据和状态,从而更好地实现界面和数据的解耦和复用。

本文的代码测试基于 2024年Android开发架构推荐,防御性编程让同事再也看不懂我的代码

本文中的Shatter碎片实现基于 【Android】业务逻辑分离用UserCase,UI逻辑分离用什么?来自定义UI碎片吧

好了,闲话就说到这里,如果有其他的更多的更好的实现方式,也希望大家能评论区交流一起学习进步。如果我的文章有错别字,不通顺的,或者代码、注释、有错漏的地方,同学们都可以指出修正。

如果感觉本文对你有一点的启发和帮助,还望你能点赞支持一下,你的支持对我真的很重要。

Ok,这一期就此完结。

相关推荐
Kapaseker2 小时前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴2 小时前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭12 小时前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab13 小时前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
BoomHe18 小时前
Now in Android 架构模式全面分析
android·android jetpack
二流小码农1 天前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos
鹏程十八少1 天前
4.Android 30分钟手写一个简单版shadow, 从零理解shadow插件化零反射插件化原理
android·前端·面试
Kapaseker1 天前
一杯美式搞定 Kotlin 空安全
android·kotlin
三少爷的鞋1 天前
Android 协程时代,Handler 应该退休了吗?
android
火柴就是我2 天前
让我们实现一个更好看的内部阴影按钮
android·flutter