Android ViewModel为什么能够跨越Activity的生命周期?

ViewModel能够在系统配置发生变化时,跨越Activity的生命周期,其实质是在旧Activity即将销毁时,通过NonConfigurationInstances将ViewModel挂载到 ActivityClientRecord上

重建的新Activity与销毁的旧Activity是共用同一个ActivityClientRecord实例

在重建新Activity时,会从ActivityClientRecord中取出旧Activity销毁之前保存的NonConfigurationInstances

1、旧Activity的销毁

1.1、AMS重建Activity时,会传入该Activitytoken。这样,在应用进程中就可以通过这个token获取到保存在mActivities中的ActivityClientRecord
复制代码
@Override
public void handleRelaunchActivity(@NonNull ActivityClientRecord tmp,
        @NonNull PendingTransactionActions pendingActions) {
    ......
    ActivityClientRecord r = mActivities.get(tmp.token);
    if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handling relaunch of " + r);
    if (r == null) {
        return;
    }

    r.activity.mConfigChangeFlags |= configChanges;
    r.mPreserveWindow = r.activity.mWindowAdded && tmp.mPreserveWindow;

    r.activity.mChangingConfigurations = true;

    handleRelaunchActivityInner(r, tmp.pendingResults, tmp.pendingIntents,
            pendingActions, tmp.startsNotResumed, tmp.overrideConfig, tmp.mActivityWindowInfo,
            "handleRelaunchActivity");
}
1.2、在重建新Activity之前,会先将失效的旧Activity销毁。同时,将ActivityClientRecord对应的字段置空,待新Activity创建完成后,再将该字段重新赋值为新Activity
复制代码
private void handleRelaunchActivityInner(@NonNull ActivityClientRecord r,
        @Nullable List<ResultInfo> pendingResults,
        @Nullable List<ReferrerIntent> pendingIntents,
        @NonNull PendingTransactionActions pendingActions, boolean startsNotResumed,
        @NonNull Configuration overrideConfig, @NonNull ActivityWindowInfo activityWindowInfo,
        @NonNull String reason) {
    // Preserve last used intent, it may be set from Activity#setIntent().
    final Intent customIntent = r.activity.mIntent;
    // Need to ensure state is saved.
    if (!r.paused) {
        performPauseActivity(r, false, reason, null /* pendingActions */);
    }
    if (!r.stopped) {
        callActivityOnStop(r, true /* saveState */, reason);
    }

    handleDestroyActivity(r, false /* finishing */, true /* getNonConfigInstance */, reason);

    r.activity = null;
    r.window = null;
    r.hideForNow = false;
    // Merge any pending results and pending intents; don't just replace them
    if (pendingResults != null) {
        if (r.pendingResults == null) {
            r.pendingResults = pendingResults;
        } else {
            r.pendingResults.addAll(pendingResults);
        }
    }
    if (pendingIntents != null) {
        if (r.pendingIntents == null) {
            r.pendingIntents = pendingIntents;
        } else {
            r.pendingIntents.addAll(pendingIntents);
        }
    }
    r.startsNotResumed = startsNotResumed;
    r.overrideConfig = overrideConfig;
    r.mActivityWindowInfo.set(activityWindowInfo);

    handleLaunchActivity(r, pendingActions, mLastReportedDeviceId, customIntent);
}
1.3、在销毁旧Activity之前,会对传入的getNonConfigInstance变量进行判断。若为真,则调用ActivityretainNonConfigurationInstances()方法, 收集需要保存的状态信息, 将其何存至Activity对应的ActivityClientRecord对象的android.app.Activity.NonConfigurationInstances类型的数据成员中
复制代码
/** Core implementation of activity destroy call. */
void performDestroyActivity(ActivityClientRecord r, boolean finishing,
        int configChanges, boolean getNonConfigInstance, String reason) {
    Class<? extends Activity> activityClass = null;
    if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
    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);
            }
        }
    }
    ......
}

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

2、新Activity的创建

2.1、在新创建Activityattach()方法中,传入之前保存的android.app.Activity.NonConfigurationInstances
复制代码
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
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,
        IBinder shareableActivityToken) {
    ......
    mTitle = title;
    mParent = parent;
    mEmbeddedID = id;
    mLastNonConfigurationInstances = lastNonConfigurationInstances;
    ......
}
2.2、在新创建的Activity中获取ViewModelStore时,如果mViewModelStore数据成员为空,则会通过getLastNonConfigurationInstance()方法,从之前恢复的android.app.Activity.NonConfigurationInstances中,获取androidx.activity.ComponentActivity.NonConfigurationInstances
复制代码
@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();
        }
    }
}

