【Android关键流程】Configuration变更时更新应用程序配置

基于Android U,只保留了关键调用步骤,略去了细节。

当设备配置发生变更时,系统会调用 ATMS 的 updateConfiguration() 方法,来通知 ATMS 处理 configuration change 事件。时序图如下。

java 复制代码
frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
public boolean updateConfiguration(Configuration values) {
    ...
    synchronized (mGlobalLock) {
        ...
        try {
            ...
            updateConfigurationLocked(values, null, false, false /* persistent */,
                    UserHandle.USER_NULL, false /* deferResume */,
                    mTmpUpdateConfigurationResult);  // 1
            return mTmpUpdateConfigurationResult.changes != 0;
        } finally {
            ...
        }
    }
}

注释1处调用 updateConfigurationLocked()

java 复制代码
frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
        boolean initLocale, boolean persistent, int userId, boolean deferResume,
        ActivityTaskManagerService.UpdateConfigurationResult result) {
    ...
    try {
        if (values != null) {
            changes = updateGlobalConfigurationLocked(values, initLocale, persistent, userId);  // 1
        }

        if (!deferResume) {
            kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);  // 2
        }
    } finally {
        ...
    }
    ...
    return kept;
}

注释1处更新当前配置;

注释2处确保给定的 Activity 使用的是当前配置,如果返回 true 表示 Activity 未被重启,否则让该 Activity destroyed 以适配当前配置。

java 复制代码
frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
boolean ensureConfigAndVisibilityAfterUpdate(ActivityRecord starting, int changes) {
    ...
    if (mainRootTask != null) {
        ...
        if (starting != null) {
            kept = starting.ensureActivityConfiguration(changes,
                    false /* preserveWindow */);  // 1
            ...
        }
    }

    return kept;
}

注释1处调用 ActivityRecord#ensureActivityConfiguration() 真正完成 Activity 配置更新。

java 复制代码
frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow) {
    return ensureActivityConfiguration(globalChanges, preserveWindow,
            false /* ignoreVisibility */, false /* isRequestedOrientationChanged */);
}

boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
        boolean ignoreVisibility, boolean isRequestedOrientationChanged) {
    // 这里省略了一些return ture的分支
    // 如果马上会再次调用updateConfiguration(),则忽略本次修改,交由下次处理,节省时间
    // 如果当前Activity已经finish,则忽略
    // 如果Activity的状态是DESTROYED,则忽略
    // 如果Activity不可见。则忽略
    ...
    // 如果新旧配置相同,则忽略
    mTmpConfig.setTo(mLastReportedConfiguration.getMergedConfiguration());
    if (getConfiguration().equals(mTmpConfig) && !forceNewConfig && !displayChanged) {
        ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration & display "
                + "unchanged in %s", this);
        return true;
    }

    // 计算哪些配置值发生了改变
    final int changes = getConfigurationChanges(mTmpConfig);

    // Update last reported values.
    final Configuration newMergedOverrideConfig = getMergedOverrideConfiguration();

    setLastReportedConfiguration(getProcessGlobalConfiguration(), newMergedOverrideConfig);

    // Activity状态是INITIALIZING,则忽略
    ...

    if (changes == 0 && !forceNewConfig) {
        ...
        if (displayChanged) {
            scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig);
        } else {
            scheduleConfigurationChanged(newMergedOverrideConfig);
        }
        notifyDisplayCompatPolicyAboutConfigurationChange(
                mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
        return true;
    }
    ...
    boolean shouldRelaunchLocked = shouldRelaunchLocked(changes, mTmpConfig);  // 1
    ...
    if (shouldRelaunchLocked || forceNewConfig) {  // 重启Activity
        ...
        if (mState == PAUSING) {
            ...
            // 如果当前Activity处于PAUSING状态,则标记其需要重启,等到PAUSING后reLaunch
            deferRelaunchUntilPaused = true;
            preserveWindowOnDeferredRelaunch = preserveWindow;
            return true;
        } else {
            ...
            relaunchActivityLocked(preserveWindow);
        }

        // All done...  tell the caller we weren't able to keep this activity around.
        return false;
    }

    // Activity可以自己处理配置变更走这里
    if (displayChanged) {
        scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig);
    } else {
        scheduleConfigurationChanged(newMergedOverrideConfig);
    }
    ...
    return true;
}

决定是否需要 reLaunch 的关键参数是 shouldRelaunchLockedforceNewConfig,任一值为 true,就会重启 Activity,而不会调用 Activity#onConfigurationChanged()

