由浅入深,详解ViewModel那些事

news2025/1/13 10:04:07

在这里插入图片描述

前言:今年的龙舟雨来了,一场接一场,雨量很大。

前言

  以往如果需要在 Activity 或者 Fragment 中保存数据状态则需要重写onSaveInstanceState ,使用bundle去存储相应的数据和状态,但是这也只能保存轻量简单的序列化数据。而 ViewModel 可以做到在配置变更后依然持有状态。

现在的 MVVM 架构模式中,将 View数据与逻辑放在 ViewModel 中(即 MVP 中的Presenter),而 ViewModel 是不需要持有UI层引用的(因为 ViewModel 的生命周期更长),然后对外暴露观察者,一般是搭配 LiveData 一起使用,以此更容易的保持状态同步。那么就避免了可能的内存泄漏,同时实现了解耦。

一、什么是ViewModel

ViewModel 作为 JetPack 核心组件,它的实现远没有我们想象的那么简单。
ViewModel 类是一种业务逻辑或屏幕级状态容器。简单理解就是为UI层提供数据,以及封装相关的业务逻辑

它的主要优点是,它可以缓存状态,并可在配置更改后持久保留相应状态。这意味着在 Activity 之间导航时或进行配置更改后(例如旋转屏幕时),界面将无需重新请求数据。

  • 具备宿主生命周期感知能力的数据存储组件(它只能感知宿主被销毁的事件);
  • ViewModel 保存的数据,在页面因配置变更导致页面销毁重建之后依然存在的。

配置变更

配置变更是指应用内的配置参数变更从而触发的 Activity 重新创建

常见的配置变更场景:横竖屏切换、分辨率调整、权限变更、系统字体样式变更等。

生命周期

关于 ViewModel 的生命周期,具体如下图所示:

在这里插入图片描述

ViewModel 最重要的特点是 生命周期长于Activity。它只能感知宿主被销毁的事件(onDestory),可以复写 onCleared() 来做一些清理和释放的工作。(Fragment的生命周期也是同理)

这是一个Jetpack组件 + MVVM架构模式的开源实战项目,加入 组件化模块化协程Flow短视频打造一个大型的 Android 项目架构。项目地址:https://github.com/suming77/SumTea_Android

二、ViewModel的用法

先来了解一下如何使用 ViewModel。

1.常规用法

ViewModel 存储的数据,仅仅只能当页面因为配置变更导致的销毁再重建时复用。复用的是 ViewModel 的实例对象整体。

ViewModel 层请求数据,处理业务逻辑:

class MainViewModel : ViewModel() {
    val userLiveData = MutableLiveData<String>()

    fun getUserInfo() {
        //为了适配因配置变更而导致的页面重建,重复利用之前的数据,加快新页面渲染,不在请求接口
        //如果value不为空,说明这个ViewModel肯定是复用的,因为新建的ViewModel的liveData是不会有数据的
        if (userLiveData.value == null) {
            // 模拟请求接口返回数据
            viewModelScope.launch {
                delay(1000)
                userLiveData.postValue("苏火火 苏火火 苏火火 苏火火 苏火火")
            }
        }
    }
}

UI层处理显示逻辑:

class MainActivity : BaseDataBindActivity<ActivityViewmodelBinding>() {

    override fun onCreate(savedInstanceState: Bundle?) {
        LogUtil.d("onCreate()")
        //通过ViewModelProvider来获取ViewModel对象
        val viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
        //注册监听,监听数据的回调
        viewModel.userLiveData.observe(this, Observer {
            //接收到数据
            dismissLoading()
            mBinding.tvUserInfo.text = it
        })

        mBinding.tvRequestUserInfo.onClick {
            // 请求数据
            showLoading()
            viewModel.getUserInfo()
        }
    }

    override fun onStop() {
        super.onStop()
        LogUtil.d("onStop()")
    }

    override fun onDestroy() {
        super.onDestroy()
        LogUtil.d("onDestroy()")
    }
}

