Configuration Change is dispatched to the App process

Overall timing


// DisplayContent.java
boolean updateDisplayOverrideConfigurationLocked(Configuration values,
        ActivityRecord starting, boolean deferResume,
        ActivityTaskManagerService.UpdateConfigurationResult result) {<!-- -->

    int changes = 0;
    boolean kept = true;

    mAtmService.deferWindowLayout();
    try {<!-- -->
        if (values != null) {<!-- -->
            if (mDisplayId == DEFAULT_DISPLAY) {<!-- -->
                // First trigger process-related Configuration changes
                changes = mAtmService.updateGlobalConfigurationLocked(values,
                        false /* initLocale */, false /* persistent */,
                        UserHandle.USER_NULL /* userId */);
            } else {<!-- -->
                changes = performDisplayOverrideConfigUpdate(values);
            }
        }

        if (!deferResume) {<!-- -->
            // Then trigger changes in Activity-related Configuration
            kept = mAtmService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
        }
    } finally {<!-- -->
        mAtmService.continueWindowLayout();
    }
    .....
}

Process level ConfigurationChangeItem

The main purpose is to update the Configuration in the Resource of the app process and trigger the onConfigurationChanged callback of Application/Service/Provider, etc.

System server

// ATMS.java
int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale,
        boolean persistent, int userId) {<!-- -->

    ...
    Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + mTempConfig);
   ...
    SparseArray<WindowProcessController> pidMap = mProcessMap.getPidMap();
    for (int i = pidMap.size() - 1; i >= 0; i--) {<!-- -->
        final int pid = pidMap.keyAt(i);
        final WindowProcessController app = pidMap.get(pid);
        ProtoLog.v(WM_DEBUG_CONFIGURATION, "Update process config of %s to new "
                 + "config %s", app.mName, mTempConfig);
        app.onConfigurationChanged(mTempConfig);
    }
    ...
}

// WindowProcessController.java
private void scheduleConfigurationChange(IApplicationThread thread, Configuration config) {<!-- -->
    ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending to proc %s new config %s", mName,
            config);
    ...
    mHasCachedConfiguration = false;
    try {<!-- -->
        mAtm.getLifecycleManager().scheduleTransaction(thread,
                ConfigurationChangeItem.obtain(config, mLastTopActivityDeviceId));
    } catch (Exception e) {<!-- -->
        Slog.e(TAG_CONFIGURATION, "Failed to schedule configuration change: " + mOwner, e);
    }
}

App

// ConfigurationController.java
void handleConfigurationChanged(@Nullable Configuration config,
        @Nullable CompatibilityInfo compat) {<!-- -->
   ...
    synchronized (mResourcesManager) {<!-- -->
        ...
        //First update the value of Configuration in Resource
        mResourcesManager.applyConfigurationToResources(config, compat);
        ...
    }
    final ArrayList<ComponentCallbacks2> callbacks =
            mActivityThread.collectComponentCallbacks(false /* includeUiContexts */);

    freeTextLayoutCachesIfNeeded(configDiff);

    if (callbacks != null) {<!-- -->
        final int size = callbacks.size();
        for (int i = 0; i < size; i + + ) {<!-- -->
            ComponentCallbacks2 cb = callbacks.get(i);
            if (!equivalent) {<!-- -->
                // Then trigger the callback of Application/Service/Provider, etc.
                performConfigurationChanged(cb, config);
            }
        }
    }
}

Activity level

Two ways of processing

For activity-level processing, only one of relaunching the current Activity or triggering the current Actiivty callback onConfigurationChanged method will occur.

  • When the app configures the corresponding configChanges in AndroidManifest.xml, it triggers the Activity callback onConfigurationChanged method.
  • Otherwise, perform the relaunch operation directly
ActivityRelaunchItem

If the app does not configure the corresponding configChanges in AndroidManifest.xml, the relaunch of the current top page will be triggered when the configuration changes. The corresponding event log is as follows:

