ViewModel的局限,销毁重建的方案SavedStateHandle

7,688 阅读4分钟

前言

大概很久很久没有发文章了...罪过罪过,春节即将结束,也趁着当下肺炎的特殊时期,宅在家里没事就写了一篇文章。一来宣示我还“活”着,二来...扶我起来,我还能学。

ViewModel,作为Jetpack中举足轻重的部分,相信大家已经很熟悉了。

而今天重提是因为要为自己的“愚蠢”买单...之前的自己一直想当然的以为ViewModel可以解决Activity销毁重建的问题(但是,这是一个错误的认知)。直到踩到了坑,才仔细的想了想...

正文

一、简述

ViewModel没办法解决销毁重建,这个显而易见。

毕竟get到ViewModel实例的最大维度是Activity级别的。而销毁重建意味了Activity实例也变了,那么顺理成章通过此实例get到的ViewModel也是全新的...

其实在官方的文档上,咱们也可以明白,比如这张图:

从图中,我们可以看到ViewModel的Scope中是没有包含onSaveInstanceState()的,因此很明显ViewModel是不会主动处理销毁重建的问题。

当然可能有小伙伴觉得这只是猜测...大家可以自己写个demo试一下,你会发现销毁重建后get到的ViewModel中的变量一切都是null。

二、如何解决

2.1、参考文档

当然这么明显的问题,Google也一定会提供方案的。事实也是如此,如下的文档可以帮助你快速找到解决方案(不过需要自备科学上网),当然不想看这些文绉绉的官方文档,也可以听我继续在这“瞎逼逼”...

codelabs.developers.google.com/codelabs/an…

developer.android.com/topic/libra…

2.2、实战

implementation "androidx.savedstate:savedstate:1.0.0"
// ktx还是alpaha尝鲜版,大家自行考虑
implementation "androidx.savedstate:savedstate-ktx:1.1.0-alpha01"

2.2.1、ViewModel的新参数

从官方文档上我们能够看到,ViewModel需要新的参数SavedStateHandle

class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }

熟练使用ViewModel的同学在看到构造函数要提供新的参数时,都能意识到:我们需要给ViewModel提供一个自定义的Fractory,很麻烦

不过,Google也意识到了这个问题,所以在activity-ktx、fragemtn-ktx中提供了快速实例化ViewModel的扩展函数:by activityViewModels()by viewModels()

上述扩展函数可以帮助我们应对单一的、含有额外的SavedStateHandle的ViewModel构造函数。

2.2.2、如何使用

其实Google并没有为我们提供特别方便的解决方法,想要在Activity级别销毁重建上被恢复,需要我们对现有的代码进行改造。

除了构造方法中SavedStateHandle的引入外,我们还要针对我们需要恢复的变量进行改造,假设我们有一个叫做userName的成员变量和一个LiveData:

class DemoViewModel(private val state: SavedStateHandle) :ViewModel{
	var userName: String? = ""
    val userNameLiveData: LiveData = MutableLiveData<String>()
}

对于userName我们要基于SaveStateHandle进行如下的改造:

class DemoViewModel(private val state: SavedStateHandle) :ViewModel{
	companion object{
        const val SAVE_STATE_KEY_STRING = "saved_state_handle_user_name"
        const val SAVE_STATE_KEY_LIVEDATE = "saved_state_handle_user_name_livedata"
    }
	var userName: String?
        get() {
            return state.get(SAVE_STATE_KEY_STRING)
        }
        set(value) {
            state.set(SAVE_STATE_KEY_STRING, value)
        }
        
    fun getUserNameLiveData(): LiveData<String> = state.getLiveData(SAVE_STATE_KEY_LIVEDATE)
    
    fun saveNewName(newName: String) {
    	state.set(SAVE_STATE_KEY_LIVEDATE, newName);
	}
}

2.2.3、原理

通过上述的例子我们就能够明白,Google就是把拥有Save能力的SavedStateHandle给我们,让我们可以通过get方法得到销毁时我们自己set的变量:

public <T> MutableLiveData<T> getLiveData(@NonNull String key) {
    return getLiveDataInternal(key, false, null);
}

public <T> T get(@NonNull String key) {
    return (T) mRegular.get(key);
}

而这种方式,和咱们onSaveInstanceState()的方式没有任何区别,我猜大家差不多能够猜到这里的实现原理,没错就是onSaveInstanceState()的那一套,比如在Fragment中是如何save的?

// 触发销毁时,会走到Fragment的这个方法
void performSaveInstanceState(Bundle outState) {
	// 我们常用的方法
    onSaveInstanceState(outState);
    // 此方法的实现在下边
    mSavedStateRegistryController.performSave(outState);
	// 省略部分代码
}

void performSave(@NonNull Bundle outBundle) {
    Bundle components = new Bundle();
    if (mRestoredState != null) {
        components.putAll(mRestoredState);
    }
    for (Iterator<Map.Entry<String, SavedStateProvider>> it =
            mComponents.iteratorWithAdditions(); it.hasNext(); ) {
        Map.Entry<String, SavedStateProvider> entry1 = it.next();
        components.putBundle(entry1.getKey(), entry1.getValue().saveState());
    }
    outBundle.putBundle(SAVED_COMPONENTS_KEY, components);
}

private final SavedStateProvider mSavedStateProvider = new SavedStateProvider() {
    @Override
    public Bundle saveState() {
        Set<String> keySet = mRegular.keySet();
        ArrayList keys = new ArrayList(keySet.size());
        ArrayList value = new ArrayList(keys.size());
        for (String key : keySet) {
            keys.add(key);
            value.add(mRegular.get(key));
        }

        Bundle res = new Bundle();
        res.putParcelableArrayList("keys", keys);
        res.putParcelableArrayList("values", value);
        return res;
    }
};

上述代码根本不需要什么解释,是在是太简单了。

补一张图,大家有兴趣可以结合源码自己感受一下,不是很难:

尾声

说实话,处理销毁重建这种常见很多情况下对于业务场景来说并不是特别的重要,因此关于这部分内容可以说仁者见仁智者见智,不过基于这个契机会多看看源码似乎也是极好的~

我是一个应届生,最近和朋友们维护了一个公众号,内容是我们在从应届生过渡到开发这一路所踩过的坑,以及我们一步步学习的记录,如果感兴趣的朋友可以关注一下,一同加油~

个人公众号:咸鱼正翻身