一道面试题:ViewModel为什么横竖屏切换时不销毁?

简介: 如今Android面试中经常问及Jetpack相关问题,很多候选人往往知道如何使用但不知道原理。原理不清虽不影响API的使用,但也正因为如此,如果能对源码有一定了解,也许可以脱颖而出得到加分。

往年面试中有关Jetpack的考察可以算是加分项,随着官方对Modern Android development (MAD) 的大力推广,今年基本上都是必选题了。

很多候选人对Jetpack各组件的功能及用法如数家珍,但一问及到原理往往卡壳。原理不清虽不影响API的使用,但也正因为如此,如果能对源码有一定了解,也许可以脱颖而出得到加分。

本文分享一个入门级的源码分析,也是在面试中经常被问到的问题

ViewModel

ViewModel是Android Jetpack中的重要组件,其优势是具有下图这样的生命周期、不会因为屏幕旋转等Activity配置变化而销毁,是实现MVVM架构中UI状态管理的重要基础。
在这里插入图片描述

class MyActivity : AppCompatActivity {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    Log.d(TAG, "onCreate")

    val activity: FragmentActivity = this
    val factory: ViewModelProvider.Factory = ViewModelProvider.NewInstanceFactory()

    // Activity由于横竖品切换销毁重建,此处的viewModel 仍然是重建前的实例
    val viewModel = ViewModelProvider(activity, factory).get(MyViewModel::class.java)
    // 如果直接new实例则会创建新的ViewModel实例
    //  val viewModel = MyViewModel()

    Log.d(TAG, "  - Activity :${this.hashCode()}")
    Log.d(TAG, "  - ViewModel:${viewModel.hashCode()}")
  }
}

上面代码在横竖屏切换时的log如下:

#Activity初次启动
onCreate
  - Activity :132818886
  - ViewModel:249530701
onStart
onResume

#屏幕旋转
onPause
onStop
onRetainNonConfigurationInstance
onDestroy
onCreate
  - Activity :103312713  #Activity实例不同
  - ViewModel:249530701  #ViewModel实例相同
onStart
onResume

下面代码是保证屏幕切换时ViewModel不销毁的关键,我们依次为入口看一下源码

val viewModel = ViewModelProvider(activity, factory).get(MyViewModel::class.java)

ViewModelProvider

ViewModelProvider源码很简单,分别持有一个ViewModelProvider.FactoryViewModelStore实例

package androidx.lifecycle;

public class ViewModelProvider {

    public interface Factory {
        @NonNull
        <T extends ViewModel> T create(@NonNull Class<T> modelClass);
    }

    private final Factory mFactory;
    private final ViewModelStore mViewModelStore;

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

    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        this.mViewModelStore = store;
    }

    ...
}

get()返回ViewModel实例

package androidx.lifecycle;

public class ViewModelProvider {
    ...

    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();

        ...

        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }

    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);

        if (modelClass.isInstance(viewModel)) {
            //noinspection unchecked
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }

        viewModel = mFactory.create(modelClass);
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        return (T) viewModel;
    }

    ...
}

逻辑非常清晰:

  1. ViewModelProvider通过ViewModelStore获取ViewModel
  2. 若获取失败,则通过ViewModelProvider.Factory创建ViewModel

ViewModelStore

package androidx.lifecycle;

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }
}

可见,ViewModelStore就是一个对Map的封装。

val viewModel = ViewModelProvider(activity, factory).get(FooViewModel::class.java)

上面代码ViewModelProvider()构造参数1中传入的FragmentActivity(基类是ComponentActivity)实际上是ViewModelStoreOwner的一个实现。

package androidx.lifecycle;

public interface ViewModelStoreOwner {
    @NonNull
    ViewModelStore getViewModelStore();
}

ViewModelProvider中的ViewModelStore正是来自ViewModelStoreOwner。

public class ViewModelProvider {

    private final ViewModelStore mViewModelStore;

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

    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        this.mViewModelStore = store;
    }

