Android之ViewModel

ViewModel是什么

官网上的介绍是这样的

The ViewModel class is a business logic or screen level state holder. It exposes state to the UI and encapsulates related business logic. Its principal advantage is that it caches state and persists it through configuration changes. This means that your UI doesn’t have to fetch data again when navigating between activities, or following configuration changes, such as when rotating the screen.

ChatGPT总结以下3点

  • 生命周期感知 感知与其关联的生命周期,适当的时候清理资源,避免内存泄漏
  • 配置更改时保留数据 ViewModel生命周期超出了单个Activity/Fragment,所以配置更改时不会丢失数据
  • 分离业务逻辑 将 UI 相关的业务逻辑和数据管理从界面控制器中分离,使得代码更易于维护和测试

ViewModel使用

作为例子说明,我们需要准备一个类继承自ViewModel,暂且命名为MainViewModel

class MainViewModel: ViewModel() { 
    ...
}

接下来是在Activity/Fragment/可组合项中使用ViewModel,创建ViewModel对象

  • 不需要依赖注入

// 使用扩展函数
private val mainViewModel by viewModels<MainViewModel>()
// 或者直接点 使用ViewModelProvider
private val mainViewModel: MainViewModel = ViewModelProvider(this).get()
  • 使用hilt依赖注入

当你使用hilt依赖时,MainViewModel需要给类和主构造函数分别添加@HiltViewModel@Inject两个注解

@HiltViewModel
class MainViewModel @Inject constructor(): ViewModel() {
    ...
}

接下来调用函数hiltViewModel()拿到ViewModel对象

val mainViewModel: MainViewModel = hiltViewModel()
  • 使用koin依赖注入

使用koin注入时,首先需要在module中注入对象

module {
    viewModel {
        MainViewModel()
    }
}

然后直接使用即可

val mainViewModel: MainViewModel = koinViewModel()

以上便是常见的创建ViewModel的几种方式,主要取决于你项目使用依赖库。不过不管是哪种,其内部都是经过ViewModelProvider通过Factory来实现的。

ViewModel的生命周期

在关于ViewModel的介绍中,ViewModel是有生命周期的,如下图所示

那ViewModel如何实现生命周期管理的呢?
前文提到, ViewModel对象通过ViewModelProvider(this).get()返回,那先看看get()方法

@MainThread
public inline fun <reified VM : ViewModel> ViewModelProvider.get(): VM = get(VM::class.java)

public open class ViewModelProvider @JvmOverloads constructor(
    private val store: ViewModelStore,
    private val factory: Factory,
    private val defaultCreationExtras: CreationExtras = CreationExtras.Empty,
) {
    @MainThread
    public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
        val canonicalName = modelClass.canonicalName
            ?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
        return get("$DEFAULT_KEY:$canonicalName", modelClass)
    }

    @Suppress("UNCHECKED_CAST")
    @MainThread
    public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
        val viewModel = store[key]
        if (modelClass.isInstance(viewModel)) {
            (factory as? OnRequeryFactory)?.onRequery(viewModel!!)
            return viewModel as T
        } else {
            @Suppress("ControlFlowWithEmptyBody")
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        val extras = MutableCreationExtras(defaultCreationExtras)
        extras[VIEW_MODEL_KEY] = key
        // AGP has some desugaring issues associated with compileOnly dependencies so we need to
        // fall back to the other create method to keep from crashing.
        return try {
            factory.create(modelClass, extras)
        } catch (e: AbstractMethodError) {
            factory.create(modelClass)
        }.also { store.put(key, it) }
    }
}

概括下上面的代码,通过一个key去store中找,如果存在,则直接返回,否则通过factory创建ViewModel对象并调用store的put方法保留到store中,而这个store就是构造函数中的ViewModelStore。那么ViewModelStore是什么呢?继续往下看

public open class ViewModelProvider @JvmOverloads constructor(
    private val store: ViewModelStore,
    private val factory: Factory,
    private val defaultCreationExtras: CreationExtras = CreationExtras.Empty,
) {
  public constructor(
      owner: ViewModelStoreOwner
  ) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))
}

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        ContextAware,
        LifecycleOwner,
        ViewModelStoreOwner,
        ...
        FullyDrawnReporterOwner {
    @Override
    public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        ensureViewModelStore();
        return mViewModelStore;
    }

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    void ensureViewModelStore() {
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
    }
}

open class ViewModelStore {
    private val map = mutableMapOf<String, ViewModel>()
    ...
    fun clear() {
        for (vm in map.values) {
            vm.clear()
        }
        map.clear()
    }
}

简单解释下,创建ViewModelProvider时,传入this即ComponentActivity,而ComponentActivity实现了ViewModelStoreOwner接口提供ViewModelStore对象,ensureViewModelStore()是为了保证ViewModelStore不为空。ViewModelStore内部通过一个LinkedHashMap管理和存储ViewModel对象的。
目前,我们了解了ComponentActivityViewModelStoreViewModel之间的关系了,总结一下:ComponentActivity实现了ViewModelStoreOwner接口从而提供ViewModelStore对象,ViewModelStore用来管理ViewModel对象的。当调用ViewModelProviderget()方法获取ViewModel对象时,首先会看ViewModelStore是否已经存在该ViewModel对象,如果存在,则直接返回,否则先创建ViewModel对象并put到ViewModelStore中。不过还没谈到Lifecycle呀。

要谈生命周期,自然想到Lifecycle,接下来请看ComponentActivity中的这块代码

public ComponentActivity() {
    Lifecycle lifecycle = getLifecycle();
    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();
                }
                ...
            }
        }
    });
}

结合生命周期的配图,是不是一目了然?在Activity经过旋转屏幕导致Activity重建时,!isChangingConfigurations()不成立,ViewModelStore中还保留着原来的ViewModel对象,在恢复过程中继续可以通过ViewModelStore得到原来的对象和数据。但如果不是因为配置变更导致onDestory时,则清空ViewModelStore,并逐一执行ViewModel对象的clear方法,生命周期结束。

如何保证数据在配置更改期间的一致性

在JVM中,想要保证数据,必须内存中有其备份。所以这个问题可以理解成,在配置更改时,ViewModel会被保存在哪里而避免在Activity重建过程中被销毁回收?
通过前文,了解到ViewModel保存在ViewModelStore中管理,其中有一个方法ensureViewModelStore()保证了ViewModelStore不为空。

@SuppressWarnings("WeakerAccess") /* synthetic access */
void ensureViewModelStore() {
    if (mViewModelStore == null) {
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
}

TODO 待完成 结合前文,加上NonConfigurationInstances解释即可

ViewModel VS onSaveInstanceState

TODO 待完成 主要差异在于ViewModel没有数据大小限制,而onSaveInstanceState是基于Bundle实现的,Bundle又有内存映射时的限制所以无法保留大数据

总结

TODO 待完成

参考链接

How Android ViewModel works under the hood to survive to configuration change