美文网首页Android技术知识
字节高工面试灵魂7问:Android架构组件—ViewModel

字节高工面试灵魂7问:Android架构组件—ViewModel

作者: 字节走动_Android | 来源:发表于2020-11-19 11:13 被阅读0次

前言

今天跟大家分享的是网友提供的一道字节跳动的面试真题,请看原题:

  • ViewModel 是什么?
  • ViewModel 为什么被设计出来,解决了什么问题?
  • 说说ViewModel原理。
  • ViewModel怎么实现自动处理生命周期?
  • 为什么在旋转屏幕后不会丢失状态?
  • 为什么ViewModel可以跟随Activity/Fragment的生命周期而又不会造成内存泄漏呢?
  • ViewModelScope了解吗?

解答

ViewModel是什么?

ViewModel是MVVM架构的一个层级,用来联系View和model之间的关系。

官方文档解释:

ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据

这里面有两个信息:

  • 注重生命周期的方式。

由于ViewModel的生命周期是作用于整个Activity的,所以就节省了一些关于状态维护的工作,最明显的就是对于屏幕旋转这种情况,以前对数据进行保存读取,而ViewModel则不需要,他可以自动保留数据。

其次,由于ViewModel在生命周期内会保持局部单例,所以可以更方便Activity的多个Fragment之间通信,因为他们能获取到同一个ViewModel实例,也就是数据状态可以共享了。

  • 存储和管理界面相关的数据。

ViewModel层的根本职责,就是负责维护界面上UI的状态,其实就是维护对应的数据,因为数据会最终体现到UI界面上。所以ViewModel层其实就是对界面相关的数据进行管理,存储等操作。

ViewModel 为什么被设计出来,解决了什么问题?

  • ViewModel组件被设计出来之前,MVVM又是怎么实现ViewModel这一层级的呢?

其实就是自己编写类,然后通过接口,内部依赖实现View和数据的双向绑定。所以Google出这个ViewModel组件,无非就是为了规范MVVM架构的实现,并尽量让ViewModel这一层级只触及到业务代码,不去关心VIew层级的引用等。然后配合其他的组件,包括livedata,databindingrang等让MVVM架构更加完善,规范,健硕。

  • 解决了什么问题呢?

1)不会因为屏幕旋转而销毁,减少了维护状态的工作
2)由于在作用域内单一实例的特性,使得多个fragment之间可以方便通信,并且维护同一个数据状态。
3)完善了MVVM架构,使得解耦更加纯粹。

说说ViewModel原理。

在分析ViewModel的工作原理之前,我们最好带着问题去分析。

  • ViewModel如何被创建的,在什么时候被销毁?
  • ViewModel的生命周期如何与组件生命周期绑定?
  • 为什么Activity重建时ViewModel的数据没有丢失?
  • Fragment之前如何共享ViewModel?

ViewModel的创建与销毁

ViewModel的创建

分析ViewModel的创建自然是从ViewModel初始化的地方开始。

viewModel = ViewModelProvider(this).get(TestViewModel::class.java)

ViewModel的初始化很简单,通过ViewModelProvider就可以获取到ViewModel实例。那么从这里入手开始分析ViewModel的创建过程。

第一步是先初始化了一个ViewModelProvider对象,所以我们先看下ViewModelProvider的构造方法;

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

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

从ViewModelProvider的构造方法中可以看到最终是需要两个参数ViewModelStoreOwner以及Factory。这两个参数中ViewModelStoreOwner是用来存储ViewModel对象的,Factory是用来创建ViewModel对象。这个接下来的分析中也会提到。

第二步是通过ViewModelProvider的get()方法获取ViewModel实例。

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key); // 1.是否有缓存ViewModel实例缓存

        ...
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass);
        }
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }

在get()方法中可以看到是通过mFactory的类型来创建ViewModel的。而Factory的类型是由ViewModelStoreOwner决定的,这是ViewModelProvider的构造方法中的逻辑。其中有两种Factory,一种是SavedStateViewModelFactory,另一种是NewInstanceFactory。接下来我们直接看两种方式的区别。

SavedStateViewModelFactory创建ViewModel
public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
        boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass);
        Constructor<T> constructor;
        ...
        SavedStateHandleController controller = SavedStateHandleController.create(
                mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
        try {
            T viewmodel;
            if (isAndroidViewModel) {
                viewmodel = constructor.newInstance(mApplication, controller.getHandle());
            } else {
                viewmodel = constructor.newInstance(controller.getHandle());
            }
            viewmodel.setTagIfAbsent(TAG_SAVED_STATE_HANDLE_CONTROLLER, controller);
            return viewmodel;
        } 
        ...
    }

在代码中可以看到,ViewModel有分为AndroidViewModel跟ViewModel,它们的区别是AndroidViewModel创建时会加入Application参数。

NewInstanceFactory创建ViewModel
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            //noinspection TryWithIdenticalCatches
            try {
                return modelClass.newInstance();
            } catch (InstantiationException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            }
        }

NewInstanceFactory创建过程相对简单,通过Class的newInstance()方法直接创建ViewModel实例。

