ViewModel进阶解析

概览

ViewModel 类旨在以注重生命周期的方式存储和管理界面相关数据。ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。

ViewModel的生命周期

ViewModel 对象的作用域限定为获取 ViewModel 时传递给 ViewModelProviderViewModelStoreOwnerLifecycleViewModel 将一直留在内存中,直到其作用域 ViewModelStoreOwner 永久消失:

  • 对于 activity,是在 activity 完成时。
  • 对于 fragment,是在 fragment 分离时。
  • 对于 Navigation 条目,是在 Navigation 条目从返回堆栈中移除时。

这使得 ViewModels 成为了存储在配置更改后仍然存在的数据的绝佳解决方案。

更多介绍可以去官网查看

ViewModel原理解析

先看实例化ViewModel

kotlin 复制代码
private val viewModel :MyViewModel by lazy {
    ViewModelProvider(this)[MyViewModel::class.java]
}

可以看到通过ViewModelProvider初始化后的get()方法获取

kotlin 复制代码
public constructor(
    owner: ViewModelStoreOwner
) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))

public constructor(owner: ViewModelStoreOwner, factory: Factory) : this(
    owner.viewModelStore,
    factory,
    defaultCreationExtras(owner)
)//可以自定义factory,比如我们需要在ViewModel里面传参

可以看到对于我们👆🏻的初始化方式我们传入一个ViewModelStoreOwner,查看Activity的继承关系到 ComponentActivity就会发现这个Activity实现了 ViewModelStoreOwner接口,所以初始化的时候我们传入this。factory是用来创建ViewModel对象的。

kotlin 复制代码
internal fun defaultFactory(owner: ViewModelStoreOwner): Factory =
    if (owner is HasDefaultViewModelProviderFactory)
        owner.defaultViewModelProviderFactory else instance

如果是 HasDefaultViewModelProviderFactory实例,就获取defaultViewModelProviderFactory,要不就创建NewInstanceFactory,NewInstanceFacetory是直接通过反射创建ViewModel实例

kotlin 复制代码
public open class NewInstanceFactory : Factory {
    @Suppress("DocumentExceptions")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return try {
            modelClass.getDeclaredConstructor().newInstance() //反射创建实例
        } catch (e: NoSuchMethodException) {
            throw RuntimeException("Cannot create an instance of $modelClass", e)
        } catch (e: InstantiationException) {
            throw RuntimeException("Cannot create an instance of $modelClass", e)
        } catch (e: IllegalAccessException) {
            throw RuntimeException("Cannot create an instance of $modelClass", e)
        }
    }
   }

defaultViewModelProviderFactory则是在Activity中通过如下方法创建获取

less 复制代码
@NonNull
@Override
public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
    if (mDefaultFactory == null) {
        mDefaultFactory = new SavedStateViewModelFactory(
                getApplication(),
                this,
                getIntent() != null ? getIntent().getExtras() : null);
    }
    return mDefaultFactory;
}
kotlin 复制代码
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
    return if (application != null) {
        create(modelClass)
    } else {
        val application = extras[APPLICATION_KEY]
        if (application != null) {
            create(modelClass, application)
        } else {
            // For AndroidViewModels, CreationExtras must have an application set
            if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
                throw IllegalArgumentException(
                    "CreationExtras must have an application by `APPLICATION_KEY`"
                )
            }
            super.create(modelClass)
        }
    }
    ...
}

创建一个带有上下文的AndroidViewModel,然后看下get()方法

