从面试角度看ViewModel

一、问题

  1. 为什么要用ViewModel,有什么好处
  2. ViewModel如何在配置变化时保留数据
  3. 在界面旋转时,ViewModel中的请求还在执行吗
  4. ViewModel如何做到在页面销毁时,停止协程中的工作

二、解答

问题1、为什么要用ViewModel,有什么好处

  1. 数据状态的维护
    1. 旋转后保证数据还在
    2. 跟随Activity生命周期的作用域,防止内存泄漏
  2. 便于单元测试
    1. 只用于做纯的业务代码
    2. 不持有Activity或View对象
  3. 便于共享
    同一个Activity下的不同Fragment之间共享

问题2、ViewModel如何在配置变化时保留数据

利用的是Activity重建机制中的NonConfigurationInstances来保存
也就是在配置变化时,Activity会被销毁,但是重建时会从旧的Activity中拿到ViewModelStore

问题3、在界面旋转时,ViewModel中的请求还在执行吗

还会在,并不会调用ViewModel的clear方法
因为系统会判断是否因为页面在旋转导致的,如果是则不销毁ViewModel

        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();
                    }
                }
            }
        });

问题4、ViewModel如何做到在页面销毁时,停止协程中的工作

这是因为ViewModel会去监听Activity/Fragment的生命周期
在onDestory时,且不需要重建的情况下,会去调用ViewModel的clear方法
在这个方法里面会去close掉所有支持Closeable的对象
如 viewModelScope

//viewModelScope
public val ViewModel.viewModelScope: CoroutineScope
    get() {
        val scope: CoroutineScope? = this.getTag(JOB_KEY)
        if (scope != null) {
            return scope
        }
        return setTagIfAbsent(
            JOB_KEY,
            CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
        )
    }
internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context
    override fun close() {
        coroutineContext.cancel()
    }
}
// ViewModel的clear方法
    @MainThread
    final void clear() {
        mCleared = true;
        // Since clear() is final, this method is still called on mock objects
        // and in those cases, mBagOfTags is null. It'll always be empty though
        // because setTagIfAbsent and getTag are not final so we can skip
        // clearing it
        if (mBagOfTags != null) {
            synchronized (mBagOfTags) {
                for (Object value : mBagOfTags.values()) {
                    // see comment for the similar call in setTagIfAbsent
                    closeWithRuntimeException(value);
                }
            }
        }
        onCleared();
    }
    private static void closeWithRuntimeException(Object obj) {
        if (obj instanceof Closeable) {
            try {
                ((Closeable) obj).close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

三、参考

Android官方架构组件ViewModel:从前世今生到追本溯源

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容