ViewModel的销毁

这里还要分开两处说,一个是在Activity中的销毁过程,一个是在Fragment中销毁过程。

首先我们把目光放到ViewModel类来看下ViewModel销毁时都做了什么。

protected void onCleared() {}

final void clear() {
    mCleared = true;
    if (mBagOfTags != null) {
        synchronized (mBagOfTags) {
            for (Object value : mBagOfTags.values()) {
                // see comment for the similar call in setTagIfAbsent
                closeWithRuntimeException(value);
            }
        }
    }
    onCleared();
}

可以看到ViewModel中有clear()方法和onCleared()方法。通过跟踪方法的调用可以知道ViewModel的销毁过程。最终在ViewModelStore类中找到了clear()方法。

public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
复制代码
Activity中的销毁

继续跟踪代码可以看到在ComponentActivity中:

 public ComponentActivity() {
    getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    if (!isChangingConfigurations()) {
                        // 销毁ViewModel
                        getViewModelStore().clear();
                    }
                }
            }
        });
 }

在ComponentActivity的构造方法中,可以看到通过Lifecycle在ON_DESTROY事件中销毁ViewModel。

Fragment中的销毁

首先通过代码跟踪到ViewModelStore的clear()方法调用的地方,在FragmentManagerViewModel类的clearNonConfigState()方法中找到了ViewModel的销毁逻辑。

void clearNonConfigState(@NonNull Fragment f) {
    ...
    // Clear and remove the Fragment's ViewModelStore
    ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
    if (viewModelStore != null) {
        // 销毁ViewModel
        viewModelStore.clear();
        mViewModelStores.remove(f.mWho);
    }
}

如果继续跟踪代码可以看到代码的调用栈是 FragmentStateManager::destroy() -> (Fragment状态切换)->FragmentManager::dispatchDestroy()->FragmentActivity::onDestory()。

这里有个小细节需要注意,也是我在项目中遇到的一个问题。就是ViewModel的销毁在onDestory()中,是晚于onDestoryView()的,所以要注意在使用ViewModel做操作时会不会触发组件更新。不然的话可能造成空指针异常。

ViewModel的生命周期绑定

在上面分了ViewModel的创建与销毁过程中就可以得出ViewModel生命周期是如何与组件交互的了。主要还是通过Lifecycle和组件的生命周期方法来进行回调管理。

Activity重建下的ViewModel

按照上面的逻辑,在Activity重建时会执行destory生命周期事件,那么为什么ViewModel没有销毁呢?

还是直接在代码里找答案,通过对ComponentActivity的getViewModelStore()方法进行分析。可以找到这个问题的答案。

public ViewModelStore getViewModelStore() {
        ...
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }

可以看大mViewModelStore变量如果是null的话,会从NonConfigurationInstances实例中取。那么我们就分析NonConfigurationInstances实例的来源。

从NonConfigurationInstances的名称可以大致判断这个类与配置无关。

public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();

        ViewModelStore viewModelStore = mViewModelStore;
        if (viewModelStore == null) {
            // No one called getViewModelStore(), so see if there was an existing
            // ViewModelStore from our last NonConfigurationInstance
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                viewModelStore = nc.viewModelStore;
            }
        }

        if (viewModelStore == null && custom == null) {
            return null;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }

继续分析下去,可以看到在onRetainNonConfigurationInstance中会存储ViewModelStore实例,这也就是为什么ViewModel不会在Activity重建时被销毁的原因。

Fragment之间共享ViewModel

在分析ViewModel的销毁过程时,我们看到Activity与Fragment存储VieModel是分离的,那么同一个Activity下的Fragment是如何共享ViewModel的呢?

这里先说答案,它们其实共享的是Activity的ViewModel。

根据Android文档的代码就可以找到答案了。

 private val model: SharedViewModel by activityViewModels()

这个实现的逻辑就在activityViewModels()中。

inline fun <reified VM : ViewModel> Fragment.activityViewModels(
    noinline factoryProducer: (() -> Factory)? = null
) = createViewModelLazy(VM::class, { requireActivity().viewModelStore },
    factoryProducer ?: { requireActivity().defaultViewModelProviderFactory })

在activityViewModels()的实现中可以看到是requireActivity()获取的viewModelStore。以此来实现共享ViewModel。

小结一下

通过上面的分析可以发现ViewModel并不算复杂,在Android其他架构组件的加持下可以说使用是很方便的。即使在项目仅仅使用(Fragment/Activity)+ViewModel也能不错的实现MVVM模式。

ViewModel生命周期问题

  • ViewModel怎么实现自动处理生命周期?
  • 为什么在旋转屏幕后不会丢失状态?
  • 为什么ViewModel可以跟随Activity/Fragment的生命周期而又不会造成内存泄漏呢?

这三个问题很类似,都是关于生命周期的问题,所以放在一起回答。其实也就是问为什么ViewModel能管理生命周期,并且不会因为重建等情况造成影响。

  • ViewModel2.0之前