生命周期打印数据如下:

/com.sum.tea D/LogUtil: onStop()
/com.sum.tea D/LogUtil: onDestroy()
/com.sum.tea D/LogUtil: onCreate()

2.单Activity多Fragment数据共享

Activity 中的多个 Fragment 需要相互通信是一种很常见的情况。以往通信的方式有接口,ResultAPI,EventBus,广播等多种方式,而 ViewModel 也可以实现一个 Activity 与其中的多个 Fragment 之间的相互通信

//发送数据
class UserFragment : BaseDataBindFragment<FragmentMainBinding>() {
    override fun initView(view: View, savedInstanceState: Bundle?) {
        val viewModel = ViewModelProvider(requireActivity()).get(MainViewModel::class.java)
        // 获取ViewModel,注意传入的是宿主Activity
        viewModel.userLiveData.observe(this) {
            mBinding.tvUserInfo.text = it
        }
    }
}
//接收数据
class VideoFragment : BaseDataBindFragment<FragmentMainBinding>() {
    override fun initView(view: View, savedInstanceState: Bundle?) {
        // 获取ViewModel,注意传入的是宿主Activity
        val viewModel = ViewModelProvider(requireActivity()).get(MainViewModel::class.java)
        viewModel.userLiveData.value = "我是苏火火,我是苏火火,我是苏火火"
    }
}

3.跨页面数据共享

还可以使用它跨页面跨 Activity 数据共享,只要 Application 实现 ViewModelStoreOwner 就可以了,其他页面通过 application 获取 ViewModel:

//让Application实现ViewModelStoreOwner接口
class SumApplication : Application(), ViewModelStoreOwner {

    private val appViewModelStore: ViewModelStore by lazy {
        ViewModelStore()
    }

    override fun getViewModelStore(): ViewModelStore {
        return appViewModelStore
    }
}

//其他页面通过application获取viewModel
val viewModel = ViewModelProvider(application).get(MainViewModel::class.java)

如果是正常关闭一个页面,ViewModel 的数据还是正常被关闭被清理的。有人会疑惑,页面都被销毁了,为什么数据还继续存在?实际上是 ViewModel 被保存了下来,页面被重建之后,我们在获取 ViewModel 实例的时候实际上还是同一个,从而达到里面数据的复用。

三、ViewModel的实现原理

很多人说 ViewModel 实现原理都是说成 ViewModel 是如何实现数据复用的,其实这种说法是不够准确的,因为 ViewModel 能够实现数据复用,本质上是 ViewModel 的实例对象得到了复用。准确点来说是 ViewModel 在宿主销毁了,还能继续存在。以至于页面恢复重建后,还能接着复用。(肯定是前后获取到的是同一个 ViewModel 实例对象)

先来认识 ViewModel 中几个重要的类:

  • ViewModelProvider:  提供获取 ViewModel 实例的类,ViewModel 都是从这个类中获取。
  • ViewModelStoreOwner:核心接口,该接口的实现类表示能够为外部提供 ViewModelStore 实例
  • ViewModelStore:   核心类,存储 ViewModel 实例的类,实际是一个HashMap。
  • Factory:        核心接口,负责创建 ViewModel 实例。

如果要分析 ViewModel 复用的原理,就要从如何获取 ViewModel 的实例说起。

1.ViewModel的存储和获取

获取 ViewModel 的实例过程:

val viewModel = ViewModelProvider(activity).get(HiViewModel::class.java)

ViewModelProvider 类有三个构造函数,无论你使用的是哪个构造函数,最后都会调用ViewModelProvider(ViewModelStore store, Factory factory)

#ViewModelProvider.java
public ViewModelProvider(ViewModelStoreOwner owner) {
    this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
            ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
            : NewInstanceFactory.getInstance());
}

public ViewModelProvider(ViewModelStoreOwner owner, Factory factory) {
    this(owner.getViewModelStore(), factory);
}

