一. 语言切换时序图
二. 关键步骤
1. Activity侧-发起端
/**
* Requests the system to update the list of system locales.
* Note that the system looks halted for a while during the Locale migration,
* so the caller need to take care of it.
*/
public static void updateLocales(LocaleList locales) {
try {
//获取am
final IActivityManager am = ActivityManagerNative.getDefault();
//获取am配置对象
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
}
}
2. AMS处理流程
/**
* 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) {
int changes = 0;
......
if (values != null) {
Configuration newConfig = new Configuration(mConfiguration);
changes = newConfig.updateFrom(values);
if (changes != 0) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.i(TAG_CONFIGURATION,
"Updating 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)));
}
...
// 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(configCopy);
//如果有配置改动,就发送该消息通知配置改动
if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {
Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);
msg.obj = new Configuration(configCopy);
msg.arg1 = userId;
mHandler.sendMessage(msg);
}
...
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 " + mConfiguration);
app.thread.scheduleConfigurationChanged(configCopy);
}
} catch (Exception e) {
}
}
...
}
语言变更后,通知所有当前正在运行的进程
//保存所有应用的进程
final ArrayList<ProcessRecord> mLruProcesses = new ArrayList<ProcessRecord>();
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 " + mConfiguration);
app.thread.scheduleConfigurationChanged(configCopy);
}
} catch (Exception e) {
}
}
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();
}
3. Activity侧-AMS回调
public void scheduleConfigurationChanged(Configuration config) {
updatePendingConfiguration(config);
//发送消息给对应的主线程
sendMessage(H.CONFIGURATION_CHANGED, config);
}
...
case CONFIGURATION_CHANGED:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
mCurDefaultDisplayDpi = ((Configuration)msg.obj).densityDpi;
mUpdatingSystemConfig = true;
//接收到AMS发来的数据
handleConfigurationChanged((Configuration)msg.obj, null);
mUpdatingSystemConfig = false;
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
资源配置的变更
public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config,
@Nullable CompatibilityInfo compat) {
private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls =
new ArrayMap<>();
try {
...
//获取更新项
int changes = mResConfiguration.updateFrom(config);
// Things might have changed in display manager, so clear the cached displays.
mDisplays.clear();
...
//更新系统资源配置
Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);
//通知配置文件修改,清理缓存,如Icon和String
ApplicationPackageManager.configurationChanged();
//Slog.i(TAG, "Configuration changed in " + currentPackageName());
Configuration tmpConfig = null;
for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
ResourcesKey key = mResourceImpls.keyAt(i);
ResourcesImpl r = mResourceImpls.valueAt(i).get();
if (r != null) {
...
r.updateConfiguration(tmpConfig, dm, compat);
} else {
r.updateConfiguration(config, dm, compat);
}
//Slog.i(TAG, "Updated app resources " + v.getKey()
// + " " + r + ": " + r.getConfiguration());
} else {
//Slog.i(TAG, "Removing old resources " + v.getKey());
mResourceImpls.removeAt(i);
}
}
return changes != 0;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
相关的资源文件缓存进行清理--ApplicationPackageManager.java,因为缓存被清除了,所以下次再获取资源文件的时候,需要重新执行ResourcesManager.getRes().
static void configurationChanged() {
synchronized (sSync) {
// 图片icon的缓存
sIconCache.clear();
// 字符串缓存,比如getText()获取到的字符串
sStringCache.clear();
}
}
activity执行配置变更逻辑
private void performConfigurationChanged(ComponentCallbacks2 cb,
IBinder activityToken,
Configuration newConfig,
Configuration amOverrideConfig,
boolean reportToActivity) {
// Only for Activity objects, check that they actually call up to their
// superclass implementation. ComponentCallbacks2 is an interface, so
// we check the runtime type and act accordingly.
Activity activity = (cb instanceof Activity) ? (Activity) cb : null;
if (activity != null) {
activity.mCalled = false;
}
boolean shouldChangeConfig = false;
if ((activity == null) || (activity.mCurrentConfig == null)) {
shouldChangeConfig = true;
} else {
// If the new config is the same as the config this Activity is already
// running with and the override config also didn't change, then don't
// bother calling 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
|| !reportToActivity) {
shouldChangeConfig = true;
}
}
}
if (shouldChangeConfig) {
// Propagate the configuration change to the Activity and ResourcesManager.
// 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();
}
// 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 {@link ComponentCallbacks2#onConfigurationChanged(Configuration)}.
if (activityToken != null) {
// 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);
}
if (reportToActivity) {
// 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);
}
if (activity != null) {
if (reportToActivity && !activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + activity.getLocalClassName() +
" did not call through to super.onConfigurationChanged()");
}
activity.mConfigChangeFlags = 0;
activity.mCurrentConfig = new Configuration(newConfig);
}
}
}