实用 AI

可在线运行 AI 集合,涵盖 AI 文案生成、写作辅助、AI 绘图与照片修复、AI 配音、字幕生成、语音转录以及 AI 视频创作和数字人等多种 AI 服务

查看详情

深入理解Android ViewModel

Android面试技术要点汇总
2021-05-17 14:29 · 阅读时长13分钟
小课

ViewModel是Android构架组件(Android Architecture Components)的一部分,我们从ViewModel的创建开始,一步步探索ViewModel的内部实现原理,以及为何它在configuration改变(常见的是横竖屏切换的场景)时,即使Activity已经重建,它仍然会保留。

ViewModel设计的宗旨就是根据生命周期来存储和管理界面相关的数据,减少开发维护成本,它是目前流的Android应用架构模式MVVM(Model-View-ViewModel)重要的组成部分,使用ViewModel的优势包括以下几个方面:

  • 自动处理configuration改变,即使activity重建了,但是ViewModel仍然保留。
  • 生命周期感应,ViewModel能够感应组件的生命周期,在生命周期彻底结束时处理数据销毁。
  • 在 Fragment 之间共享数据,ViewModel可以为同一个Activity下的多个Fragment之间共享数据。
  • 支持kotlin协程,可以很方便的集成协程,处理异步任务。
ViewModel内部是如何实现?

下面通过一个示例项目来解释,ViewModel在configuration改变时是如何被保留的。示例项目大概的功能是这样的,在MainActivity中展示一个计数和当前Activity的哈希值,这个计数通过LiveData保存在ViewModel中。当configuration改变时,当前的Activity实例将会销毁,通过它的哈希值我们能够看出来,但是保存在ViewModel中的计数值不会改变,这就说明ViewModel并没有销毁重新创建。

文件大小修改时间
app
2022年02月11日
build.gradle
662 B 2022年02月11日
gradle/wrapper
2022年02月11日
gradle.properties
1 kB 2022年02月11日
gradlew
5 kB 2022年02月11日
gradlew.bat
2 kB 2022年02月11日
local.properties
345 B 2022年02月11日
README.md
65 B 2022年02月11日
settings.gradle
47 B 2022年02月11日

为什么Activity实例都销毁了,ViewModel实例却还保留?ViewModel创建之后保存在哪里了?在Activity中,创建一个ViewModelProvider实例,然后调用它的get方法就能得到相应的ViewModel实例。

viewModel = ViewModelProvider(this, ViewModelFactory()).get(CounterViewModel::class.java)

下面看看ViewModelProvider的构造方法,有两个参数,一个是ViewModelStoreOwner,用于提供ViewModelStore,我们的MainActivity的父类ComponentActivity实现了ViewModelStoreOwner接口,第二个是Factory,这个参数主要是用来自定义创建ViewModel1,最终会调用它另外一个重载的构造方法,把ViewModelStore和Factory保存下来。

1public interface ViewModelStoreOwner {
2    ViewModelStore getViewModelStore();
3}
4
5public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
6    this(owner.getViewModelStore(), factory);
7}
8
9public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
10    mFactory = factory;
11    mViewModelStore = store;
12}

接下来看看ViewModelProvider的get方法,通过get方法,我们才能获取到ViewModel实例。

1public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
2    String canonicalName = modelClass.getCanonicalName();
3    if (canonicalName == null) {
4        throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
5    }
6    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
7}
8
9public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
10    ViewModel viewModel = mViewModelStore.get(key);
11    if (modelClass.isInstance(viewModel)) {
12        if (mFactory instanceof OnRequeryFactory) {
13            ((OnRequeryFactory) mFactory).onRequery(viewModel);
14        }
15        return (T) viewModel;
16    } else {
17        //noinspection StatementWithEmptyBody
18        if (viewModel != null) {
19            // TODO: log a warning.
20        }
21    }
22    if (mFactory instanceof KeyedFactory) {
23        viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
24    } else {
25        viewModel = (mFactory).create(modelClass);
26    }
27    mViewModelStore.put(key, viewModel);
28    return (T) viewModel;
29}

