从面试角度看ViewModel

2021-11-19  本文已影响0人  艾伦宇

一、问题

  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:从前世今生到追本溯源

上一篇 下一篇

猜你喜欢

热点阅读