请点赞,你的点赞对我含义严重,满意下我的虚荣心。
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"
// 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。重视我,带你了解更多。
参考资料
- ViewModel 概览 —— 官方文档
- 保存界面状况 —— 官方文档
- ViewModel 的 SavedState 模块 —— 官方文档
- ViewModel 和 LiveData:为设计形式打 Call 还是唱反调? —— 官方博文
你的点赞对我含义严重!微信查找大众号 [彭旭锐],期望大家能够一同讨论技术,找到情投意合的朋友,咱们下次见!
我正在参与技术社区创作者签约方案招募活动,点击链接报名投稿。