Activity在onDestroy会尝试对ViewModelStore清空。如果是由于ConfigurationChanged带来的Destroy则不进行清空,避免横竖屏切换等造成ViewModel销毁。

//ComponentActivity.java
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 (!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();
                    }
                }
            }
        });()) {
                        getViewModelStore().clear();
                    }
                }
            }
        });

<br/>

FragmentActivity#getViewModelStore()

FragmentActivity实现了ViewModelStoreOwnergetViewModelStore方法

package androidx.fragment.app;

public class FragmentActivity extends ComponentActivity implements ViewModelStoreOwner ... {

    private ViewModelStore mViewModelStore;

    @NonNull
    @Override
    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;
    }

    static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
        FragmentManagerNonConfig fragments;
    }

    ...
}

通过getLastNonConfigurationInstance() 获取 NonConfigurationInstances 实例,从而得到真正的viewModelStoregetLastNonConfigurationInstance()又是什么?
<br/>

Activity#getLastNonConfigurationInstance()

package android.app;

public class Activity extends ContextThemeWrapper implements ... {

    /* package */ NonConfigurationInstances mLastNonConfigurationInstances;

    @Nullable
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }
Retrieve the non-configuration instance data that was previously returned by onRetainNonConfigurationInstance(). This will be available from the initial onCreate(Bundle) and onStart() calls to the new instance, allowing you to extract any useful dynamic state from the previous instance.

通过官方文档我们知道,屏幕旋转前通过onRetainNonConfigurationInstance()返回的Activity实例,屏幕旋转后可以通过getLastNonConfigurationInstance()获取,因此屏幕旋转前后不销毁的关键就在onRetainNonConfigurationInstance

Activity#onRetainNonConfigurationInstance()

#Activity初次启动
onCreate
  - Activity :132818886
  - ViewModel:249530701
onStart
onResume

#屏幕旋转
onPause
onStop
onRetainNonConfigurationInstance
onDestroy
onCreate
  - Activity :103312713  #Activity实例不同
  - ViewModel:249530701  #ViewModel实例相同
onStart
onResume

屏幕旋转时,onRetainNonConfigurationInstance()onStoponDestroy之间调用

package android.app;

public class Activity extends ContextThemeWrapper implements ... {

    public Object onRetainNonConfigurationInstance() {
        return null;
    }

    ...
}

onRetainNonConfigurationInstance在Activity中只有空实现,在FragmentActivity中被重写

package androidx.fragment.app;

public class FragmentActivity extends ComponentActivity implements ViewModelStoreOwner, ... {

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

        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

        if (fragments == null && mViewModelStore == null && custom == null) {
            return null;
        }

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

    static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
        FragmentManagerNonConfig fragments;
    }

    ...
}

FragmentActivity 通过 onRetainNonConfigurationInstance() 返回 了存放ViewModelStore的NonConfigurationInstances 实例。
值得一提的是onRetainNonConfigurationInstance提供了一个hook时机:onRetainCustomNonConfigurationInstance,允许我们像ViewModel一样使得自定义对象不被销毁

NonConfigurationInstances会在attach中由系统传递给新重建的Activity:

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)
        

然后在onCreate中,通过getLastNonConfigurationInstance()获取NonConfigurationInstances中的ViewModelStore

package androidx.fragment.app;

public class FragmentActivity extends ComponentActivity implements ViewModelStoreOwner ... {

    private ViewModelStore mViewModelStore;

    @SuppressWarnings("deprecation")
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        mFragments.attachHost(null /*parent*/);

        super.onCreate(savedInstanceState);

        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null && nc.viewModelStore != null && mViewModelStore == null) {
            mViewModelStore = nc.viewModelStore;
        }
        ...
    }
}

总结

Activity首次启动

  • FragmentActivity#onCreate()被调用

    • 此时 FragmentActivity 的 mViewModelStore 尚为 null
  • HogeActivity的onCreate() 被调用

    • ViewModelProvider 实例创建
    • FragmentActivity#getViewModelStore() 被调用,mViewModelStore被创建并赋值