/**
 * Retrieve the non-configuration instance data that was previously
 * returned by {@link #onRetainNonConfigurationInstance()}.  This will
 * be available from the initial {@link #onCreate} and
 * {@link #onStart} calls to the new instance, allowing you to extract
 * any useful dynamic state from the previous instance.
 *
 * <p>Note that the data you retrieve here should <em>only</em> be used
 * as an optimization for handling configuration changes.  You should always
 * be able to handle getting a null pointer back, and an activity must
 * still be able to restore itself to its previous state (through the
 * normal {@link #onSaveInstanceState(Bundle)} mechanism) even if this
 * function returns null.
 *
 * <p><strong>Note:</strong> For most cases you should use the {@link Fragment} API
 * {@link Fragment#setRetainInstance(boolean)} instead; this is also
 * available on older platforms through the Android support libraries.
 *
 * @return the object previously returned by {@link #onRetainNonConfigurationInstance()}
 */
@Nullable
public Object getLastNonConfigurationInstance() {
    return mLastNonConfigurationInstances != null
            ? mLastNonConfigurationInstances.activity : null;
}

3、androidx.activity.ComponentActivity.NonConfigurationInstancesandroid.app.Activity.NonConfigurationInstances之间的转换

复制代码
static final class NonConfigurationInstances {
    Object custom;
    ViewModelStore viewModelStore;
}

static final class NonConfigurationInstances {
    Object activity;
    HashMap<String, Object> children;
    FragmentManagerNonConfig fragments;
    ArrayMap<String, LoaderManager> loaders;
    VoiceInteractor voiceInteractor;
}
3.1、将androidx.activity.ComponentActivity.NonConfigurationInstance保存至android.app.Activity.NonConfigurationInstances类中的activity(位于对象内存模型的最开头)数据成员
复制代码
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;
    }
3.2、将android.app.Activity.NonConfigurationInstances类中的activity成员强转成androidx.activity.ComponentActivity.NonConfigurationInstance类型
复制代码
@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();
        }
    }
}

4、ViewModel的销毁

4.1、重建新Activity之前,先将待销毁的旧ActivitymChangingConfigurations变量置为true。随后,依次执行旧ActivityonPause()onStop()onDestroy()
复制代码
@Override
public void handleRelaunchActivity(@NonNull ActivityClientRecord tmp,
        @NonNull PendingTransactionActions pendingActions) {
    // If we are getting ready to gc after going to the background, well
    // we are back active so skip it.
    unscheduleGcIdler();
    mSomeActivitiesChanged = true;

    ......

    r.activity.mConfigChangeFlags |= configChanges;
    r.mPreserveWindow = r.activity.mWindowAdded && tmp.mPreserveWindow;

    r.activity.mChangingConfigurations = true;

    ......
}
4.2、只有当Activity不是在配置发生变化,而引起重建的情况下,ViewModel才会在Activity销毁时,进行clear操作。否则,不会跟随Activity销毁进行clear操作
复制代码
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();
            }
        }
    }
});
相关推荐
Meteors.13 小时前
安卓源码阅读——01.grade设置binding为true时,xml如何进行映射
android·xml
_李小白14 小时前
【android opencv学习笔记】Day 26: 滤波算法之低通滤波与图像缩放插值
android·opencv·学习
NiceCloud喜云15 小时前
Claude Code Routines 实战:三种触发器跑通云端自动化编码
android·运维·数据库·人工智能·自动化·json·飞书
我命由我1234518 小时前
Bugly - Bugly 基本使用( App 质量追踪平台)
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
weiggle18 小时前
第二篇:搭建你的第一个 Compose 项目——开发环境与项目结构
android·前端
阿巴斯甜18 小时前
为什么 AIDL 接口客户端、服务端要写两份一模一样的?
android
weiggle19 小时前
第一篇:Jetpack Compose 宣言——为什么 Android 开发需要声明式 UI
android
城管不管21 小时前
什么是Prompt?
android·java·数据库·语言模型·llm·prompt
weiggle21 小时前
Jetpack Compose 重组机制与性能优化深度剖析
android