为什么 Activity 都重建了 ViewModel 还存在?—— Jetpack 系列

请点赞,你的点赞对我意义重大,满足下我的虚荣心。

🔥 Hi,我是小彭。本文已收录到 GitHub · Android-NoteBook 中。这里有 Android 进阶成长知识体系,有志同道合的朋友,欢迎跟我一起成长。

前言

ViewModel 是 Jetpack 组件中较常用的组件之一,也是实现 MVVM 模式或 MVI 模式的标准组件之一。在这篇文章里,我将与你讨论 ViewModel 实用和面试常见的知识点。如果能帮上忙请务必点赞加关注,这对我非常重要。


这篇文章是 Jetpack 系列文章第 3 篇,专栏文章列表:

一、架构组件:

二、其他:

  • 1、AppStartup:轻量级初始化框架
  • 2、DataStore:新一代键值对存储方案
  • 3、Room:ORM 数据库访问框架
  • 4、WindowManager:加强对多窗口模式的支持
  • 5、WorkManager:加强对后台任务的支持
  • 6、Compose:新一代视图开发方案

1. 认识 ViewModel

1.1 为什么要使用 ViewModel?

ViewModel 的作用可以区分 2 个维度来理解:

  • 1、界面控制器维度: 在最初的 MVC 模式中,Activity / Fragment 中承担的职责过重,因此,在后续的 UI 开发模式中,我们选择将 Activity / Fragment 中与视图无关的职责抽离出来,在 MVP 模式中叫作 Presenter,在 MVVM 模式中叫作 ViewModel。因此,我们使用 ViewModel 来承担界面控制器的职责,并且配合 LiveData / Flow 实现数据驱动。
  • 2、数据维度: 由于 Activity 存在因配置变更销毁重建的机制,会造成 Activity 中的所有瞬态数据丢失,例如网络请求得到的用户信息、视频播放信息或者异步任务都会丢失。而 ViewModel 能够应对 Activity 因配置变更而重建的场景,在重建的过程中恢复 ViewModel 数据,从而降低用户体验受损。

关于 MVVM 等模式的更多内容,我们在 5、从 MVC 到 MVP、MVVM、MVI:Android UI 架构演进 这篇文章讨论过。

MVVM 模式示意图:

MVI 模式示意图:

ViewModel 生命周期示意图:

1.2 ViewModel 的使用方法

  • 1、添加依赖: 在 build.gradle 中添加 ViewModel 依赖,需要注意区分过时的方式:
// 过时方式(lifecycle-extensions 不再维护)
implementation "androidx.lifecycle:lifecycle-extensions:2.4.0"

// 目前的方式:
def lifecycle_version = "2.5.0"
// Lifecycle 核心类
implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
  • 2、模板代码: ViewModel 通常会搭配 LiveData 使用,以下为使用模板,相信大家都很熟悉了:

NameViewModel.kt

class NameViewModel : ViewModel() {
    val currentName: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }
}

MainActivity.kt

class MainActivity : AppCompatActivity() {

    private val model: NameViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // LiveData 观察者
        val nameObserver = Observer<String> { newName ->
            // 更新视图
            nameTextView.text = newName
        }

        // 注册 LiveData 观察者,this 为生命周期宿主
        model.currentName.observe(this, nameObserver)

        // 修改 LiveData 数据
        button.setOnClickListener {
            val anotherName = "John Doe"
            model.currentName.value = anotherName
        }
    }
}

1.3 ViewModel 的创建方式

创建 ViewModel 实例的方式主要有 3 种,它们最终都是通过第 1 种 ViewModelProvider 完成的:

  • 方法 1: ViewModelProvider 是创建 ViewModel 的工具类:

示例程序

// 不带工厂的创建方式
val vm = ViewModelProvider(this).get(MainViewModel::class.java)
// 带工厂的创建方式
val vmWithFactory = ViewModelProvider(this, MainViewModelFactory()).get(MainViewModel::class.java)

// ViewModel 工厂
class MainViewModelFactory(

) : ViewModelProvider.Factory {

    private val repository = MainRepository()

    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return MainViewModel(repository) as T
    }
}
  • 方法 2: 使用 Kotlin by 委托属性,本质上是间接使用了 ViewModelProvider:

示例程序

