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

Hi,我是小彭。本文已收录到 GitHub · Android-NoteBook 中。这里有 Android 进阶成长常识体系,有气味相投的敌人,关注公众号 [彭旭锐] 跟我一起成长。

前言

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


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

一、架构组件:

  • 1、Lifecycle:生命周期感知型组件的根底
  • 2、LiveData:生命周期感知型数据容器
  • 3、ViewModel:数据驱动型界面控制器(本文)
  • 4、Flow:LiveData 的代替计划
  • 5、从 MVC 到 MVP、MVVM、MVI:Android UI 架构演进
  • 6、ViewBinding:新一代视图绑定计划
  • 7、Fragment:模块化的微型 Activity
  • 8、RecyclerView:可复用型列表视图
  • 9、Navigation:单 Activity 多 Fragment 的导航计划
  • 10、Dagger2:从 Dagger2 到 Hilt 玩转依赖注入(一)
  • 11、Hilt:从 Dagger2 到 Hilt 玩转依赖注入(二)
  • 12、OnBackPressedDispatcher:解决回退事件的新姿态

二、其余:

  • 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"// LiveDataimplementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"// ViewModelimplementation "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 架构组件的反对

示例程序

@HiltAndroidAppclass DemoApplication : Application() { ... }@HiltViewModelclass MainViewModel @Inject constructor() : ViewModel() {    ...}@AndroidEntryPointclass 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 复原界面数据的个性。

谬误示例

@AndroidEntryPointclass 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 反射创立 ViewModelpublic 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@MainThreadpublic <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@MainThreadpublic <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

@MainThreadpublic 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@Overridepublic ViewModelStore getViewModelStore() {    // 最终调用 FragmentManagerViewModel#getViewModelStore(Fragment)    return mFragmentManager.getViewModelStore(this);}

FragmentManagerViewModel.java

// <Fragment - ViewModelStore> 映射表private final HashMap<String, ViewModelStore> mViewModelStores = new HashMap<>();@NonNullViewModelStore 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@Nullablepublic 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 为 trueActivityClientRecord 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 LiveDataMutableLiveData userIdLiveData = new MutableLiveData<String>(); // 用户数据 LiveDataLiveData 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。关注我,带你理解更多。


参考资料

  • ViewModel 概览 —— 官网文档
  • 保留界面状态 —— 官网文档
  • ViewModel 的 SavedState 模块 —— 官网文档
  • ViewModel 和 LiveData:为设计模式打 Call 还是唱反调? —— 官网博文
你的点赞对我意义重大!微信搜寻公众号 [彭旭锐],心愿大家能够一起探讨技术,找到气味相投的敌人,咱们下次见!