首先它会将ViewModel的类名和DEFAULT_KEY组合为key,然后调用重载的get方法,检查当前key在mViewModelStore2中是否存在,如果存在则直接返回,如果不存在,则调用Factory的create方法来创建ViewModel,然后保存在mViewModelStore中。

  1. 我们这里其实可以不传Factory,因为我们并不需要自定义创建ViewModel,不传的话会生成默认的Factory。
  2. ViewModelStore内部存在一个Map,根据key来存储ViewModel。
注释

那么问题是ViewModelStore明明是Activity提供的,为什么Activity实例都销毁重建了,我们还能拿到之前创建的ViewModel呢?下面看看ComponentActivity中是如何实现ViewModelStoreOwner接口的。

1public class ComponentActivity extends androidx.core.app.ComponentActivity implements
2        LifecycleOwner,
3        ViewModelStoreOwner,
4        SavedStateRegistryOwner,
5        OnBackPressedDispatcherOwner {
6    ...
7    static final class NonConfigurationInstances {
8        Object custom;
9        ViewModelStore viewModelStore;
10    }
11
12    @Override
13    public ViewModelStore getViewModelStore() {
14        if (getApplication() == null) {
15            throw new IllegalStateException("Your activity is not yet attached to the "
16                    + "Application instance. You can't request ViewModel before onCreate call.");
17        }
18        if (mViewModelStore == null) {
19            NonConfigurationInstances nc =
20                    (NonConfigurationInstances) getLastNonConfigurationInstance();
21            if (nc != null) {
22                // Restore the ViewModelStore from NonConfigurationInstances
23                mViewModelStore = nc.viewModelStore;
24            }
25            if (mViewModelStore == null) {
26                mViewModelStore = new ViewModelStore();
27            }
28        }
29        return mViewModelStore;
30    }
31    ...
32}

从上面可以看出来,在Activity重新创建后,调用getViewModelStore方法时,会首先检查NonConfigurationInstances是否为空,不为空则从NonConfigurationInstances拿到ViewModelStore,而这个ViewModelStore就是Activity重建之前保存进去的。

NonConfigurationInstances中为什么会有之前Activity的ViewModelStore呢?

在Activity销毁时,会调用AcitivityThread.performDestroyActivity方法,在这个方法中会调用Activity的retainNonConfigurationInstances方法来获取Activity.NonConfigurationInstances实例,并保存在ActivityClientRecord中。

public final class ActivityThread extends ClientTransactionHandler {
    ...
    ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
            int configChanges, boolean getNonConfigInstance, String reason) {
        ActivityClientRecord r = mActivities.get(token);
        ...
        r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
    }
    ...
}

在Activity.retainNonConfigurationInstances方法中会调用ComponentActivity.onRetainNonConfigurationInstance来获取ComponentActivity.NonConfigurationInstances实例。

1public class Activity extends ... {
2    ...
3    NonConfigurationInstances retainNonConfigurationInstances() {
4        Object activity = onRetainNonConfigurationInstance();
5        ...
6        NonConfigurationInstances nci = new NonConfigurationInstances();
7        nci.activity = activity;
8        ...
9        return nci;
10    }
11    ...
12}

在ComponentActivity.onRetainNonConfigurationInstance方法中,会保存把当前的ViewModelStore保存下来。

1public class ComponentActivity extends ... {
2    ...
3    public final Object onRetainNonConfigurationInstance() {
4        Object custom = onRetainCustomNonConfigurationInstance();
5        ViewModelStore viewModelStore = mViewModelStore;
6        if (viewModelStore == null) {
7            NonConfigurationInstances nc =
8                    (NonConfigurationInstances) getLastNonConfigurationInstance();
9            if (nc != null) {
10                viewModelStore = nc.viewModelStore;
11            }
12        }
13        if (viewModelStore == null && custom == null) {
14            return null;
15        }
16        NonConfigurationInstances nci = new NonConfigurationInstances();
17        nci.custom = custom;
18        nci.viewModelStore = viewModelStore;
19        return nci;
20    }
21    ...
22}

最后,在重新创建Activity时,会调用AcitivityThread.performLaunchActivity再把ActivityClientRecord作为参数传回来,这样新的Activity中就有了之前Activity的NonConfigurationInstances实例了。

注:Activity和ComponentActivity内部都定义来一个名为NonConfigurationInstances的静态内部类。

注释
ViewModelandroid