// 在 Activity 中使用
class MainActivity : AppCompatActivity() {
    // 使用 Activity 的作用域
    private val viewModel : MainViewModel by viewModels()
}

// 在 Fragment 中使用
class MainFragment : Fragment() {
    // 使用 Activity 的作用域,与 MainActivity 使用同一个对象
    val activityViewModel : MainViewModel by activityViewModels()
    // 使用 Fragment 的作用域
    val viewModel : MainViewModel by viewModels()
}
  • 方法 3: Hilt 提供了注入部分 Jetpack 架构组件的支持

示例程序

@HiltAndroidApp
class DemoApplication : Application() { ... }

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

@AndroidEntryPoint
class MainHiltActivity : AppCompatActivity(){
    val viewModel by viewModels<MainViewModel>()
    ...
}

依赖项

// Hilt ViewModel 支持
implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0"
// Hilt 注解处理器
kapt "androidx.hilt:hilt-compiler:1.0.0"

需要注意的是,虽然可以使用依赖注入普通对象的方式注入 ViewModel,但是这相当于绕过了 ViewModelProvider 来创建 ViewModel。这意味着 ViewModel 实例一定不会存放在 ViewModelStore 中,将失去 ViewModel 恢复界面数据的特性。

错误示例

@AndroidEntryPoint
class MainHiltActivity : AppCompatActivity(){
    @Inject
    lateinit var viewModel : MainViewModel
}

2. ViewModel 实现原理分析

2.1 ViewModel 的创建过程

上一节提到,3 种创建 ViewModel 实例的方法最终都是通过 ViewModelProvider 完成的。ViewModelProvider 可以理解为创建 ViewModel 的工具类,它需要 2 个参数:

  • 参数 1 ViewModelStoreOwner: 它对应于 Activity / Fragment 等持有 ViewModel 的宿主,它们内部通过 ViewModelStore 维持一个 ViewModel 的映射表,ViewModelStore 是实现 ViewModel 作用域和数据恢复的关键;
  • 参数 2 Factory: 它对应于 ViewModel 的创建工厂,缺省时将使用默认的 NewInstanceFactory 工厂来反射创建 ViewModel 实例。

创建 ViewModelProvider 工具类后,你将通过 get() 方法来创建 ViewModel 的实例。get() 方法内部首先会通过 ViewModel 的全限定类名从映射表(ViewModelStore)中取缓存,未命中才会通过 ViewModel 工厂创建实例再缓存到映射表中。

正因为同一个 ViewModel 宿主使用的是同一个 ViewModelStore 映射表,因此在同一个宿主上重复调用 ViewModelProvider#get() 返回同一个 ViewModel 实例。

ViewModelProvider.java

// ViewModel 创建工厂
private final Factory mFactory;
// ViewModel 存储容器
private final ViewModelStore mViewModelStore;

// 默认使用 NewInstanceFactory 反射创建 ViewModel
public ViewModelProvider(ViewModelStoreOwner owner) {
    this(owner.getViewModelStore(), ... NewInstanceFactory.getInstance());
}

// 自定义 ViewModel 创建工厂
public ViewModelProvider(ViewModelStoreOwner owner, Factory factory) {
    this(owner.getViewModelStore(), factory);
}

// 记录宿主的 ViewModelStore 和 ViewModel 工厂
public ViewModelProvider(ViewModelStore store, Factory factory) {
    mFactory = factory;
    mViewModelStore = store;
}