public ViewModelProvider(ViewModelStore store, Factory factory) {
    mFactory = factory;
    mViewModelStore = store;
}

在创建 ViewModelProvider 的时候,参数虽然是 Activity,实际上这里要求的是 ViewModelStoreOwner,那是因为 Activity 也就是 ComponentActivity 它实现了 ViewModelStoreOwner 这个接口的,并且复写了 getViewModelStore() 方法,然后把 Store 和 Factory 两个参数保存了起来:

public interface ViewModelStoreOwner {
    //获取ViewModelStore
    ViewModelStore getViewModelStore();
}

这个 ViewModelStore 就是用来存储 ViewModel 实例的,它本质上就是一个 HashMap,非常简单:

//用于存储ViewModel实例
public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    // 保存viewModel实例
    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());
    }

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

如果不传递 Factory 参数则会有个默认的,Factory 就是用来创建 ViewModel 实例的,当我们从 ViewModelStore 里面获取 ViewModel 实例的时候,如果获取不到,就会通过 Factory 来创建它,待会来分析它,继续看 ViewModelProvider 的 get 方法:

#ViewModelProvider.java
@MainThread
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    String canonicalName = modelClass.getCanonicalName();
    //拿到Key,即ViewModelStore中的Map用于存ViewModel的Key
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

private static final String DEFAULT_KEY =
        "androidx.lifecycle.ViewModelProvider.DefaultKey";

通过 canonicalName 和 DEFAULT_KEY 字段拼接成一个字符串,从而形成一个完整的 Key 字段,就是在 ViewModelStore 里面存储 ViewModel 的 key

#ViewModelProvider.java
@MainThread
public <T extends ViewModel> T get(String key, Class<T> modelClass) {
    //根据key从ViewModelStore中获取ViewModel实例
    ViewModel viewModel = mViewModelStore.get(key);

    if (modelClass.isInstance(viewModel)) {
        if (mFactory instanceof OnRequeryFactory) {
            ((OnRequeryFactory) mFactory).onRequery(viewModel);
        }
        // 如果有这个实例则返回
        return (T) viewModel;
    } 
    
    // 根据mFactory的类型来创建ViewModel实例
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
    } else {
        viewModel = mFactory.create(modelClass);
    }
    // 存储到ViewModelStore中
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

回到第一个构造函数中:

ViewModelProviderpublic ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
    this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
            ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
            : NewInstanceFactory.getInstance());
}

getDefaultViewModelProviderFactory() 实际是 SavedStateViewModelFactory:

SavedStateViewModelFactory类:
@Override
public <T extends ViewModel> T create(String key, Class<T> modelClass) {
    // 判断是否为AndroidViewModel
    boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass);
    Constructor<T> constructor;
    // 获取构造器
    if (isAndroidViewModel && mApplication != null) {
        constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE);
    } else {
        constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE);
    }
    // 普通方式创建ViewModel实例
    if (constructor == null) {
        return mFactory.create(modelClass);
    }

    try {
        T viewmodel;
        if (isAndroidViewModel && mApplication != null) {
            viewmodel = constructor.newInstance(mApplication, controller.getHandle());
        } else {
            viewmodel = constructor.newInstance(controller.getHandle());
        }
        return viewmodel;
}

创建的时候判断 modelClass 是否拥有两种构造函数:

//第一种:有两个参数
private static final Class<?>[] ANDROID_VIEWMODEL_SIGNATURE = new Class[]{Application.class,
        SavedStateHandle.class};
//第二种:只有一个参数
private static final Class<?>[] VIEWMODEL_SIGNATURE = new Class[]{SavedStateHandle.class};

如果上面两种都没有,那个在构造实例的时候,就会以普通的形式构造实例 AndroidViewModelFactory,实际上是通过反射

  if (constructor == null) {
        return mFactory.create(modelClass);
    }

而 NewInstanceFactory 则是一个简单的创建工厂,根据空构造函数,直接通过 Class 直接创建一个 ViewModel 实例

