Android 面試總結 - ViewModel 是怎麼保存和恢復?

畢業季來啦,你是畢業即失業,還是畢業就 入職 / 轉正 ?今天在這裏就和大家分享一波Android面試總結,希望對大家有幫助。

原文地址:https://juejin.cn/post/6976025197333708837

結合上一篇文章 Android 面試總結 - ViewModel我們知道:

在配置更改時會調用 Activity#onRetainNonConfigurationInstance() 來保存保存着 ViewModel 示例的對象 mViewModelStore,並在 Activity 重建後調用 getViewModelStore() ,其中會調用 ensureViewModelStore() 在它內部會調用 getLastNonConfigurationInstance() 方法獲取是否有緩存的 ViewModelStore 對象,若有則返回,沒有則創建新 ViewModelStore 實例。

這篇文章主要看看在配置更改後,怎麼調用 Activity#onRetainNonConfigurationInstance()

通過斷點調試方式知道在配置更改後會調用到 ActivityThread#handleRelaunchActivity 方法

爲啥會調用 ActivityThread#handleRelaunchActivity 後面學到了再分析 (剛看了一遍,沒看懂,看懂再說~啦啦啦)

    @Override
    public void handleRelaunchActivity(ActivityClientRecord tmp,
            PendingTransactionActions pendingActions) {
        ...
        handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
                pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
        ...

handleRelaunchActivityInner

    private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
            PendingTransactionActions pendingActions, boolean startsNotResumed,
            Configuration overrideConfig, String reason) {
        ...
        // 注意第三個參數爲 true
        handleDestroyActivity(r.token, false, configChanges, true, reason);
        ...
        handleLaunchActivity(r, pendingActions, customIntent);

內部調用了 handleDestroyActivity,並且第三個參數 getNonConfigInstance = true handleLaunchActivity 方法後面再說

    @Override
    public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
            boolean getNonConfigInstance, String reason) {
        ActivityClientRecord r = performDestroyActivity(token, finishing,
                configChanges, getNonConfigInstance, reason);
        ....

再看 performDestroyActivity 方法 它是 Activity 銷燬調用的核心實現

    /** Core implementation of activity destroy call. */
    ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
            int configChanges, boolean getNonConfigInstance, String reason) {
        ActivityClientRecord r = mActivities.get(token);
        if (r != null) {
            activityClass = r.activity.getClass();
            r.activity.mConfigChangeFlags |= configChanges;
            if (finishing) {
                r.activity.mFinished = true;
            }

            performPauseActivityIfNeeded(r, "destroy");

            if (!r.stopped) {
                callActivityOnStop(r, false /* saveState */, "destroy");
            }
            if (getNonConfigInstance) {
                try {
                    // 重點來了
                    r.lastNonConfigurationInstances
                            = r.activity.retainNonConfigurationInstances();
                } catch (Exception e) {
                    if (!mInstrumentation.onException(r.activity, e)) {
                        throw new RuntimeException(
                                "Unable to retain activity "
                                + r.intent.getComponent().toShortString()
                                + ": " + e.toString(), e);
                    }
                }
            }

重點在 performDestroyActivity 中,r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances(); 調用了 Activity 對象的 retainNonConfigurationInstances() 並將返回值賦值給了 ActivityClientRecord 類型的 r 對象的 lastNonConfigurationInstances 屬性。

再看看 Activity#retainNonConfigurationInstances 做了啥:

    NonConfigurationInstances retainNonConfigurationInstances() {
        // 重點
        Object activity = onRetainNonConfigurationInstance();
        HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

        // We're already stopped but we've been asked to retain.
        // Our fragments are taken care of but we need to mark the loaders for retention.
        // In order to do this correctly we need to restart the loaders first before
        // handing them off to the next activity.
        mFragments.doLoaderStart();
        mFragments.doLoaderStop(true);
        ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();

        if (activity == null && children == null && fragments == null && loaders == null
                && mVoiceInteractor == null) {
            return null;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.activity = activity;
        nci.children = children;
        nci.fragments = fragments;
        nci.loaders = loaders;
        if (mVoiceInteractor != null) {
            mVoiceInteractor.retainInstance();
            nci.voiceInteractor = mVoiceInteractor;
        }
        return nci;
    }

retainNonConfigurationInstances 中調用了 onRetainNonConfigurationInstance() 。 到這兒,知道了 onRetainNonConfigurationInstance() 是怎麼調用的了。

回頭再看看 handleRelaunchActivityInner 中最後調用了 handleLaunchActivity熟悉Activity 啓動流程的應該知道 handleLaunchActivity 是啓動 Activity 的重要步驟

    private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
            PendingTransactionActions pendingActions, boolean startsNotResumed,
            Configuration overrideConfig, String reason) {
        ...
        // 注意第三個參數爲 true
        handleDestroyActivity(r.token, false, configChanges, true, reason);
        ...
        handleLaunchActivity(r, pendingActions, customIntent);
    }

handleLaunchActivity

    @Override
    public Activity handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, Intent customIntent) {
         final Activity a = performLaunchActivity(r, customIntent);
    }

performLaunchActivity 啓動 Activity 的核心實現

    /**  Core implementation of activity launch. */
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
            Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }
        activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback,
                        r.assistToken);

創建了 Activity 實例並調用了 activityattach 方法,注意 attach 方法有一個參數 傳入了 r.lastNonConfigurationInstances ,有沒有很熟悉,剛剛在 performDestroyActivity 中,r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances(); 調用了 Activity 對象的 retainNonConfigurationInstances() 並將返回值賦值給了 ActivityClientRecord 類型的 r 對象的 lastNonConfigurationInstances 屬性。 已經串起來了。

    @UnsupportedAppUsage
    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) {
        attachBaseContext(context);
        ...
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        ...

attach 中把之前保存的 lastNonConfigurationInstances 對象又賦值進新的 Activity 實例的 mLastNonConfigurationInstances 對象中了。

再回顧一下 怎麼獲取 ViewModel

    val mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)

    // ViewModelProvider 的構造方法
    public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }
    // 
    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        ensureViewModelStore();
        return mViewModelStore;
    }

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    void ensureViewModelStore() {
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
    }

獲取 ViewModelStore 時,調用了 ensureViewModelStore() 方法,ensureViewModelStore() 在它內部會調用 getLastNonConfigurationInstance() 獲取是否有緩存的 ViewModelStore 對象,若有則返回,沒有則創建新 ViewModelStore 實例。

getLastNonConfigurationInstance

    @Nullable
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }

getLastNonConfigurationInstance 內部返回的是剛剛 Activity#attach 賦值的 mLastNonConfigurationInstances 對象。

到此 ViewModel 怎麼保存和恢復的 這個問題解決!

歡迎star我的GitHub(我的開發分享,另外主頁超多系統整理)

技術文系列:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章