当我们在系统设置-更多设置-语言和输入法中点击语言,在其中选择一种新的语言后,系统在框架层做了什么?带着这个问题,我们去看下系统的源码
ConfigurationChange Flow

修改系统configuration(包括语言、屏幕方向等)之后,会引起已经启动运行的进程的一系列变化。
打开本地路径:D:\android\sdk\sources\android-26\com\android\internal\app,其中类LocalePicker是原生系统切换语言的一个Fragment,然后看一下updateLocales(LocaleList locales) 。
/**
* 请求系统更新locale列表,需要注意,当改变Locale时系统看起来会停顿一下
*/
public static void updateLocales(LocaleList locales) {
try {
final IActivityManager am = ActivityManager.getService();
final Configuration config = am.getConfiguration();
config.setLocales(locales);
config.userSetLocale = true;
am.updatePersistentConfiguration(config);
// Trigger the dirty bit for the Settings Provider.
BackupManager.dataChanged("com.android.providers.settings");
} catch (RemoteException e) {
// Intentionally left blank
}
}
只要本地locale改变便会调用该方法。ActivityManager.getService()返回的是远程服务对象ActivityManagerService.java在本地的一个代理,最终调用的是ActivityManagerService.java中的updatePersistentConfiguration()方法。
@Override
public void updatePersistentConfiguration(Configuration values) {
enforceCallingPermission(CHANGE_CONFIGURATION, "updatePersistentConfiguration()");
enforceWriteSettingsPermission("updatePersistentConfiguration()");
if (values == null) {
throw new NullPointerException("Configuration must not be null");
}
int userId = UserHandle.getCallingUserId();
synchronized(this) {
updatePersistentConfigurationLocked(values, userId);
}
}
这个方法首先检查权限,然后同步地调用updatePersistentConfigurationLocked方法。
private void updatePersistentConfigurationLocked(Configuration values, @UserIdInt int userId) {
final long origId = Binder.clearCallingIdentity();
try {
updateConfigurationLocked(values, null, false, true, userId, false /* deferResume */);
} finally {
Binder.restoreCallingIdentity(origId);
}
}
private boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
boolean initLocale, boolean persistent, int userId, boolean deferResume) {
return updateConfigurationLocked(values, starting, initLocale, persistent, userId,
deferResume, null /* result */);
}
/**
* Do either or both things: (1) change the current configuration, and (2)
* make sure the given activity is running with the (now) current
* configuration. Returns true if the activity has been left running, or
* false if <var>starting</var> is being destroyed to match the new
* configuration.
*
* @param userId is only used when persistent parameter is set to true to persist configuration
* for that particular user
*/
private boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
boolean initLocale, boolean persistent, int userId, boolean deferResume,
UpdateConfigurationResult result) {
int changes = 0;
boolean kept = true;
if (mWindowManager != null) {
mWindowManager.deferSurfaceLayout();
}
try {
if (values != null) {
changes = updateGlobalConfiguration(values, initLocale, persistent, userId,
deferResume);
}
kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
} finally {
if (mWindowManager != null) {
mWindowManager.continueSurfaceLayout();
}
}
if (result != null) {
result.changes = changes;
result.activityRelaunched = !kept;
}
return kept;
}
由上面的updateConfigurationLocked()的注释开始看起,函数做了以下两件事情或者至少其中一个方面。
- 改变当前的configuration。
- 确保给定的activity正在运行当前的configuration。(这点是关键) 。
我们按照这个思路看看android是如何更新configuration. 查看代码 , 首先可以看到这个函数首先判断values是否为空, 这里values肯定不为空的, 然后调用updateGlobalConfiguration方法。
/** Update default (global) configuration and notify listeners about changes. */
private int updateGlobalConfiguration(@NonNull Configuration values, boolean initLocale,
boolean persistent, int userId, boolean deferResume) {
mTempConfig.setTo(getGlobalConfiguration());
final int changes = mTempConfig.updateFrom(values);
if (changes == 0) {
// 由于调用Activity.setRequestedOrientation会导致window被冻结以及WindowManagerService.mWaitingForConfig被设置为true,
// 所以调用performDisplayOverrideConfigUpdate为了发送新的配置事件(即便是什么都没有改变) 去解冻window是重要的.
performDisplayOverrideConfigUpdate(values, deferResume, DEFAULT_DISPLAY);
return 0;
}
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.i(TAG_CONFIGURATION,
"Updating global configuration to: " + values);
EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes);
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);
mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG,
locales.get(bestLocaleIndex)));
}
mConfigurationSeq = Math.max(++mConfigurationSeq, 1);
mTempConfig.seq = mConfigurationSeq;
// Update stored global config and notify everyone about the change.
mStackSupervisor.onConfigurationChanged(mTempConfig);
Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + mTempConfig);
// TODO(multi-display): Update UsageEvents#Event to include displayId.
mUsageStatsService.reportConfigurationChange(mTempConfig,
mUserController.getCurrentUserIdLocked());
// TODO: If our config changes, should we auto dismiss any currently showing dialogs?
mShowDialogs = shouldShowDialogs(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);
// We need another copy of global config because we're scheduling some calls instead of
// running them in place. We need to be sure that object we send will be handled unchanged.
final Configuration configCopy = new Configuration(mTempConfig);
if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {
Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);
msg.obj = configCopy;
msg.arg1 = userId;
mHandler.sendMessage(msg);
}
// 遍历最近运行的进程,分别调用其scheduleConfigurationChanged方法
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord app = mLruProcesses.get(i);
try {
if (app.thread != null) {
if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc "
+ app.processName + " new config " + configCopy);
app.thread.scheduleConfigurationChanged(configCopy);
}
} catch (Exception e) {
}
}
Intent intent = new Intent(Intent.ACTION_CONFIGURATION_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_REPLACE_PENDING
| Intent.FLAG_RECEIVER_FOREGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
UserHandle.USER_ALL);
if ((changes & ActivityInfo.CONFIG_LOCALE) != 0) {
intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
if (initLocale || !mProcessesReady) {
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
}
broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
UserHandle.USER_ALL);
}
// Override configuration of the default display duplicates global config, so we need to
// update it also. This will also notify WindowManager about changes.
performDisplayOverrideConfigUpdate(mStackSupervisor.getConfiguration(), deferResume,
DEFAULT_DISPLAY);
return changes;
}
从注释看,其作用是更新默认(全局)的configuration,并通知相关监听类。
其中如下代码,遍历了mLruProcesses变量(其中存储了最近运行的app),并调用了app.thread.scheduleConfigurationChanged(configCopy);
来通知每一个进程。app.thread类型为ApplicationThreadNative,是一个进程的实例。
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord app = mLruProcesses.get(i);
try {
if (app.thread != null) {
if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc "
+ app.processName + " new config " + configCopy);
app.thread.scheduleConfigurationChanged(configCopy);
}
} catch (Exception e) {
}
}
/**
* List of running applications, sorted by recent usage.
* The first entry in the list is the least recently used.
*/
final ArrayList<ProcessRecord> mLruProcesses = new ArrayList<ProcessRecord>();
我们接着进入scheduleConfigurationChanged这个方法
,奇怪在26的sources里没有找到ApplicationThreadNative,贴上25的
public final void scheduleConfigurationChanged(Configuration config)
throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
config.writeToParcel(data, 0);
mRemote.transact(SCHEDULE_CONFIGURATION_CHANGED_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
data.recycle();
}
通过binder,这里回调到ActivityThread内部的ApplicationThread类。接着贴,
private class ApplicationThread extends IApplicationThread.Stub {
。。。
public void scheduleConfigurationChanged(Configuration config) {
updatePendingConfiguration(config);
sendMessage(H.CONFIGURATION_CHANGED, config);
}
。。。
}
sendMessage之后,
public void handleMessage(Message msg) {
。。。
case CONFIGURATION_CHANGED:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
mCurDefaultDisplayDpi = ((Configuration)msg.obj).densityDpi;
mUpdatingSystemConfig = true;
try {
handleConfigurationChanged((Configuration) msg.obj, null);
} finally {
mUpdatingSystemConfig = false;
}
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
。。。
}
ActivityThread内部的handler收到消息,进入处理case,调用handleConfigurationChanged方法。
final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
int configDiff = 0;
synchronized (mResourcesManager) {
if (mPendingConfiguration != null) {
if (!mPendingConfiguration.isOtherSeqNewer(config)) {
config = mPendingConfiguration;
mCurDefaultDisplayDpi = config.densityDpi;
updateDefaultDensity();
}
mPendingConfiguration = null;
}
if (config == null) {
return;
}
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: "
+ config);
mResourcesManager.applyConfigurationToResourcesLocked(config, compat);
updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(),
mResourcesManager.getConfiguration().getLocales());
if (mConfiguration == null) {
mConfiguration = new Configuration();
}
if (!mConfiguration.isOtherSeqNewer(config) && compat == null) {
return;
}
configDiff = mConfiguration.updateFrom(config);
config = applyCompatConfiguration(mCurDefaultDisplayDpi);
final Theme systemTheme = getSystemContext().getTheme();
if ((systemTheme.getChangingConfigurations() & configDiff) != 0) {
systemTheme.rebase();
}
final Theme systemUiTheme = getSystemUiContext().getTheme();
if ((systemUiTheme.getChangingConfigurations() & configDiff) != 0) {
systemUiTheme.rebase();
}
}
ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(false, config);
freeTextLayoutCachesIfNeeded(configDiff);
if (callbacks != null) {
final int N = callbacks.size();
for (int i=0; i<N; i++) {
ComponentCallbacks2 cb = callbacks.get(i);
if (cb instanceof Activity) {
// If callback is an Activity - call corresponding method to consider override
// config and avoid onConfigurationChanged if it hasn't changed.
Activity a = (Activity) cb;
performConfigurationChangedForActivity(mActivities.get(a.getActivityToken()),
config);
} else {
performConfigurationChanged(cb, config);
}
}
}
}
代码ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(false, config);
中的collectComponentCallbacks方法手机了进程中所有的Application、Activity和Service实例,然后再遍历他们,分别调用其回调方法来通知其config的改变。
ArrayList<ComponentCallbacks2> collectComponentCallbacks(
boolean allActivities, Configuration newConfig) {
ArrayList<ComponentCallbacks2> callbacks
= new ArrayList<ComponentCallbacks2>();
synchronized (mResourcesManager) {
final int NAPP = mAllApplications.size();
for (int i=0; i<NAPP; i++) {
callbacks.add(mAllApplications.get(i));
}
final int NACT = mActivities.size();
for (int i=0; i<NACT; i++) {
ActivityClientRecord ar = mActivities.valueAt(i);
Activity a = ar.activity;
if (a != null) {
Configuration thisConfig = applyConfigCompatMainThread(
mCurDefaultDisplayDpi, newConfig,
ar.packageInfo.getCompatibilityInfo());
if (!ar.activity.mFinished && (allActivities || !ar.paused)) {
// If the activity is currently resumed, its configuration
// needs to change right now.
callbacks.add(a);
} else if (thisConfig != null) {
// Otherwise, we will tell it about the change
// the next time it is resumed or shown. Note that
// the activity manager may, before then, decide the
// activity needs to be destroyed to handle its new
// configuration.
if (DEBUG_CONFIGURATION) {
Slog.v(TAG, "Setting activity "
+ ar.activityInfo.name + " newConfig=" + thisConfig);
}
ar.newConfig = thisConfig;
}
}
}
final int NSVC = mServices.size();
for (int i=0; i<NSVC; i++) {
callbacks.add(mServices.valueAt(i));
}
}
synchronized (mProviderMap) {
final int NPRV = mLocalProviders.size();
for (int i=0; i<NPRV; i++) {
callbacks.add(mLocalProviders.valueAt(i).mLocalProvider);
}
}
return callbacks;
}
再回去看下边的for循环,它遍历了所有组件的回调方法。
if (callbacks != null) {
final int N = callbacks.size();
for (int i=0; i<N; i++) {
ComponentCallbacks2 cb = callbacks.get(i);
if (cb instanceof Activity) {
// 如果callback是一个activity的实例的话,考虑调用相应的方法覆盖旧的config,而避免调用onConfigurationChanged(如果它没有更改的话)。
Activity a = (Activity) cb;
performConfigurationChangedForActivity(mActivities.get(a.getActivityToken()),
config);
} else {
performConfigurationChanged(cb, config);
}
}
}
/**
* Updates the configuration for an Activity. The ActivityClientRecord's
* {@link ActivityClientRecord#overrideConfig} is used to compute the final Configuration for
* that Activity. {@link ActivityClientRecord#tmpConfig} is used as a temporary for delivering
* the updated Configuration.
* @param r ActivityClientRecord representing the Activity.
* @param newBaseConfig The new configuration to use. This may be augmented with
* {@link ActivityClientRecord#overrideConfig}.
*/
private void performConfigurationChangedForActivity(ActivityClientRecord r,
Configuration newBaseConfig) {
performConfigurationChangedForActivity(r, newBaseConfig,
r.activity.getDisplay().getDisplayId(), false /* movedToDifferentDisplay */);
}
/**
* Updates the configuration for an Activity. The ActivityClientRecord's
* {@link ActivityClientRecord#overrideConfig} is used to compute the final Configuration for
* that Activity. {@link ActivityClientRecord#tmpConfig} is used as a temporary for delivering
* the updated Configuration.
* @param r ActivityClientRecord representing the Activity.
* @param newBaseConfig The new configuration to use. This may be augmented with
* {@link ActivityClientRecord#overrideConfig}.
* @param displayId The id of the display where the Activity currently resides.
* @param movedToDifferentDisplay Indicates if the activity was moved to different display.
* @return {@link Configuration} instance sent to client, null if not sent.
*/
private Configuration performConfigurationChangedForActivity(ActivityClientRecord r,
Configuration newBaseConfig, int displayId, boolean movedToDifferentDisplay) {
r.tmpConfig.setTo(newBaseConfig);
if (r.overrideConfig != null) {
r.tmpConfig.updateFrom(r.overrideConfig);
}
final Configuration reportedConfig = performActivityConfigurationChanged(r.activity,
r.tmpConfig, r.overrideConfig, displayId, movedToDifferentDisplay);
freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
return reportedConfig;
}
/**
* Decides whether to update an Activity's configuration and whether to inform it.
* @param activity The activity to notify of configuration change.
* @param newConfig The new configuration.
* @param amOverrideConfig The override config that differentiates the Activity's configuration
* from the base global configuration. This is supplied by
* ActivityManager.
* @param displayId Id of the display where activity currently resides.
* @param movedToDifferentDisplay Indicates if the activity was moved to different display.
* @return Configuration sent to client, null if no changes and not moved to different display.
*/
private Configuration performActivityConfigurationChanged(Activity activity,
Configuration newConfig, Configuration amOverrideConfig, int displayId,
boolean movedToDifferentDisplay) {
if (activity == null) {
throw new IllegalArgumentException("No activity provided.");
}
final IBinder activityToken = activity.getActivityToken();
if (activityToken == null) {
throw new IllegalArgumentException("Activity token not set. Is the activity attached?");
}
// 判断是否应该回调onConfigurationChaned方法
boolean shouldChangeConfig = false;
// 在manifest文件中配置了configChanges的话,这里应该不为null
if (activity.mCurrentConfig == null) {
shouldChangeConfig = true;
} else {
// 如果新的config和activity当前正在运行的config一致并且override config也没有改动的话,就不要很麻烦的去调用onConfigurationChanged了,反正也没变化。
int diff = activity.mCurrentConfig.diff(newConfig);
if (diff != 0 || !mResourcesManager.isSameResourcesOverrideConfig(activityToken,
amOverrideConfig)) {
// Always send the task-level config changes. For system-level configuration, if
// this activity doesn't handle any of the config changes, then don't bother
// calling onConfigurationChanged as we're going to destroy it.
if (!mUpdatingSystemConfig
|| (~activity.mActivityInfo.getRealConfigChanged() & diff) == 0
|| !REPORT_TO_ACTIVITY) {
shouldChangeConfig = true;
}
}
}
// 是否是被显示到不同的设备上了
if (!shouldChangeConfig && !movedToDifferentDisplay) {
// 什么都不需要做,直接返回null
return null;
}
// Propagate the configuration change to ResourcesManager and Activity.
// ContextThemeWrappers may override the configuration for that context. We must check and
// apply any overrides defined.
Configuration contextThemeWrapperOverrideConfig = activity.getOverrideConfiguration();
// We only update an Activity's configuration if this is not a global configuration change.
// This must also be done before the callback, or else we violate the contract that the new
// resources are available in ComponentCallbacks2#onConfigurationChanged(Configuration).
// Also apply the ContextThemeWrapper override if necessary.
// NOTE: Make sure the configurations are not modified, as they are treated as immutable in
// many places.
final Configuration finalOverrideConfig = createNewConfigAndUpdateIfNotNull(
amOverrideConfig, contextThemeWrapperOverrideConfig);
mResourcesManager.updateResourcesForActivity(activityToken, finalOverrideConfig,
displayId, movedToDifferentDisplay);
activity.mConfigChangeFlags = 0;
activity.mCurrentConfig = new Configuration(newConfig);
// Apply the ContextThemeWrapper override if necessary.
// NOTE: Make sure the configurations are not modified, as they are treated as immutable
// in many places.
final Configuration configToReport = createNewConfigAndUpdateIfNotNull(newConfig,
contextThemeWrapperOverrideConfig);
if (!REPORT_TO_ACTIVITY) {
// Not configured to report to activity.
return configToReport;
}
if (movedToDifferentDisplay) {
activity.dispatchMovedToDisplay(displayId, configToReport);
}
if (shouldChangeConfig) {
activity.mCalled = false;
activity.onConfigurationChanged(configToReport);
if (!activity.mCalled) {
throw new SuperNotCalledException("Activity " + activity.getLocalClassName() +
" did not call through to super.onConfigurationChanged()");
}
}
return configToReport;
}
上述一系列方法调用,完成了对callback instanceof Activity 情况下的处理。对于其他情况,代码如下:
/**
* Decides whether to update a component's configuration and whether to inform it.
* @param cb The component callback to notify of configuration change.
* @param newConfig The new configuration.
*/
private void performConfigurationChanged(ComponentCallbacks2 cb, Configuration newConfig) {
if (!REPORT_TO_ACTIVITY) {
return;
}
// ContextThemeWrappers may override the configuration for that context. We must check and
// apply any overrides defined.
Configuration contextThemeWrapperOverrideConfig = null;
if (cb instanceof ContextThemeWrapper) {
final ContextThemeWrapper contextThemeWrapper = (ContextThemeWrapper) cb;
contextThemeWrapperOverrideConfig = contextThemeWrapper.getOverrideConfiguration();
}
// Apply the ContextThemeWrapper override if necessary.
// NOTE: Make sure the configurations are not modified, as they are treated as immutable
// in many places.
final Configuration configToReport = createNewConfigAndUpdateIfNotNull(
newConfig, contextThemeWrapperOverrideConfig);
cb.onConfigurationChanged(configToReport);
}
网友评论