public static class NewInstanceFactory implements Factory {
    @Override
    public <T extends ViewModel> T create(@Class<T> modelClass) {
         // 通过modelClass反射获取ViewModel实例
         return modelClass.newInstance();
    }
}

首先通过 mViewModelStore 根据 key 获取 ViewModel 实例,还会判断该实例是不是传入的 Class 类型的,如果是则直接返回,如果不是则会根据 mFactory 是什么类型的,来决定是用什么创建方式来创建 ViewModel 实例,创建完成后存储到 ViewModelStore 中。这就是 ViewModel 实例的存储和获取过程。

2.ViewModelStore的存储和获取

ViewModel 实例是用 ViewModelStore 来获取的,既然要做到 ViewModel 实例的复用,那么 ViewModelStore 它也必须做到复用才可以,关键点就在这里,ViewModelStore 是在 ComponentActivity 里面被获取的:

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory {
        
    public ViewModelStore getViewModelStore() {
        // 如果存储器为空则从NonConfigurationInstances中获取
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                //从NonConfigurationInstances中恢复ViewModelStore
                mViewModelStore = nc.viewModelStore;
            }
            // 如果获取不到,则直接创建一个
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }
}

注意了,首先通过 NonConfigurationInstances 获取 ViewModelStore,如果 NonConfigurationInstances 不存则创建一个 ViewModelStore 实例。那么 NonConfigurationInstances 是什么?

static final class NonConfigurationInstances {
    Object custom;
    ViewModelStore viewModelStore;
}

NonConfigurationInstance s对象本质上是一个包装类,包装一些在配置变更后还想留存的数据,比如说 Fragment,如果一个 Activity 上面有一个 Fragment,我们的页面在旋转了以后,Activity 会被重建,Fragment 还是原来的哪一个,这里的实现原理就是通过 NonConfigurationInstances 来实现的,那么 ViewModel 的复用也是同样的原理。

但是 ViewModel 对象的存储发生在什么时候呢?要先存储了才能够实现复用,实际发生在 onRetainNonConfigurationInstance() 方法里面:

#ComponentActivity.java
@Override
// 在Activity因配置变更而销毁再重建Activity时,此方法会被调用
// 因配置变更而被销毁而保存的数据,在重建的时候还想继续复用
public final Object onRetainNonConfigurationInstance() {
    Object custom = onRetainCustomNonConfigurationInstance();

    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) {
        // 尝试从NonConfigurationInstance获取ViewModelStore
        NonConfigurationInstances nc =
        (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            viewModelStore = nc.viewModelStore;
        }
    }

    // 如果 viewModelStore == null 则直接返回null
    if (viewModelStore == null && custom == null) {
        return null;
    }

    // viewModelStore不为空则构建了一个NonConfigurationInstances对象,并且把viewModelStore存了进去。
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}

重点来了,在 Activity 因配置变更而要销毁时,且会重新创建 Activity,系统就会调用这个方法。 也就说,配置改变时系统把 viewModelStore 存在了 NonConfigurationInstances 中

跟进 onRetainNonConfigurationInstance(),它是复写父类的,在 Activity 里面找到调用它的时机:

#Activity.java
NonConfigurationInstances retainNonConfigurationInstances() {
    Object activity = onRetainNonConfigurationInstance();
    HashMap<String, Object> children = onRetainNonConfigurationChildInstances();

    //······
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.activity = activity;
    nci.children = children;
    nci.fragments = fragments;
    nci.loaders = loaders;
    if (mVoiceInteractor != null) {
        mVoiceInteractor.retainInstance();
        nci.voiceInteractor = mVoiceInteractor;
    }
    return nci;
}

重要的是retainNonConfigurationInstances()这个方法的调用入口在哪里呢?这个是无法在IDE中查看的,它实际是在 ActivityThread 里面。

3.Activity的重建流程