发生屏幕旋转

  • FragmentActivity#onRetainNonConfigurationInstance() 被调用

    • 持有mViewModelStore 的NonConfigurationInstances 实例被返回

Activity重建

  • FragmentActivity#onCreate() 被调用

    • 从Activity#getLastNonConfigurationInstance() 获取 NonConfigurationInstances 实例
    • NonConfigurationInstances 中保存了屏幕旋转前的 FragmentActivity 的 mViewModelStore,将其赋值给重建后的FragmentActivity 的 mViewModelStore
  • HogeActivity#onCreate() 被调用

    • 通过ViewModelProvider#get() 获取 ViewModel 实例
目录
相关文章
|
Unix Perl
sed的插入操作
sed的插入操作
619 4
|
2月前
|
Arthas 监控 Java
Java死锁 如何定位?如何避免Java死锁?(图解+秒懂+史上最全)
Java死锁 如何定位?如何避免Java死锁?(图解+秒懂+史上最全)
Java死锁 如何定位?如何避免Java死锁?(图解+秒懂+史上最全)
|
Android开发
【错误记录】Android Studio 中 build.gradle 配置 buildFeatures prefab 错误处理 ( AS 4.1 以上开发环境 | Gradle及插件版本 )
【错误记录】Android Studio 中 build.gradle 配置 buildFeatures prefab 错误处理 ( AS 4.1 以上开发环境 | Gradle及插件版本 )
2115 0
【错误记录】Android Studio 中 build.gradle 配置 buildFeatures prefab 错误处理 ( AS 4.1 以上开发环境 | Gradle及插件版本 )
|
存储 算法 Java
深入解析Java虚拟机(JVM):技术原理与性能优化
深入解析Java虚拟机(JVM):技术原理与性能优化
380 1
|
Android开发
Android面试高频知识点(1) 图解 Android 事件分发机制
在Android开发中,事件分发机制是一块Android比较重要的知识体系,了解并熟悉整套的分发机制有助于更好的分析各种点击滑动失效问题,更好去扩展控件的事件功能和开发自定义控件,同时事件分发机制也是Android面试必问考点之一,如果你能把下面的一些事件分发图当场画出来肯定加分不少。废话不多说,总结一句:事件分发机制很重要。
353 9
|
11月前
|
Java 调度 Android开发
Android面试题之Kotlin中async 和 await实现并发的原理和面试总结
本文首发于公众号“AntDream”,详细解析了Kotlin协程中`async`与`await`的原理及其非阻塞特性,并提供了相关面试题及答案。协程作为轻量级线程,由Kotlin运行时库管理,`async`用于启动协程并返回`Deferred`对象,`await`则用于等待该对象完成并获取结果。文章还探讨了协程与传统线程的区别,并展示了如何取消协程任务及正确释放资源。
234 0
钉钉中,如果你想使用卡片模板ID来发送工作通知
钉钉中,如果你想使用卡片模板ID来发送工作通知
545 2
|
机器学习/深度学习 算法 PyTorch
【PyTorch实战演练】深入剖析MTCNN(多任务级联卷积神经网络)并使用30行代码实现人脸识别
【PyTorch实战演练】深入剖析MTCNN(多任务级联卷积神经网络)并使用30行代码实现人脸识别
1130 2
|
安全 Java 编译器
在Java中,什么是类型擦除机制,如何有效运用泛型的类型擦除机制?
Java的类型擦除机制在编译时移除了泛型的类型参数信息,生成的字节码不包含泛型,以确保向后兼容。这导致运行时无法直接获取泛型类型,但编译器仍做类型检查。为了有效利用类型擦除,应避免运行时类型检查,使用通配符和界限增加代码灵活性,通过超类型令牌获取泛型信息,以及利用泛型方法来保证安全性。理解这些策略能帮助开发者编写更安全的泛型代码。
322 8
|
安全 编译器 API
Android HAL深入探索(5): 调试HAL报错与解决方案
Android HAL深入探索(5): 调试HAL报错与解决方案
2746 1