@NonNull
@MainThread
public <T extends ViewModel> T get(Class<T> modelClass) {
    String canonicalName = modelClass.getCanonicalName();
    if (canonicalName == null) {
        throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
    }
        // 使用类名作为缓存的 KEY
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

// Fragment
@NonNull
@MainThread
public <T extends ViewModel> T get(String key, Class<T> modelClass) {
    // 1. 先从 ViewModelStore 中取缓存
    ViewModel viewModel = mViewModelStore.get(key);
    if (modelClass.isInstance(viewModel)) {
        return (T) viewModel;
    }
    // 2. 使用 ViewModel 工厂创建实例
    viewModel = mFactory.create(modelClass);
    ...
    // 3. 存储到 ViewModelStore
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

// 默认的 ViewModel 工厂
public static class NewInstanceFactory implements Factory {

    private static NewInstanceFactory sInstance;

    @NonNull
    static NewInstanceFactory getInstance() {
        if (sInstance == null) {
            sInstance = new NewInstanceFactory();
        }
        return sInstance;
    }

    @NonNull
    @Override
    public <T extends ViewModel> T create(Class<T> modelClass) {
        // 反射创建 ViewModel 对象
        return modelClass.newInstance();
    }
}

ViewModelStore.java

// ViewModel 本质上就是一个映射表而已
public class ViewModelStore {
    // <String - ViewModel> 哈希表
    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }

    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

ViewModel 宿主是 ViewModelStoreOwner 接口的实现类,例如 Activity:

ViewModelStoreOwner.java

public interface ViewModelStoreOwner {
    @NonNull
    ViewModelStore getViewModelStore();
}

androidx.activity.ComponentActivity.java

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
    ContextAware,
    LifecycleOwner,
    ViewModelStoreOwner ... {
        
    // ViewModel 的存储容器
    private ViewModelStore mViewModelStore;
    // ViewModel 的创建工厂
    private ViewModelProvider.Factory mDefaultFactory;

    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        if (mViewModelStore == null) {
            // 已简化,后文补全
            mViewModelStore = new ViewModelStore();
        }
        return mViewModelStore;
    }
}

2.2 by viewModels() 实现原理分析

by 关键字是 Kotlin 的委托属性,内部也是通过 ViewModelProvider 来创建 ViewModel。关于 Kotlin 委托属性的更多内容,我们在 Kotlin | 委托机制 & 原理 & 应用 这篇文章讨论过,这里不重复。

ActivityViewModelLazy.kt

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

    return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}

ViewModelLazy.kt

public class ViewModelLazy<VM : ViewModel> (
    private val viewModelClass: KClass<VM>,
    private val storeProducer: () -> ViewModelStore,
    private val factoryProducer: () -> ViewModelProvider.Factory
) : 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 创建 ViewModel 实例
                ViewModelProvider(store, factory).get(viewModelClass.java).also {
                    cached = it
                }
            } else {
                viewModel
            }
        }

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

2.3 ViewModel 如何实现不同的作用域

ViewModel 内部会为不同的 ViewModel 宿主分配不同的 ViewModelStore 映射表,不同宿主是从不同的数据源来获取 ViewModel 的实例,因而得以区分作用域。

具体来说,在使用 ViewModelProvider 时,我们需要传入一个 ViewModelStoreOwner 宿主接口,它将在 getViewModelStore() 接口方法中返回一个 ViewModelStore 实例。

  • 对于 Activity 来说,ViewModelStore 实例是直接存储在 Activity 的成员变量中的;
  • 对于 Fragment 来说,ViewModelStore 实例是间接存储在 FragmentManagerViewModel 中的 <Fragment - ViewModelStore> 映射表中的。

这样就实现了不同的 Activity 或 Fragment 分别对应不同的 ViewModelStore 实例,进而区分不同作用域。

androidx.activity.ComponentActivity.java

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
    ContextAware,
    LifecycleOwner,
    ViewModelStoreOwner ... {

        @NonNull
        @Override
        public ViewModelStore getViewModelStore() {
            if (mViewModelStore == null) {
                // 已简化,后文补全
                mViewModelStore = new ViewModelStore();
            }
            return mViewModelStore;
        }
}

Fragment.java

@NonNull
@Override
public ViewModelStore getViewModelStore() {
    // 最终调用 FragmentManagerViewModel#getViewModelStore(Fragment)
    return mFragmentManager.getViewModelStore(this);
}

FragmentManagerViewModel.java

// <Fragment - ViewModelStore> 映射表
private final HashMap<String, ViewModelStore> mViewModelStores = new HashMap<>();

@NonNull
ViewModelStore getViewModelStore(@NonNull Fragment f) {
    ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
    if (viewModelStore == null) {
        viewModelStore = new ViewModelStore();
        mViewModelStores.put(f.mWho, viewModelStore);
    }
    return viewModelStore;
}

2.4 为什么 Activity 在屏幕旋转重建后可以恢复 ViewModel?