正常启动一个 Activity 的时候,它会在执行 ActivityThread 的 handleLaunchActivity(),但是一个页面因为配置变更而重建的时候它执行 handleRelaunchActivity() 方法,这个方法是重新创建 Avtivity 的一个入口,但是它正在创建的方法是在里面的 handleRelaunchActivityInner

#ActivityThread.java
//因为配置变更而重建的时候会执行
@Override
public void handleRelaunchActivity(ActivityClientRecord tmp,
        PendingTransactionActions pendingActions) {
    //·····
    //mActivities里面存储的就是当前应用当前进程所已经打开的所有Activity信息的集合
    ActivityClientRecord r = mActivities.get(tmp.token);

    // 重新创建Activity执行
    handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
        pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
     //·····
}

mActivities 里面存储的就是当前应用当前进程所已经打开的所有 Activity 信息的集合,通过 mActivities 集合获取到一个
ActivityClientRecord,它里面有个重要的参数lastNonConfigurationInstances

public static final class ActivityClientRecord {
    //······
    //因配置变更而被销毁的Activity它所存留下来的数据
    Activity.NonConfigurationInstances lastNonConfigurationInstances;
}

它所存储的就是因配置变更而被销毁的 Activity 它所存留下来的配置,只不过在这个方法当中它的值还是空的。

// 执行重新创建Activity
private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
        List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
        PendingTransactionActions pendingActions, boolean startsNotResumed,
        Configuration overrideConfig, String reason) {
    //保留上次使用的Intent
    final Intent customIntent = r.activity.mIntent;
    //重新创建Acitivty之前需要把发生配置变更的Activity销毁掉
    if (!r.paused) {
        performPauseActivity(r, false, reason, null /* pendingActions */);
    }
    if (!r.stopped) {
        callActivityOnStop(r, true /* saveState */, reason);
    }

    // 销毁Activity
    handleDestroyActivity(r, false, configChanges, true, reason);

    //创建和启动Activity
    handleLaunchActivity(r, pendingActions, customIntent);
}

在重新创建 Acitivty 之前需要把发生配置变更的 Activity 销毁掉,所以执行配置变更的 performPauseActivity() 方法以及 callActivityOnStop() 方法,在 callActivityOnStop() 一并执行 callActivityOnSaveInstanceState(r) 并且有机会存储数据。

@Override
public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges,
        boolean getNonConfigInstance, String reason) {
    // 执行Activity销毁方法的核心方法
    performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason);
}

handleDestroyActivity() 方法里面有个 boolean getNonConfigInstance 参数, performDestroyActivity() 是执行 Activity 销毁方法的核心方法

// 销毁Activity
void performDestroyActivity(ActivityClientRecord r, boolean finishing,
        int configChanges, boolean getNonConfigInstance, String reason) {
    activityClass = r.activity.getClass();
    //·····
    if (!r.stopped) {
        callActivityOnStop(r, false /* saveState */, "destroy");
    }
    // 保存lastNonConfigurationInstances,从而保存了数据
    if (getNonConfigInstance) {
       r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
    }
}

如果 getNonConfigInstance == true,则会执行 r.activity.retainNonConfigurationInstances() 方法,从而把 Activity 当中受到配置变更而不想丢失的数据给保存起来,这样 ViewModelStore 对象也就被保存起来了。数据就被存储在 lastNonConfigurationInstances 里面:

image.png

这个数据都会被保存到 ActivityClientRecord 的 lastNonConfigurationInstances 里面,这个r对象是通过 ActivityClientRecord r = mActivities.get(tmp.token) 获取的,那实际上就跟在 handleLaunchActivity() 当中获取到的那个 ActivityClientRecord 对象是同一个实例对象。

那么在 performDestroyActivity() 中的 ActivityClientRecord 就包含了被销毁的 Activity 存留下来的对象,销毁之后就执行 Avtivity 重建工作,重建最终会走到 performLaunchActivity() 方法,执行 newActivity() 去 New 一个 Activity,还会执行 activity.attach() 方法:

// 执行创建启动Activity
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ActivityInfo aInfo = r.activityInfo;

    //······
    Activity activity = null;
    // 创建Activity
    activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);

    //里面保存了lastNonConfigurationInstances,省略部分参数
     activity.attach(appContext, this, r.lastNonConfigurationInstances ····);

            r.lastNonConfigurationInstances = null;
            
            r.activity = activity;
            if (r.isPersistable()) {
                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
            } else {
                mInstrumentation.callActivityOnCreate(activity, r.state);
            }
        }
        r.setState(ON_CREATE);
    }

    return activity;
}

这里又把 r.lastNonConfigurationInstances 传递了进去,在 Activity 类中的 attach() 方法就把 lastNonConfigurationInstances 保存起来:

#Activity.java
final void attach(Context context, ActivityThread aThread,
        NonConfigurationInstances lastNonConfigurationInstances ······) {
    // 保存lastNonConfigurationInstances
    mLastNonConfigurationInstances = lastNonConfigurationInstances;
}

所以我们在 CompontentActivity 当中的 getLastNonConfigurationInstance() 中的 mLastNonConfigurationInstances 获取到数据了:

@Nullable
public Object getLastNonConfigurationInstance() {
    return mLastNonConfigurationInstances != null
            ? mLastNonConfigurationInstances.activity : null;
}

那么就可以在 getViewModelStore() 中获取到 NonConfigurationInstances 从而完成 viewModelStore 对象的复用。