利用一个无view 的HolderFragment来维持它的生命周期,我们知道ViewModel实例是存储到一个ViewModelStore容器里的,那么这个空的fragment就可以用来管理这个容器,只要Activity处于活动状态,HolderFragment也就不会被销毁,就保证了ViewModel的生命周期。

而且设置setRetainInstance(true)方法可以保证configchange时的生命周期不被改变,让这个Fragment在Activity重建时存活下来。总结来说就是用一个空的fragment来管理维护ViewModelStore,然后对应的activity销毁的时候就去把viewmodel的映射删除。就让ViewModel的生命周期保持和Activity一样了。这也是很多三方库用到的巧妙方法,比如Glide,也是建立空的Fragment来管理。

  • 2.0之后,有了androidx支持

其实是用到了Activity的一个子类ComponentActivity,然后重写了onRetainNonConfigurationInstance()方法保存ViewModelStore,并在需要的时候,也就是重建的Activity中去通过getLastNonConfigurationInstance()方法获取到ViewModelStore实例。这样也就保证了ViewModelStore中的ViewModel不会随Activity的重建而改变。

同时由于实现了LifecycleOwner接口,所以能利用Lifecycles组件组件感知每个页面的生命周期,就可以通过它来订阅当Activity销毁时,且不是因为配置导致的destory情况下,去清除ViewModel,也就是调用ViewModelStore的clear方法。

getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                // 判断是否因为配置更改导致的destroy
                if (!isChangingConfigurations()) {
                    getViewModelStore().clear();
                }
            }
        }
    });

这里的onRetainNonConfigurationInstance方法再说下,是会在Activity因为配置改变而被销毁时被调用,跟onSaveInstanceState方法调用时机比较相像,不同的是onSaveInstanceState保存的是Bundle,Bundle是有类型限制和大小限制的,而且需要在主线程进行序列号。而onRetainNonConfigurationInstance方法都没有限制,所以更倾向于用它。

所以,到这里,第三个问题应该也可以回答了,2.0之前呢,都是通过他们创建了一个空的fragment,然后跟随这个fragment的生命周期。2.0之后呢,是因为不管是Activity或者Fragment,都实现了LifecycleOwner接口,所以ViewModel是可以通过Lifecycles感知到他们的生命周期,从而进行实例管理的。

ViewModelScope了解吗

这里主要就是考ViewModel和其他一些组件的关系了。关于协程,之前也专门说过一篇,主要用作线程切换。如果在多个协程中,需要停止某些任务,就必须对这些协程进行管理,一般是加入一个CoroutineScope,如果需要取消协程,就可以去取消这个CoroutineScope,他所跟踪的所有协程都会被取消。

GlobalScope.launch {
    longRunningFunction()
    anotherLongRunningFunction()
}

但是这种全局使用方法,是不被推荐使用的,如果要限定作用域的时候,一般推荐viewModelScope。

viewModelScope 是一个 ViewModel 的 Kotlin 扩展属性。它能在ViewModel销毁时 (onCleared() 方法调用时) 退出。所以只要使用了 ViewModel,就可以使用 viewModelScope在 ViewModel 中启动各种协程,而不用担心任务泄漏。

class MyViewModel() : ViewModel() {

    fun initialize() {
        viewModelScope.launch {
            processBitmap()
        }
    }

    suspend fun processBitmap() = withContext(Dispatchers.Default) {
        // 在这里做耗时操作
    }

}

文末

看到这里,我们再来回顾一下开篇的几个问题你都能回答出来了吗?

ViewModel 是什么?
ViewModel 为什么被设计出来,解决了什么问题?
说说ViewModel原理。
ViewModel怎么实现自动处理生命周期?
为什么在旋转屏幕后不会丢失状态?
为什么ViewModel可以跟随Activity/Fragment的生命周期而又不会造成内存泄漏呢?
ViewModelScope了解吗?

如果掌握的不太熟练,可以点赞收藏起来慢慢看。还有什么不太理解的点,也欢迎在评论区留言,大家一起学习讨论。

最后也给大家准备了一个小彩蛋:《2020互联网大厂Android高级面试真题收录解析》这份资料由我从金三银四春招开始收集整理,包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

最开始主要是方便自己积累、复习、成长。现已经在十月中旬拿到了阿里的offer,所以将这份《1296页2020互联网大厂Android高级面试真题收录解析》分享出来给大家。希望可以帮助到更多的小伙伴顺利的通过面试,拿到心仪的offer。

资料已经打包上传在我的GitHub。或者可以在评论区留下你的邮箱,看到后我会将PDF发给你。希望大家可以点赞支持一下。你小小的一个赞,对我来说非常重要,谢谢大家!

参考:
https://juejin.im/post/6890092603091075079
https://mp.weixin.qq.com/s/P74JqO8eKvtdBhdoCzYI8A
https://mp.weixin.qq.com/s/5zIRbE69tWFZ3BIewusiLw

相关文章

网友评论

    本文标题:字节高工面试灵魂7问:Android架构组件—ViewModel

    本文链接:https://www.haomeiwen.com/subject/dorkiktx.html