10-10 16:12:05.951 3972 8346 I configuration_changed: 512
10-10 16:12:06.021 3972 8346 I wm_relaunch_resume_activity: [0,237413577,21,com.miui.gallery/.activity.MapActivity,200]
10-10 16:12:06.022 3972 8346 I wm_relaunch_activity: [0,218943334,21,com.miui.gallery/.activity.HomePageActivity,200]
// The page of top resume still stays in the onResume state after reconstruction.
10-10 16:12:06.244 20930 20930 I wm_on_top_resumed_lost_called: [237413577,com.miui.gallery.activity.MapActivity,pausing]
10-10 16:12:06.284 20930 20930 I wm_on_paused_called: [0,237413577,com.miui.gallery.activity.MapActivity,performPause,39]
10-10 16:12:06.294 20930 20930 I wm_on_stop_called: [0,237413577,com.miui.gallery.activity.MapActivity,handleRelaunchActivity,2]
10-10 16:12:06.328 20930 20930 I wm_on_destroy_called: [0,237413577,com.miui.gallery.activity.MapActivity,performDestroy,32]
10-10 16:12:06.461 20930 20930 I wm_on_create_called: [0,237413577,com.miui.gallery.activity.MapActivity,performCreate,29]
10-10 16:12:06.599 20930 20930 I wm_on_start_called: [0,237413577,com.miui.gallery.activity.MapActivity,handleStartActivity,135]
10-10 16:12:06.603 20930 20930 I wm_on_resume_called: [0,237413577,com.miui.gallery.activity.MapActivity,RESUME_ACTIVITY,2]
10-10 16:12:06.603 20930 20930 I wm_on_top_resumed_gained_called: [237413577,com.miui.gallery.activity.MapActivity,topWhenResuming]

// Visible but the final state of the pause page is still onPause after reconstruction
10-10 16:12:06.625 20930 20930 I wm_on_stop_called: [0,218943334,com.miui.gallery.activity.HomePageActivity,handleRelaunchActivity,6]
10-10 16:12:06.649 20930 20930 I wm_on_destroy_called: [0,218943334,com.miui.gallery.activity.HomePageActivity,performDestroy,13]
10-10 16:12:06.701 20930 20930 I wm_on_create_called: [0,218943334,com.miui.gallery.activity.HomePageActivity,performCreate,34]
10-10 16:12:06.903 20930 20930 I wm_on_start_called: [0,218943334,com.miui.gallery.activity.HomePageActivity,handleStartActivity,199]
10-10 16:12:06.910 20930 20930 I wm_on_resume_called: [0,218943334,com.miui.gallery.activity.HomePageActivity,LIFECYCLER_RESUME_ACTIVITY,5]
10-10 16:12:06.926 20930 20930 I wm_on_paused_called: [0,218943334,com.miui.gallery.activity.HomePageActivity,performPause,1]

Send a ClientTransaction to the app process:

  • If it should resume: ActivityRelaunchItem (handles Activity reconstruction) & ResumeActivityItem (handles final life cycle)
  • Otherwise: ActivityRelaunchItem & PauseActivityItem
void relaunchActivityLocked(boolean preserveWindow) {<!-- -->
    ...
    if (andResume) {<!-- -->
        EventLogTags.writeWmRelaunchResumeActivity(mUserId, System.identityHashCode(this),
                task.mTaskId, shortComponentName, Integer.toHexString(configChangeFlags));
    } else {<!-- -->
        EventLogTags.writeWmRelaunchActivity(mUserId, System.identityHashCode(this),
                task.mTaskId, shortComponentName, Integer.toHexString(configChangeFlags));
    }

    startFreezingScreenLocked(0);

    try {<!-- -->
       ...
        final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(pendingResults,
                pendingNewIntents, configChangeFlags,
                new MergedConfiguration(getProcessGlobalConfiguration(),
                        getMergedOverrideConfiguration()),
                preserveWindow);
        final ActivityLifecycleItem lifecycleItem;
        if (andResume) {<!-- -->
            lifecycleItem = ResumeActivityItem.obtain(isTransitionForward(),
                    shouldSendCompatFakeFocus());
        } else {<!-- -->
            lifecycleItem = PauseActivityItem.obtain();
        }
        final ClientTransaction transaction = ClientTransaction.obtain(app.getThread(), token);
        transaction.addCallback(callbackItem);
        transaction.setLifecycleStateRequest(lifecycleItem);
        mAtmService.getLifecycleManager().scheduleTransaction(transaction);
    } catch (RemoteException e) {<!-- -->
        ProtoLog.i(WM_DEBUG_STATES, "Relaunch failed %s", e);
    }
   ...
}

Then execute the corresponding life cycle according to ResumeActivityItem and PauseActivityItem. Generally, the top Activity executes onResume, and the bottom one executes onPause.

ActivityConfigurationChangeItem

If the corresponding configChanges is not configured in the function manifest file, the current Activity of the app will be triggered to execute the onConfigurationChanged method, which mainly posts an ActivityConfigurationChangeItem to the app process (different from the process-level ConfigurationChangeItem).

Update top Activity’s Configuration