@Override
public ViewModelStore getViewModelStore() {
    // 如果存储器为空则从NonConfigurationInstances中获取
    if (mViewModelStore == null) {
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            //从NonConfigurationInstances中恢复ViewModelStore
            mViewModelStore = nc.viewModelStore;
        }
        // 如果还为空则创建一个ViewModelStore
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

也就是说,ActivityThread 中的 ActivityClientRecord 是不受 Activity 销毁重建的影响而被保存下来,ActivityClientRecord中的lastNonConfigurationInstances也就被保存下来,那么ComponentActivity中的NonConfigurationInstances的viewModelStore就被保存下来实现复用。

到这里 ViewModel 复用的分析完了,本质上就是 ViewModelStore 的复用,顺便分析了一把 Activity 销毁重建的流程。

四、总结

ViewModel 的执行流程如下:

在这里插入图片描述

  1. ViewModelProvider 从 ViewModelStore 根据 key 获取 ViewModel实例,判断该实例的 Class 类型的,如果是则直接返回,如果不是则会根据 mFactory 是什么类型的,来决定是用什么创建方式来创建 ViewModel 实例,创建完成后存储到 ViewModelStore 中

  2. Activity 实现了 ViewModelStoreOwner 这个接口,并复写了 getViewModelStore() 方法,先从 NonConfigurationInstances 里面获取 ViewModelStore,如果没有就创建新的实例并保存起来

  3. ViewModelStore 通过 key-value 的形式存储 ViewModel,而它自己在 Activity 因配置变更而销毁再重建时,调用 onRetainNonConfigurationInstance() 存储在 NonConfigurationInstances 里面

  4. Activity 销毁时会将 NonConfigurationInstances 保存在ActivityThread#ActivityClientRecord 中,重建后通 Activity.attach() 重新传递给Activity,实现复用

  5. ActivityThread 中的 ActivityClientRecord 是不受 Activity 销毁重建的影响而被保存下来,ActivityClientRecord中的lastNonConfigurationInstances也就被保存下来,那么ComponentActivity中的NonConfigurationInstances的viewModelStore就被保存下来实现复用

因为 ViewModel 能够实现数据复用,本质上是 ViewModel 的实例对象得到了复用。

源码地址:https://github.com/suming77/SumTea_Android

点关注,不迷路


好了各位,以上就是这篇文章的全部内容了,很感谢您阅读这篇文章。我是suming,感谢各位的支持和认可,您的点赞就是我创作的最大动力。山水有相逢,我们下篇文章见!

本人水平有限,文章难免会有错误,请批评指正,不胜感激 !

参考链接:

  • ViewModel官网

希望我们能成为朋友,在 Github博客 上一起分享知识,一起共勉!Keep Moving!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/662365.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【STM32】软件I2C

【STM32】软件I2C I2C简介 I2C总线是一种串行、半双工的总线&#xff0c;主要用于近距离、低速的芯片之间的通信。I2C总线有两根双向的信号线&#xff0c;一根数据线SDA用于收发数据&#xff0c;一根时钟线SCL用于通信双方时钟的同步。 在一个i2c通讯总线中&#xff0c;可连接…

怎么显示文件后缀名?查看文件后缀名可以这样做!

案例&#xff1a;在我的电脑上&#xff0c;看不到文件的后缀名&#xff0c;这会导致命名时出现重复文件后缀的情况&#xff0c;给我带来了不好的体验。怎么才能看到文件的后缀名呢&#xff1f;如何操作&#xff1f; 在日常使用电脑的过程中&#xff0c;我们经常需要查看文件的…

实习记录(二)Java常用工具库

一.Lombok 1.背景概述 Lombok是一个非常高效的专用于Java的自动构建插件库&#xff0c;其简化了 JavaBean 的编写&#xff0c;避免了冗余和样板式代码的出现&#xff0c;让编写的类更加简洁明了&#xff0c;可以帮助大家节省很多重复低效的代码编写。比如重复性的Setter、Gett…

【c语言】五道经典练习题④

目录 ①、年月日经过n天后的日期 ②、坐标排序 ③、统计文件中出现某个单词的次数 ④、输出含for的行 ⑤、比较两个文本是否相等 ①、年月日经过n天后的日期 题述&#xff1a;定义包含年月日表示的日期的结构体&#xff0c;写程序实现计算某年某月某日过n天后的日期是哪年…

肠道细菌阻碍阿卡波糖的降血糖作用

我们知道&#xff0c;口服抗糖尿病药是治疗糖尿病的有效方式之一。然而&#xff0c;患者对抗糖尿病药的反应程度各不相同&#xff0c;例如&#xff0c;有些患者在长期使用阿卡波糖后会产生耐药性。 阿卡波糖通常在饭前口服。它抑制人α-葡萄糖苷酶达到降血糖作用&#xff0c;包…

GWO-VMD-近似熵-极限学习机的轴承故障诊断软件,以西储大学轴承数据为例,采用MATLABAPP开发

采用灰狼算法优化VMD两个参数&#xff0c;以包络熵为最小适应度值&#xff0c;在最佳参数下提取采用近似熵指标提取西储大学轴承数据的特征向量&#xff0c;最后选用极限学习机ELM进行故障诊断。将以上程序集成在MATLABAPP进行开发。 首先是这个软件的各个界面展示。 软件启动…

云安全技术(四)之云计算安全的设计原则

计算安全的设计原则 Understand Design Principles of Secure Cloud Computing 1.1 云安全数据生命周期 Cloud secure data lifecycle 数据始终是安全保护的首要问题。必须深刻了解数据生命周期&#xff0c;以便正确制定和遵守安全策略&#xff0c;把握正确的步骤顺序&#xf…

万博智云与品高股份完成产品兼容性互认证,持续助力国产化生态建设

近日&#xff0c;万博智云的HyperBDR云容灾软件与广州市品高股份有限公司&#xff08;简称&#xff1a;品高股份&#xff09;旗下产品品高基础架构云资源管理软件V9.0完成了产品兼容性认证。 经万博智云和品高云双方人员的共同测试&#xff0c;得出结论&#xff1a; HyperBDR…

【C/C++数据结构与算法】C语言链表

目录 一、单链表 二、双向循环链表 三、判断链表是否带环 四、链表的回文结构判断 五、复制带随机指针的链表 一、单链表 优点&#xff1a;头部增删效率高&#xff0c;动态存储无空间浪费 缺点&#xff1a;尾部增删、遍历效率低&#xff0c;不支持随机访问节点 头结点&…

【夜深人静学习数据结构与算法 | 第六篇】贪心算法

目录 前言&#xff1a; 引入: 贪心算法&#xff1a; 455. 分发饼干 - 力扣&#xff08;LeetCode&#xff09; 376. 摆动序列 - 力扣&#xff08;LeetCode&#xff09; 53. 最大子数组和 - 力扣&#xff08;LeetCode&#xff09; 122. 买卖股票的最佳时机 II - 力扣&a…

【Python 随练】统计字符类型个数

题目&#xff1a; 输入一行字符&#xff0c;分别统计出其中英文字母、空格、数字和其它字符的个数。 简介&#xff1a; 在本篇博客中&#xff0c;我们将解决一个字符统计问题&#xff1a;输入一行字符&#xff0c;统计其中英文字母、空格、数字和其他字符的个数。我们将提供…

学习python爬虫需要掌握哪些库?

Python爬虫是指使用Python编写的程序&#xff0c;用来自动化地获取互联网上的数据。通过爬取网站的HTML内容&#xff0c;并解析和提取所需的数据&#xff0c;可以实现自动化地收集、分析和处理大量的在线数据。 学习Python爬虫需要掌握以下几个核心库&#xff1a; Requests&am…

【ARM AMBA AXI 入门 9 - AXI 总线 AxPROT 与安全之间的关系 】

文章目录 介绍ARM Trustzone的安全扩展简介 1.1 AXI AxPROT 介绍1.1.1 AXI 对 Trustzone的支持 介绍 ARMv8 架构中的AXI&#xff08;Advanced eXtensible Interface&#xff09;总线与NS&#xff08;Non-Secure&#xff09;位密切相关。NS位是指在ARM TrustZone安全扩展中定义…

LeetCode 1254. Number of Closed Islands【DFS,BFS,并查集】中等

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

单片机MCU如何实现让部分代码运行在RAM中

随着单片机硬件的发展&#xff0c;其中的RAM和flash越做越大。MCU在实际的使用中&#xff0c;通常程序都是运行在flash上的&#xff0c;RAM的高速空间并没有得到充分的利用&#xff0c;如果我们的程序需要运行的更快&#xff0c;系统有更好的实时性&#xff0c;我们可以考虑将这…

CSS查缺补漏之《常用长度单位(px、em、rem、%、vw/vh、vmin/vmax)》

此文内容较少&#xff0c;轻轻松松掌握&#xff0c;莫要有压力~ 正如现实生活中长度具有mm、dm、cm、m等&#xff0c;在css中&#xff0c;也具备多种长度单位&#xff0c;本文对常用的几种单位进行详细举例介绍~ px&#xff1a;像素单位 初学css时&#xff0c;px单位经常被使用…

【Leetcode60天带刷】day08字符串——344.反转字符串, 541. 反转字符串II,剑指Offer 05.替换空格,151.翻转字符串里的单词

题目&#xff1a; 344. 反转字符串 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 不要给另外的数组分配额外的空间&#xff0c;你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 示例 1&#xff1a; 输入&…

基于SpringBoot+Vue的“漫画之家”系统设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架下…

新电脑机环境安装笔记

「Navicat_15.0.25_64bit_Setup.exe」 下载https://www.aliyundrive.com/s/b9xUw2JpuJb Navicat Keygen Patch v5.6.0 下载 https://www.aliyundrive.com/s/YYyE5BQMMuN 全程断网操作 patch 将安装目录选中 提示 check 64 mysql安装&#xff1a; https://baijiahao.baidu…

因子分析——SPSS实例分析

【续上篇主成分分析】 因子分析常用于通过可观测变量推断出其背后的公共因子&#xff08;也称为隐变量&#xff09;&#xff0c;样本在公共因子上的取值变化影响其在可观测变量上的取值&#xff0c;因为一般公共因子的个数小于可观测变量的数目&#xff0c;所以因子分析也可以…