本文描述从启动一个新进程的Activity起,Framwork层Configuration的创建和传导过程。
首先,我们知道所有的Window容器都继承于WindowContainer,而WindowContainer本身是ConfigurationContainer的子类。于此同时,WindowProcessController也是ConfigurationContainer的子类。ConfigurationContainer可认为是可以对Window、进程进行Configuration配置的基本载体和单位。
我们从启动一个Activity的角度出发看Configuration的传导过程:
启动Activity过程中会调用ActivityTaskSupervisor的realStartActivityLocked方法:
platform/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskSupervisor.javaboolean realStartActivityLocked(ActivityRecord r, WindowProcessController proc,
boolean andResume, boolean checkConfig) throws RemoteException {...if (checkConfig) {
// Deferring resume here because we're going to launch new activity shortly.
// We don't want to perform a redundant launch of the same record while ensuring
// configurations and trying to resume top activity of focused root task.
mRootWindowContainer.ensureVisibilityAndConfig(r, r.getDisplayId(),
false /* markFrozenIfConfigChanged */, true /* deferResume */);
}...
}
在Activity启动的场景,checkConfig为true。
看ensureVisibilityAndConfig:
platform/frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.javaboolean ensureVisibilityAndConfig(ActivityRecord starting, int displayId,
boolean markFrozenIfConfigChanged, boolean deferResume) {
// First ensure visibility without updating the config just yet. We need this to know what
// activities are affecting configuration now.
// Passing null here for 'starting' param value, so that visibility of actual starting
// activity will be properly updated.
ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
false /* preserveWindows */, false /* notifyClients */); if (displayId == INVALID_DISPLAY) {
// The caller didn't provide a valid display id, skip updating config.
return true;
} // Force-update the orientation from the WindowManager, since we need the true configuration
// to send to the client now.
final DisplayContent displayContent = getDisplayContent(displayId);
Configuration config = null;
if (displayContent != null) {
config = displayContent.updateOrientation(starting, true /* forceUpdate */);
}
// Visibilities may change so let the starting activity have a chance to report. Can't do it
// when visibility is changed in each AppWindowToken because it may trigger wrong
// configuration push because the visibility of some activities may not be updated yet.
if (starting != null) {
starting.reportDescendantOrientationChangeIfNeeded();
}
if (starting != null && markFrozenIfConfigChanged && config != null) {
starting.frozenBeforeDestroy = true;
} if (displayContent != null) {
// Update the configuration of the activities on the display.
return displayContent.updateDisplayOverrideConfigurationLocked(config, starting,
deferResume, null /* result */);
} else {
return true;
}
}
调用了DisplayContent的updateOrientation,注意,这里第二个参数forceUpdate为true,强制更新。
platform/frameworks/base/services/core/java/com/android/server/wm/DisplayContent.javaConfiguration updateOrientation(WindowContainer<?> freezeDisplayWindow, boolean forceUpdate) {
if (!mDisplayReady) {
return null;
} Configuration config = null;
if (updateOrientation(forceUpdate)) {
// If we changed the orientation but mOrientationChangeComplete is already true,
// we used seamless rotation, and we don't need to freeze the screen.
if (freezeDisplayWindow != null && !mWmService.mRoot.mOrientationChangeComplete) {
final ActivityRecord activity = freezeDisplayWindow.asActivityRecord();
if (activity != null && activity.mayFreezeScreenLocked()) {
activity.startFreezingScreen();
}
}
config = new Configuration();
computeScreenConfiguration(config);
} return config;
}
如果需要更新Orientation,则会创建new Configuration(),并调用computeScreenConfiguration。
platform/frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java/**
* Compute display configuration based on display properties and policy settings.
* Do not call if mDisplayReady == false.
*/
void computeScreenConfiguration(Configuration config) {
final DisplayInfo displayInfo = updateDisplayAndOrientation(config);
final int dw = displayInfo.logicalWidth;
final int dh = displayInfo.logicalHeight;
mTmpRect.set(0, 0, dw, dh);
config.windowConfiguration.setBounds(mTmpRect);
config.windowConfiguration.setMaxBounds(mTmpRect);
config.windowConfiguration.setWindowingMode(getWindowingMode());
config.windowConfiguration.setDisplayWindowingMode(getWindowingMode()); computeScreenAppConfiguration(config, dw, dh, displayInfo.rotation); config.screenLayout = (config.screenLayout & ~Configuration.SCREENLAYOUT_ROUND_MASK)
| ((displayInfo.flags & Display.FLAG_ROUND) != 0
? Configuration.SCREENLAYOUT_ROUND_YES
: Configuration.SCREENLAYOUT_ROUND_NO); config.densityDpi = displayInfo.logicalDensityDpi; config.colorMode =
((displayInfo.isHdr() && mWmService.hasHdrSupport())
? Configuration.COLOR_MODE_HDR_YES
: Configuration.COLOR_MODE_HDR_NO)
| (displayInfo.isWideColorGamut() && mWmService.hasWideColorGamutSupport()
? Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_YES
: Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO); // Update the configuration based on available input devices, lid switch,
// and platform configuration.
config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
config.keyboard = Configuration.KEYBOARD_NOKEYS;
config.navigation = Configuration.NAVIGATION_NONAV; int keyboardPresence = 0;
int navigationPresence = 0;
final InputDevice[] devices = mWmService.mInputManager.getInputDevices();
final int len = devices != null ? devices.length : 0;
for (int i = 0; i < len; i++) {
InputDevice device = devices[i];
// Ignore virtual input device.
if (device.isVirtual()) {
continue;
} // Check if input device can dispatch events to current display.
if (!mWmService.mInputManager.canDispatchToDisplay(device.getId(), mDisplayId)) {
continue;
} final int sources = device.getSources();
final int presenceFlag = device.isExternal()
? WindowManagerPolicy.PRESENCE_EXTERNAL : WindowManagerPolicy.PRESENCE_INTERNAL; if (mWmService.mIsTouchDevice) {
if ((sources & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN) {
config.touchscreen = Configuration.TOUCHSCREEN_FINGER;
}
} else {
config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
} if ((sources & InputDevice.SOURCE_TRACKBALL) == InputDevice.SOURCE_TRACKBALL) {
config.navigation = Configuration.NAVIGATION_TRACKBALL;
navigationPresence |= presenceFlag;
} else if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD
&& config.navigation == Configuration.NAVIGATION_NONAV) {
config.navigation = Configuration.NAVIGATION_DPAD;
navigationPresence |= presenceFlag;
} if (device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
config.keyboard = Configuration.KEYBOARD_QWERTY;
keyboardPresence |= presenceFlag;
}
} if (config.navigation == Configuration.NAVIGATION_NONAV && mWmService.mHasPermanentDpad) {
config.navigation = Configuration.NAVIGATION_DPAD;
navigationPresence |= WindowManagerPolicy.PRESENCE_INTERNAL;
} // Determine whether a hard keyboard is available and enabled.
// TODO(multi-display): Should the hardware keyboard be tied to a display or to a device?
boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS;
if (hardKeyboardAvailable != mWmService.mHardKeyboardAvailable) {
mWmService.mHardKeyboardAvailable = hardKeyboardAvailable;
mWmService.mH.removeMessages(REPORT_HARD_KEYBOARD_STATUS_CHANGE);
mWmService.mH.sendEmptyMessage(REPORT_HARD_KEYBOARD_STATUS_CHANGE);
} mDisplayPolicy.updateConfigurationAndScreenSizeDependentBehaviors(); // Let the policy update hidden states.
config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO;
config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO;
config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO;
mWmService.mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence);
}
computeScreenAppConfiguration用来更新屏幕尺寸、密度、rotation等:
platform/frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java/** Compute configuration related to application without changing current display. */
private void computeScreenAppConfiguration(Configuration outConfig, int dw, int dh,
int rotation) {
final DisplayPolicy.DecorInsets.Info info =
mDisplayPolicy.getDecorInsetsInfo(rotation, dw, dh);
// AppBounds at the root level should mirror the app screen size.
outConfig.windowConfiguration.setAppBounds(info.mNonDecorFrame);
outConfig.windowConfiguration.setRotation(rotation);
outConfig.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; final float density = mDisplayMetrics.density;
outConfig.screenWidthDp = (int) (info.mConfigFrame.width() / density + 0.5f);
outConfig.screenHeightDp = (int) (info.mConfigFrame.height() / density + 0.5f);
outConfig.compatScreenWidthDp = (int) (outConfig.screenWidthDp / mCompatibleScreenScale);
outConfig.compatScreenHeightDp = (int) (outConfig.screenHeightDp / mCompatibleScreenScale);
outConfig.screenLayout = computeScreenLayout(
Configuration.resetScreenLayout(outConfig.screenLayout),
outConfig.screenWidthDp, outConfig.screenHeightDp); final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
outConfig.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, dw, dh);
outConfig.windowConfiguration.setDisplayRotation(rotation);
}
回到ensureVisibilityAndConfig, 获取了Configuration后,调用displayContent.updateDisplayOverrideConfigurationLocked
platform/frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java/**
* Updates override configuration specific for the selected display. If no config is provided,
* new one will be computed in WM based on current display info.
*/
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) {
// Override configuration of the default display duplicates global config, so
// we're calling global config update instead for default display. It will also
// apply the correct override config.
changes = mAtmService.updateGlobalConfigurationLocked(values,
false /* initLocale */, false /* persistent */,
UserHandle.USER_NULL /* userId */);
} else {
changes = performDisplayOverrideConfigUpdate(values);
}
} if (!deferResume) {
kept = mAtmService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
}
} finally {
mAtmService.continueWindowLayout();
} if (result != null) {
result.changes = changes;
result.activityRelaunched = !kept;
}
return kept;
}
以单个屏幕的场景,mDisplayId == DEFAULT_DISPLAY,调用mAtmService.updateGlobalConfigurationLocked,
platform/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java/** Update default (global) configuration and notify listeners about changes. */
int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale,
boolean persistent, int userId) { mTempConfig.setTo(getGlobalConfiguration());
final int changes = mTempConfig.updateFrom(values);
if (changes == 0) {
return 0;
} ... // Note: certain tests currently run as platform_app which is not allowed
// to set debug system properties. To ensure that system properties are set
// only when allowed, we check the current UID.
if (Process.myUid() == Process.SYSTEM_UID) {
if (values.mcc != 0) {
SystemProperties.set("debug.tracing.mcc", Integer.toString(values.mcc));
}
if (values.mnc != 0) {
SystemProperties.set("debug.tracing.mnc", Integer.toString(values.mnc));
}
} if (!initLocale && !values.getLocales().isEmpty() && values.userSetLocale) {
final LocaleList locales = values.getLocales();
int bestLocaleIndex = 0;
if (locales.size() > 1) {
if (mSupportedSystemLocales == null) {
mSupportedSystemLocales = Resources.getSystem().getAssets().getLocales();
}
bestLocaleIndex = Math.max(0, locales.getFirstMatchIndex(mSupportedSystemLocales));
}
SystemProperties.set("persist.sys.locale",
locales.get(bestLocaleIndex).toLanguageTag());
LocaleList.setDefault(locales, bestLocaleIndex);
} mTempConfig.seq = increaseConfigurationSeqLocked(); Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + mTempConfig);
// TODO(multi-display): Update UsageEvents#Event to include displayId.
mUsageStatsInternal.reportConfigurationChange(mTempConfig, mAmInternal.getCurrentUserId()); // TODO: If our config changes, should we auto dismiss any currently showing dialogs?
updateShouldShowDialogsLocked(mTempConfig); AttributeCache ac = AttributeCache.instance();
if (ac != null) {
ac.updateConfiguration(mTempConfig);
} // Make sure all resources in our process are updated right now, so that anyone who is going
// to retrieve resource values after we return will be sure to get the new ones. This is
// especially important during boot, where the first config change needs to guarantee all
// resources have that config before following boot code is executed.
mSystemThread.applyConfigurationToResources(mTempConfig); if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {
final Message msg = PooledLambda.obtainMessage(
ActivityTaskManagerService::sendPutConfigurationForUserMsg,
this, userId, new Configuration(mTempConfig));
mH.sendMessage(msg);
} 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);
} final Message msg = PooledLambda.obtainMessage(
ActivityManagerInternal::broadcastGlobalConfigurationChanged,
mAmInternal, changes, initLocale);
mH.sendMessage(msg); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RootConfigChange");
// Update stored global config and notify everyone about the change.
mRootWindowContainer.onConfigurationChanged(mTempConfig);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return changes;
}
调用所用进程对应的WindowProcessController的onConfigurationChanged方法, 这个方法最终会通知到客户端应用的所有Activity、Service等。随后通过broadcastGlobalConfigurationChanged发送ACTION_CONFIGURATION_CHANGED广播,最后调用mRootWindowContainer.onConfigurationChanged(mTempConfig);,这个是通知服务端从RootWindowContainer及其所有子窗口进行configuration更新。
客户端的传递路线参考序列图。最终会调用到ConfigurationController的handleConfigurationChanged:
platform/frameworks/base/core/java/android/app/ConfigurationController.java/**
* Update the configuration to latest.
* @param config The new configuration.
* @param compat The new compatibility information.
*/
void handleConfigurationChanged(@Nullable Configuration config,
@Nullable CompatibilityInfo compat) {
int configDiff;
boolean equivalent; // Get theme outside of synchronization to avoid nested lock.
final Resources.Theme systemTheme = mActivityThread.getSystemContext().getTheme();
final ContextImpl systemUiContext = mActivityThread.getSystemUiContextNoCreate();
final Resources.Theme systemUiTheme =
systemUiContext != null ? systemUiContext.getTheme() : null;
synchronized (mResourcesManager) {
if (mPendingConfiguration != null) {
if (!mPendingConfiguration.isOtherSeqNewer(config)) {
config = mPendingConfiguration;
updateDefaultDensity(config.densityDpi);
}
mPendingConfiguration = null;
} if (config == null) {
return;
} // This flag tracks whether the new configuration is fundamentally equivalent to the
// existing configuration. This is necessary to determine whether non-activity callbacks
// should receive notice when the only changes are related to non-public fields.
// We do not gate calling {@link #performActivityConfigurationChanged} based on this
// flag as that method uses the same check on the activity config override as well.
equivalent = mConfiguration != null && (0 == mConfiguration.diffPublicOnly(config)); if (DEBUG_CONFIGURATION) {
Slog.v(TAG, "Handle configuration changed: " + config);
} final Application app = mActivityThread.getApplication();
final Resources appResources = app.getResources();
mResourcesManager.applyConfigurationToResources(config, compat);
updateLocaleListFromAppContext(app.getApplicationContext()); if (mConfiguration == null) {
mConfiguration = new Configuration();
}
if (!mConfiguration.isOtherSeqNewer(config) && compat == null) {
return;
} configDiff = mConfiguration.updateFrom(config);
config = applyCompatConfiguration();
HardwareRenderer.sendDeviceConfigurationForDebugging(config); if ((systemTheme.getChangingConfigurations() & configDiff) != 0) {
systemTheme.rebase();
} if (systemUiTheme != null
&& (systemUiTheme.getChangingConfigurations() & configDiff) != 0) {
systemUiTheme.rebase();
}
} 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) {
performConfigurationChanged(cb, config);
}
}
}
}
mResourcesManager.applyConfigurationToResources(config, compat);将config更新到resourceManager;
configDiff = mConfiguration.updateFrom(config);、config = applyCompatConfiguration();更新本地config,让后通过performConfigurationChanged传导给所有Acivity、Service等。
无论是WindowProcessController的onConfigurationChanged还是mRootWindowContainer.onConfigurationChanged, 都会首先执行基类ConfigurationContainer的onConfigurationChanged:
platform/frameworks/base/services/core/java/com/android/server/wm/ConfigurationContainer.java/**
* Notify that parent config changed and we need to update full configuration.
* @see #mFullConfiguration
*/
public void onConfigurationChanged(Configuration newParentConfig) {
mResolvedTmpConfig.setTo(mResolvedOverrideConfiguration);
resolveOverrideConfiguration(newParentConfig);
mFullConfiguration.setTo(newParentConfig);
// Do not inherit always-on-top property from parent, otherwise the always-on-top
// property is propagated to all children. In that case, newly added child is
// always being positioned at bottom (behind the always-on-top siblings).
mFullConfiguration.windowConfiguration.unsetAlwaysOnTop();
mFullConfiguration.updateFrom(mResolvedOverrideConfiguration);
onMergedOverrideConfigurationChanged();
if (!mResolvedTmpConfig.equals(mResolvedOverrideConfiguration)) {
// This depends on the assumption that change-listeners don't do
// their own override resolution. This way, dependent hierarchies
// can stay properly synced-up with a primary hierarchy's constraints.
// Since the hierarchies will be merged, this whole thing will go away
// before the assumption will be broken.
// Inform listeners of the change.
for (int i = mChangeListeners.size() - 1; i >= 0; --i) {
mChangeListeners.get(i).onRequestedOverrideConfigurationChanged(
mResolvedOverrideConfiguration);
}
}
for (int i = mChangeListeners.size() - 1; i >= 0; --i) {
mChangeListeners.get(i).onMergedOverrideConfigurationChanged(
mMergedOverrideConfiguration);
}
for (int i = getChildCount() - 1; i >= 0; --i) {
dispatchConfigurationToChild(getChildAt(i), mFullConfiguration);
}
}
这个方法会对Configuration相关的一些重要属性进行更新。暂不详述。