10-10 16:12:05.951 3972 8346 I configuration_changed: 512
10-10 16:12:06.021 3972 8346 I wm_relaunch_resume_activity: [0,237413577,21,com.miui.gallery/.activity.MapActivity,200]
// The page of top resume still stays in the onResume state after reconstruction.
10-10 16:12:06.244 20930 20930 I wm_on_top_resumed_lost_called: [237413577,com.miui.gallery.activity.MapActivity,pausing]
10-10 16:12:06.284 20930 20930 I wm_on_paused_called: [0,237413577,com.miui.gallery.activity.MapActivity,performPause,39]
10-10 16:12:06.294 20930 20930 I wm_on_stop_called: [0,237413577,com.miui.gallery.activity.MapActivity,handleRelaunchActivity,2]
10-10 16:12:06.328 20930 20930 I wm_on_destroy_called: [0,237413577,com.miui.gallery.activity.MapActivity,performDestroy,32]
10-10 16:12:06.461 20930 20930 I wm_on_create_called: [0,237413577,com.miui.gallery.activity.MapActivity,performCreate,29]
10-10 16:12:06.599 20930 20930 I wm_on_start_called: [0,237413577,com.miui.gallery.activity.MapActivity,handleStartActivity,135]
10-10 16:12:06.603 20930 20930 I wm_on_resume_called: [0,237413577,com.miui.gallery.activity.MapActivity,RESUME_ACTIVITY,2]
10-10 16:12:06.603 20930 20930 I wm_on_top_resumed_gained_called: [237413577,com.miui.gallery.activity.MapActivity,topWhenResuming]
// ATMS.java
/** Applies latest configuration and/or visibility updates if needed. */
boolean ensureConfigAndVisibilityAfterUpdate(ActivityRecord starting, int changes) {<!-- -->
    boolean kept = true;
    final Task mainRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
    // mainRootTask is null during startup.
    if (mainRootTask != null) {<!-- -->
        if (changes != 0 & amp; & amp; starting == null) {<!-- -->
            // If the configuration changed, and the caller is not already
            // in the process of starting an activity, then find the top
            // activity to check if its configuration needs to change.
            starting = mainRootTask.topRunningActivity();
        }

        if (starting != null) {<!-- -->
            //Update the configuration of the top running page
            kept = starting.ensureActivityConfiguration(changes,
                    false /* preserveWindow */);
            // And we need to make sure at this point that all other activities
            // are made visible with the correct configuration.
            // Update the configuration of other visible activities
            mRootWindowContainer.ensureActivitiesVisible(starting, changes,
                    !PRESERVE_WINDOWS);
        }
    }

    return kept;
}

Update the Configuration of other visible activities

// EnsureActivitiesVisibleHelper.java
private void setActivityVisibilityState(ActivityRecord r, ActivityRecord starting,
        final boolean resumeTopActivity) {<!-- -->
    .....
    final boolean reallyVisible = r.shouldBeVisibleUnchecked();
    ...
    // If visible and not top
    if (reallyVisible) {<!-- -->
       .....
        // First: if this is not the current activity being started, make
        // sure it matches the current configuration.
        if (r != mStarting & amp; & amp; mNotifyClients) {<!-- -->
            r.ensureActivityConfiguration(0 /* globalChanges */, mPreserveWindows,
              true /* ignoreVisibility */);
        }
10-10 16:12:05.951 3972 8346 I configuration_changed: 512
10-10 16:12:06.022 3972 8346 I wm_relaunch_activity: [0,218943334,21,com.miui.gallery/.activity.HomePageActivity,200]
// Visible but the final state of the pause page is still onPause after reconstruction
10-10 16:12:06.625 20930 20930 I wm_on_stop_called: [0,218943334,com.miui.gallery.activity.HomePageActivity,handleRelaunchActivity,6]
10-10 16:12:06.649 20930 20930 I wm_on_destroy_called: [0,218943334,com.miui.gallery.activity.HomePageActivity,performDestroy,13]
10-10 16:12:06.701 20930 20930 I wm_on_create_called: [0,218943334,com.miui.gallery.activity.HomePageActivity,performCreate,34]
10-10 16:12:06.903 20930 20930 I wm_on_start_called: [0,218943334,com.miui.gallery.activity.HomePageActivity,handleStartActivity,199]
10-10 16:12:06.910 20930 20930 I wm_on_resume_called: [0,218943334,com.miui.gallery.activity.HomePageActivity,LIFECYCLER_RESUME_ACTIVITY,5]
10-10 16:12:06.926 20930 20930 I wm_on_paused_called: [0,218943334,com.miui.gallery.activity.HomePageActivity,performPause,1]

Update the Configuration of other invisible activities

Other activities will trigger relaunch the next time they resume, and are not executed in the same thread and at the same time as the above two. If you want to find the specific reason for relaunch, you need to look forward in time to find the corresponding configuration_changed log. For details, refer to https://blog .csdn.net/xiaoyantan/article/details/126292133

10-11 10:58:57.071 3998 11823 I wm_set_resumed_activity: [0,com.miui.gallery/.activity.HomePageActivity,resumeTopActivity - onActivityStateChanged]
10-11 10:58:57.074 3998 11823 I wm_relaunch_resume_activity: [0,197612339,22,com.miui.gallery/.activity.HomePageActivity,200]