kotlin 复制代码
public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
    val canonicalName = modelClass.canonicalName
        ?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
    return get("$DEFAULT_KEY:$canonicalName", modelClass)
}
kotlin 复制代码
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
    val viewModel = store[key]
    if (modelClass.isInstance(viewModel)) {
        (factory as? OnRequeryFactory)?.onRequery(viewModel!!)
        return viewModel as T
    } else {
        @Suppress("ControlFlowWithEmptyBody")
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
    ...

store中获取

kotlin 复制代码
open class ViewModelStore {

    private val map = mutableMapOf<String, ViewModel>()

    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    fun put(key: String, viewModel: ViewModel) {
        val oldViewModel = map.put(key, viewModel)
        oldViewModel?.onCleared()
    }

    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    operator fun get(key: String): ViewModel? {
        return map[key]
    }

    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    fun keys(): Set<String> {
        return HashSet(map.keys)
    }
    fun clear() {
        for (vm in map.values) {
            vm.clear()
        }
        map.clear()
    }
}

里面维护了一个HashMap,根据Key值对viewmodel进行存储,并提供了clear()方法。

kotlin 复制代码
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
    val viewModel = store[key]
    if (modelClass.isInstance(viewModel)) {
        (factory as? OnRequeryFactory)?.onRequery(viewModel!!)
        return viewModel as T
    } else {
        @Suppress("ControlFlowWithEmptyBody")
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
    val extras = MutableCreationExtras(defaultCreationExtras)
    extras[VIEW_MODEL_KEY] = key
    // AGP has some desugaring issues associated with compileOnly dependencies so we need to
    // fall back to the other create method to keep from crashing.
    return try {
        factory.create(modelClass, extras)
    } catch (e: AbstractMethodError) {
        factory.create(modelClass)
    }.also { store.put(key, it) }
}

获取ViewModelStore

ViewModelProvider初始化时会通过 ViewModelStoreOwner获取ViewModelStore

csharp 复制代码
public ViewModelStore getViewModelStore() {
    if (getApplication() == null) {
        throw new IllegalStateException("Your activity is not yet attached to the "
                + "Application instance. You can't request ViewModel before onCreate call.");
    }
    ensureViewModelStore();
    return mViewModelStore;
}
ini 复制代码
void ensureViewModelStore() {
    if (mViewModelStore == null) {
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
}

这里是获取 NonConfigurationInstances对象,然后获取到 ViewModelStore,如果 NonConfigurationInstances为null,则创建ViewModelStore.看下 getLastNonConfigurationInstance()方法

typescript 复制代码
public Object getLastNonConfigurationInstance() {
    return mLastNonConfigurationInstances != null
            ? mLastNonConfigurationInstances.activity : null;
}

那这个 mLastNonConfigurationInstances在哪被赋值?

arduino 复制代码
final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,
        Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
        IBinder shareableActivityToken) {
    attachBaseContext(context);

    mFragments.attachHost(null /*parent*/);

  ...
    mLastNonConfigurationInstances = lastNonConfigurationInstances;//这这============
    ...

存储ViewModelStore

可以看到是在Activity的attach方法里面被赋值。我们知道在配置更改引起的Activity重建时会调用 onRetainNonConfigurationInstance()方法

ini 复制代码
public final Object onRetainNonConfigurationInstance() {
    // Maintain backward compatibility.
    Object custom = onRetainCustomNonConfigurationInstance();
       
    ViewModelStore viewModelStore = mViewModelStore; 
    if (viewModelStore == null) {
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            viewModelStore = nc.viewModelStore;
        }
    }
     //1.获取销毁之前的viewModelStore  *****************
    if (viewModelStore == null && custom == null) {
        return null;
    }
    //2.创建NonConfigurationInstances,赋值viewModelStore ****************
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}

现在viewModelStroe已经被存储在了NonConfigurationInstances中,页面要销毁,执行ActivityThread的 performDestroyActivity方法

php 复制代码
void performDestroyActivity(ActivityClientRecord r, boolean finishing,
        int configChanges, boolean getNonConfigInstance, String reason) {
    Class<? extends Activity> activityClass = null;
    if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
    activityClass = r.activity.getClass();
    r.activity.mConfigChangeFlags |= configChanges;
    if (finishing) {
        r.activity.mFinished = true;
    }
    ...
    if (getNonConfigInstance) {
        try {
            r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances(); 3*******
        } catch (Exception e) {
            if (!mInstrumentation.onException(r.activity, e)) {
                throw new RuntimeException("Unable to retain activity "
                        + r.intent.getComponent().toShortString() + ": " + e.toString(), e);
            }
        }
    }
    ...

会调用activity的 retainNonConfigurationInstances()方法

ini 复制代码
NonConfigurationInstances retainNonConfigurationInstances() {
    Object activity = onRetainNonConfigurationInstance();// 1  ***这块可以获取到刚才封装好的NonConfigurationInstances实例,里面有viewmodelStroe
    HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
    FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
    mFragments.doLoaderStart();
    mFragments.doLoaderStop(true);
    ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();

    if (activity == null && children == null && fragments == null && loaders == null
            && mVoiceInteractor == null) {
        return null;
    }

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

总结下上面就是把之前创建的带有viewmodelstroe实例的NonConfigurationInstances(上面1),在封装到一个NonConfigurationInstances(上面2),然后保存到ActivityClientRecord中(上面3)

恢复Viewmodel

然后在页面重建时,调用ActivityThread的 performLaunchActivity方法

ini 复制代码
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ActivityInfo aInfo = r.activityInfo;
   ...
            activity.attach(appContext, this, getInstrumentation(), r.token,
                    r.ident, app, r.intent, r.activityInfo, title, r.parent,
                    r.embeddedID, r.lastNonConfigurationInstances, config,
                    r.referrer, r.voiceInteractor, window, r.activityConfigCallback,
                    r.assistToken, r.shareableActivityToken);

            if (customIntent != null) {
                activity.mIntent = customIntent;
            }
  ...

然后就回到我们刚在说的(可以看到是在Activity的attach方法里面被赋值。)这样从保留NonConfigurationInstances到保留viewmodelstroe,最后viewmodel就被保留了。

最后看下销毁吧

less 复制代码
getLifecycle().addObserver(new LifecycleEventObserver() {
    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
            @NonNull Lifecycle.Event event) {
        if (event == Lifecycle.Event.ON_DESTROY) {
            // Clear out the available context
            mContextAwareHelper.clearAvailableContext();
            // And clear the ViewModelStore
            if (!isChangingConfigurations()) {
                getViewModelStore().clear();
            }
        }
    }
});

很简单,通过Lifecycle监听生命周期,然后是非配置更改引起的ON_DESTROY事件,清理viewmodelstore.

总结:

Viewmodel在配置更改时能保存数据,是因为在Activity销毁前,调用了retainNonConfigurationInstances(),将viewmodelstore存储到了NonConfigurationInstances对象里,然后在ondestory又创建了NonConfigurationInstances在把之前的NonConfigurationInstances存储进来,最后存储在了ActivityClientRecord中,在Activity重建时,通过attach方法,赋值给 mLastNonConfigurationInstances,这样最终就可以获取到之前的viewmodel

相关推荐
DeBuggggggg26 分钟前
centos 7.6安装mysql8
android
你这个年龄怎么睡得着的1 小时前
为什么 JavaScript 中 'str' 不是对象,却能调用方法?
前端·javascript·面试
浩浩测试一下2 小时前
渗透信息收集- Web应用漏洞与指纹信息收集以及情报收集
android·前端·安全·web安全·网络安全·安全架构
移动开发者1号3 小时前
深入理解原子类与CAS无锁编程:原理、实战与优化
android·kotlin
陈卓4103 小时前
MySQL-主从复制&分库分表
android·mysql·adb
牛客企业服务3 小时前
2025年AI面试推荐榜单,数字化招聘转型优选
人工智能·python·算法·面试·职场和发展·金融·求职招聘
移动开发者1号3 小时前
深入理解 ThreadLocal:原理、实战与优化指南
android·kotlin
zhangphil3 小时前
Android PNG/JPG图ARGB_8888/RGB_565‌解码形成Bitmap在物理内存占用大小的简单计算
android
厦门德仔3 小时前
【WPF】WPF(样式)
android·java·wpf
aqi004 小时前
FFmpeg开发笔记(七十二)Linux给FFmpeg集成MPEG-5视频编解码器EVC
android·ffmpeg·音视频·流媒体