注释1处 shouldRelaunchLocked() 会通过对比事件是否在 Activity 自己可以处理的范围内来决定是否 reLaunch,其中包括 AndroidManifest.xml 中配置的 android:configChanges 属性。

  1. 重启 Activity 分支

    执行 ActivityRecord#relaunchActivityLocked(),经过层层调用,最终调用到 ActivityThread#handleRelaunchActivityInner()

    java 复制代码
    frameworks/base/core/java/android/app/ActivityThread.java
    private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
            PendingTransactionActions pendingActions, boolean startsNotResumed,
            Configuration overrideConfig, String reason) {
        ...
        handleDestroyActivity(r, false, configChanges, true, reason);
        ...
        handleLaunchActivity(r, pendingActions, mLastReportedDeviceId, customIntent);
    }

    handleRelaunchActivityInner() 中,先调用 handleDestroyActivity() 销毁当前 Activity,然后调用 handleLaunchActivity() 重启 Activity。Activity 有一个回调方法 onRetainNonConfigurationInstance(),当设备信息变更时,会保存该方法返回的 Object,之后可以在重启的 Activity 中通过 getLastNonConfigurationInstance() 获取该 Object。

  2. 不重启 Activity 分支

    java 复制代码
    frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
    boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
            boolean ignoreVisibility, boolean isRequestedOrientationChanged) {
        ...
        // Activity可以自己处理配置变更走这里
        if (displayChanged) {
            scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig);
        } else {
            scheduleConfigurationChanged(newMergedOverrideConfig);
        }
        ...
    }

    这两个分支最终都会调用 ActivityThread#handleActivityConfigurationChanged() 方法。

    java 复制代码
    frameworks/base/core/java/android/app/ActivityThread.java
    void handleActivityConfigurationChanged(ActivityClientRecord r,
            @NonNull Configuration overrideConfig, int displayId, boolean alwaysReportChange) {
        ... 
        final Configuration reportedConfig = performConfigurationChangedForActivity(r,
                mConfigurationController.getCompatConfiguration(),
                movedToDifferentDisplay ? displayId : r.activity.getDisplayId(),
                alwaysReportChange);
        ...
    }

    调用 performConfigurationChangedForActivity()。

    java 复制代码
    frameworks/base/core/java/android/app/ActivityThread.java
    private Configuration performConfigurationChangedForActivity(ActivityClientRecord r,
            Configuration newBaseConfig, int displayId, boolean alwaysReportChange) {
        r.tmpConfig.setTo(newBaseConfig);
        if (r.overrideConfig != null) {
            r.tmpConfig.updateFrom(r.overrideConfig);
        }
        final Configuration reportedConfig = performActivityConfigurationChanged(r,
                r.tmpConfig, r.overrideConfig, displayId, alwaysReportChange);
        freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
        return reportedConfig;
    }

    调用 performActivityConfigurationChanged()

    java 复制代码
    private Configuration performActivityConfigurationChanged(ActivityClientRecord r,
            Configuration newConfig, Configuration amOverrideConfig, int displayId,
            boolean alwaysReportChange) {
        final Activity activity = r.activity;
        ...
        final Configuration currentResConfig = activity.getResources().getConfiguration();
        final int diff = currentResConfig.diffPublicOnly(newConfig);
        final boolean hasPublicResConfigChange = diff != 0;
        // TODO(b/173090263): Use diff instead after the improvement of AssetManager and
        // ResourcesImpl constructions.
        final boolean shouldUpdateResources = hasPublicResConfigChange
                || shouldUpdateResources(activityToken, currentResConfig, newConfig,
                amOverrideConfig, movedToDifferentDisplay, hasPublicResConfigChange);
        final boolean shouldReportChange = shouldReportChange(
                activity.mCurrentConfig, newConfig, r.mSizeConfigurations,
                activity.mActivityInfo.getRealConfigChanged(), alwaysReportChange);
        // Nothing significant, don't proceed with updating and reporting.
        if (!shouldUpdateResources && !shouldReportChange) {
            return null;
        }
        ...
        final Configuration configToReport = createNewConfigAndUpdateIfNotNull(newConfig,
                contextThemeWrapperOverrideConfig);
        ...
        activity.mConfigChangeFlags = 0;
        if (shouldReportChange) {
            activity.mCalled = false;
            activity.mCurrentConfig = new Configuration(newConfig);
            activity.onConfigurationChanged(configToReport);  // 1
            if (!activity.mCalled) {
                throw new SuperNotCalledException("Activity " + activity.getLocalClassName() +
                                " did not call through to super.onConfigurationChanged()");
            }
        }
    
        return configToReport;
    }

    注释1处调用 Activity.onConfigurationChanged()

总结:设备配置发生变更时,系统会根据一系列条件决定是重启 Activity,还是调用 Activity.onConfigurationChanged()

相关推荐
h7ml2 小时前
于 CompletableFuture 的异步编排优化企业微信通知发送性能
android·windows·企业微信
子木鑫2 小时前
[SUCTF 2019] CheckIn1 — 利用 .user.ini 与图片马构造 PHP 后门并绕过上传检测
android·开发语言·安全·php
风清云淡_A2 小时前
【ANDROID】使用webview实现加载第三方的网页效果
android
吴声子夜歌2 小时前
RxJava——操作符详解(四)
android·echarts·rxjava
我是阿亮啊3 小时前
Android Handler 消息机制之 Looper 深度解析
android·loop·handler·looper
Mr YiRan3 小时前
Android 16KB 腾讯Mars XLog适配
android
2501_915921433 小时前
不用 Xcode 上架 iOS,拆分流程多工具协作完成 iOS 应用的发布准备与提交流程
android·macos·ios·小程序·uni-app·iphone·xcode
子木鑫3 小时前
[SUCTF2019 & GXYCTF2019] 文件上传绕过实战:图片马 + .user.ini / .htaccess 构造 PHP 后门
android·开发语言·安全·php
一起养小猫3 小时前
Flutter for OpenHarmony 实战:打造功能完整的记账助手应用
android·前端·flutter·游戏·harmonyos