ViewModel 底层是基于原生 Activity 因设备配置变更重建时恢复数据的机制实现的,这个其实跟 Fragment#setRetainInstance(true) 持久 Fragment 的机制是相同的。当 Activity 因配置变更而重建时,我们可以将页面上的数据或状态可以定义为 2 类:

  • 第 1 类 - 配置数据: 例如窗口大小、多语言字符、多主题资源等,当设备配置变更时,需要根据最新的配置重新读取新的数据,因此这部分数据在配置变更后便失去意义,自然也就没有存在的价值;
  • 第 2 类 - 非配置数据: 例如用户信息、视频播放信息、异步任务等非配置相关数据,这些数据跟设备配置没有一点关系,如果在重建 Activity 的过程中丢失,不仅没有必要,而且会损失用户体验(无法快速恢复页面数据,或者丢失页面进度)。

基于以上考虑,Activity 是支持在设备配置变更重建时恢复 第 2 类 - 非配置数据 的,源码中存在 NonConfiguration 字眼的代码,就是与这个机制相关的代码。我将整个过程大概可以概括为 3 个阶段:

  • 阶段 1: 系统在处理 Activity 因配置变更而重建时,会先调用 retainNonConfigurationInstances 获取旧 Activity 中的数据,其中包含 ViewModelStore 实例,而这一份数据会临时存储在当前 Activity 的 ActivityClientRecord(属于当前进程,下文说明);
  • 阶段 2: 在新 Activity 重建后,系统通过在 Activity#onAttach(…) 中将这一份数据传递到新的 Activity 中;
  • 阶段 3: Activity 在构造 ViewModelStore 时,会优先从旧 Activity 传递过来的这份数据中获取,为空才会创建新的 ViewModelStore。

对于 ViewModel 来说,相当于旧 Activity 中所有的 ViewModel 映射表被透明地传递到重建后新的 Activity 中,这就实现了恢复 ViewModel 的功能。总结一下重建前后的实例变化,帮助你理解:

  • Activity: 构造新的实例;
  • ViewModelStore: 保留旧的实例;
  • ViewModel: 保留旧的实例(因为 ViewModel 存储在 ViewModelStore 映射表中);
  • LiveData: 保留旧的实例(因为 LiveData 是 ViewModel 的成员变量);

现在,我们逐一分析这 3 个阶段的源码执行过程:

阶段 1 源码分析:

Activity.java

// 阶段 1:获取 Activity 的非配置相关数据
NonConfigurationInstances retainNonConfigurationInstances() {
    // 1.1 构造 Activity 级别的非配置数据
    Object activity = onRetainNonConfigurationInstance();
    // 1.2 构造 Fragment 级别的费配置数据数据
    FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

    ...

    // 1.3 构造并返回 NonConfigurationInstances 非配置相关数据类
    NonConfigurationInstances nci = new NonConfigurationInstances();
        
    nci.activity = activity;
    nci.fragments = fragments;
        ...
    return nci;
}

// 1.1 默认返回 null,由 Activity 子类定义
public Object onRetainNonConfigurationInstance() {
    return null;
}

androidx.activity.ComponentActivity.java

private ViewModelStore mViewModelStore;

// 1.1 ComponentActivity 在 onRetainNonConfigurationInstance() 中写入了 ViewModelStore
@Override
@Nullable
public final Object onRetainNonConfigurationInstance() {
    ViewModelStore viewModelStore = mViewModelStore;
    // 这一个 if 语句是处理异常边界情况:
    // 如果重建的 Activity 没有调用 getViewModelStore(),那么旧的 Activity 中的 ViewModel 并没有被取出来,
    // 因此在准备再一次存储当前 Activity 时,需要检查一下旧 Activity 传过来的数据。
    if (viewModelStore == null) {
        NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            viewModelStore = nc.viewModelStore;
        }
    }
    // ViewModelStore 为空说明当前 Activity 和旧 Activity 都没有 ViewModel,没必要存储和恢复
    if (viewModelStore == null) {
        return null;
    }
    NonConfigurationInstances nci = new NonConfigurationInstances();
    // 保存 ViewModelStore 对象
    nci.viewModelStore = viewModelStore;
    return nci;
}

ActivityThread.java

// Framework 调用 retainNonConfigurationInstances() 获取非配置数据后,
// 会通过当前进程内存临时存储这一份数据,这部分源码我们暂且放到一边。

阶段 2 源码分析:

Activity.java

// 阶段 2:在 Activity#attach() 中传递旧 Activity 的数据
NonConfigurationInstances mLastNonConfigurationInstances;

final void attach(Context context, ActivityThread aThread,
    ...
    NonConfigurationInstances lastNonConfigurationInstances) {
    ...
    mLastNonConfigurationInstances = lastNonConfigurationInstances;
    ...
}

至此,旧 Activity 的数据就传递到新 Activity 的成员变量 mLastNonConfigurationInstances 中。

阶段 3 源码分析:

androidx.activity.ComponentActivity.java

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
    ContextAware,
    LifecycleOwner,
    ViewModelStoreOwner ... {
        
    private ViewModelStore mViewModelStore;
    private ViewModelProvider.Factory mDefaultFactory;

    // 阶段 3:Activity 的 ViewModelStore 优先使用旧 Activity 传递过来的 ViewModelStore
    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        if (mViewModelStore == null) {
            // 3.1 优先使用旧 Activity 传递过来的 ViewModelStore
            NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                mViewModelStore = nc.viewModelStore;
            }
            // 3.2 否则创建新的 ViewModelStore
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }
}

Activity.java

// 这个变量在阶段 2 赋值
NonConfigurationInstances mLastNonConfigurationInstances;

// 返回从 attach() 中传递过来的旧 Activity 数据
public Object getLastNonConfigurationInstance() {
    return mLastNonConfigurationInstances != null ? mLastNonConfigurationInstances.activity : null;
}

至此,就完成 ViewModel 数据恢复了。


现在,我们回过头来分析下 ActivityThread 这一部分源码:

ActivityThread 中的调用过程:

在 Activity 因配置变更而重建时,系统将执行 Relaunch 重建过程。系统在这个过程中通过同一个 ActivityClientRecord 来完成信息传递,会销毁当前 Activity,紧接着再马上重建同一个 Activity。

  • 阶段 1: 在处理 Destroy 逻辑时,调用 Activity#retainNonConfigurationInstances() 方法获取旧 Activity 中的非配置数据,并临时保存在 ActivityClientRecord 中;
  • 阶段 2: 在处理 Launch 逻辑时,调用 Activity#attach(…) 将 ActivityClientRecord 中临时保存的非配置数据传递到新 Activity 中。

ActivityThread.java

private void handleRelaunchActivityInner(ActivityClientRecord r, ...) {
    final Intent customIntent = r.activity.mIntent;
    // 处理 onPause()
    performPauseActivity(r, false, reason, null /* pendingActions */);
    // 处理 onStop()
    callActivityOnStop(r, true /* saveState */, reason);
    // 阶段 1:获取 Activity 的非配置相关数据
    handleDestroyActivity(r.token, false, configChanges, true, reason);

    // 至此,Activity 中的 第 2 类 - 非配置数据就记录在 ActivityClientRecord 中,
    // 并通过同一个 ActivityClientRecord 重建一个新的 Activity

    // 阶段 2:在 Activity#attach() 中传递旧 Activity 的数据
    handleLaunchActivity(r, pendingActions, customIntent);

    // 至此,旧 Activity 中的非配置数据已传递到新 Activity
}

public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) {
    ActivityClientRecord r = performDestroyActivity(token, finishing, configChanges, getNonConfigInstance, reason);
    ...
    if (finishing) {
        ActivityTaskManager.getService().activityDestroyed(token);
    }
}

// 阶段 1:获取 Activity 的非配置相关数据
// 参数 finishing 为 false
// 参数 getNonConfigInstance 为 true
ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) {
    ActivityClientRecord r = mActivities.get(token);
    // 保存非配置数据,调用了阶段 1 中提到的 retainNonConfigurationInstances() 方法
    if (getNonConfigInstance) {
        r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
    }
    // 执行 onDestroy()
    mInstrumentation.callActivityOnDestroy(r.activity);
    return r;
}

public Activity handleLaunchActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent) {
    final Activity a = performLaunchActivity(r, customIntent);
}

// 阶段 2:在 Activity#attach() 中传递旧 Activity 的数据
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    // 创建新的 Activity 实例
    Activity activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
    // 创建或获取 Application 实例,在这个场景里是获取
    Application app = r.packageInfo.makeApplication(false, mInstrumentation);
    // 传递 lastNonConfigurationInstances 数据
    activity.attach(appContext, ..., r.lastNonConfigurationInstances,...);
    // 清空临时变量
    r.lastNonConfigurationInstances = null;
    ...
}

2.5 ViewModel 的数据在什么时候才会清除

ViewModel 的数据会在 Activity 非配置变更触发的销毁时清除,具体分为 3 种情况:

  • 第 1 种: 直接调用 Activity#finish() 或返回键等间接方式;
  • 第 2 种: 异常退出 Activity,例如内存不足;
  • 第 3 种: 强制退出应用。

第 3 种没有给予系统或应用存储数据的时机,内存中的数据自然都会被清除。而前 2 种情况都属于非配置变更触发的,在 Activity 中存在 1 个 Lifecycle 监听:当 Activity 进入 DESTROYED 状态时,如果 Activity 不处于配置变更重建的阶段,将调用 ViewModelStore#clear() 清除 ViewModel 数据。

androidx.activity.ComponentActivity.java

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
    ContextAware,
    LifecycleOwner,
    ViewModelStoreOwner ... {
        
    private ViewModelStore mViewModelStore;
    private ViewModelProvider.Factory mDefaultFactory;

    public ComponentActivity() {
        // DESTROYED 状态监听
        getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    mContextAwareHelper.clearAvailableContext();
                    // 是否处于配置变更引起的重建
                    if (!isChangingConfigurations()) {
                        getViewModelStore().clear();
                    }
                }
            }
        });
    }
}

Activity.java

boolean mChangingConfigurations = false;

public boolean isChangingConfigurations() {
    return mChangingConfigurations;
}

3. ViewModel 的内存泄漏问题

ViewModel 的内存泄漏是指 Activity 已经销毁,但是 ViewModel 却被其他组件引用。这往往是因为数据层是通过回调监听器的方式返回数据,并且数据层是单例对象或者属于全局生命周期,所以导致 Activity 销毁了,但是数据层依然间接持有 ViewModel 的引用。

如果 ViewModel 是轻量级的或者可以保证数据层操作快速完成,这个泄漏影响不大可以忽略。但如果数据层操作并不能快速完成,或者 ViewModel 存储了重量级数据,就有必要采取措施。例如:

  • 方法 1: 在 ViewModel#onCleared() 中通知数据层丢弃对 ViewModel 回调监听器的引用;
  • 方法 2: 在数据层使用对 ViewModel 回调监听器的弱引用(这要求 ViewModel 必须持有回调监听器的强引用,而不能使用匿名内部类,这会带来编码复杂性);
  • 方法 3: 使用 EventBus 代替回调监听器(这会带来编码复杂性);
  • 方法 4: 使用 LiveData 的 Transformations.switchMap() API 包装数据层的请求方法,这相当于在 ViewModel 和数据层中间使用 LiveData 进行通信。例如:

MyViewModel.java

// 用户 ID LiveData
MutableLiveData userIdLiveData = new MutableLiveData<String>(); 

// 用户数据 LiveData
LiveData userLiveData = Transformations.switchMap(userIdLiveData, id -> 
    // 调用数据层 API
    repository.getUserById(id));

// 设置用户 ID
// 每次的 userIdLiveData 的值发生变化,repository.getUserById(id) 将被调用,并将结果设置到 userLiveData 上
public void setUserId(String userId) { 
    this.userIdLiveData.setValue(userId); 
}

4. ViewModel 和 onSaveInstanceState() 的对比

ViewModel 和 onSaveInstanceState() 都是对数据的恢复机制,但由于它们针对的场景不同,导致它们的实现原理不同,进而优缺点也不同。

  • 1、ViewModel: 使用场景针对于配置变更重建中非配置数据的恢复,由于内存是可以满足这种存储需求的,因此可以选择内存存储。又由于内存空间相对较大,因此可以存储大数据,但会受到内存空间限制;
  • 2、onSaveInstanceState() :使用场景针对于应用被系统回收后重建时对数据的恢复,由于应用进程在这个过程中会消亡,因此不能选择内存存储而只能选择使用持久化存储。又由于这部分数据需要通过 Bundle 机制在应用进程和 AMS 服务之间传递,因此会受到 Binder 事务缓冲区大小限制,只可以存储小规模数据。

如果是正常的 Activity 退出,例如返回键或者 finish(),都不属于 ViewModel 和 onSaveInstanceState() 的应用场景,因此都不会存储和恢复数据。


5. 总结

到这里,Jetpack 中的 ViewModel 组件就讲完了。下一篇文章,我们来讨论 LiveData 的替代方案 Flow。关注我,带你了解更多。


参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342