前言
前篇阐述了Android语言切换的方法,那么这一篇文章我们就深入了解一下Android语言切换的原理吧。
正文
1、进入语言切换的入口类LocalePicker,找到updateLocale()方法
/**
* Requests the system to update the system locale. 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 updateLocale(Locale locale) {
try {
IActivityManager am = ActivityManagerNative.getDefault();
Configuration config = am.getConfiguration();
config.setLocale(locale);
config.userSetLocale = true;
am.updateConfiguration(config);
// Trigger the dirty bit for the Settings Provider.
BackupManager.dataChanged("com.android.providers.settings");
} catch (RemoteException e) {
// Intentionally left blank
}
}
当locale发生改变的时候就会请求系统更新语言设置。ActivityManagerNative.getDefault()获取了ActivityManagerService的代理类,然后调这个类的updateConfiguration(config)的方法。
public void updateConfiguration(Configuration values) {
//进行权限校验
enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
"updateConfiguration()");
synchronized(this) {
final long origId = Binder.clearCallingIdentity();
if (values != null) {
Settings.System.clearConfiguration(values);
}
updateConfigurationLocked(values, null, false, false);
Binder.restoreCallingIdentity(origId);
}
}
首先会进行权限的校验,然后清除Binder调用标志,再通过Settings.System.clearConfiguration(values)清除原有Configuration参数配置,然后再赋予新的参数配置。最后调用updateConfigurationLocaked()方法。
第一段主要方法newConfig.updateFrom(values)
/**
* 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 persistent TODO
*/
public boolean updateConfigurationLocked(Configuration values,
ActivityRecord starting, boolean persistent, boolean initLocale) {
int changes = 0;
if (values != null) {
Configuration newConfig = new Configuration(mConfiguration);
changes = newConfig.updateFrom(values);
if (changes != 0) {
.....
mConfigurationSeq++;
if (mConfigurationSeq <= 0) {
mConfigurationSeq = 1;
}
newConfig.seq = mConfigurationSeq;
mConfiguration = newConfig;
final Configuration configCopy = new Configuration(mConfiguration);
AttributeCache ac = AttributeCache.instance();
if (ac != null) {
ac.updateConfiguration(configCopy);
}
第二段主要关注for循环
// 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);
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, "Sending to proc "
+ app.processName + " new config " + mConfiguration);
app.thread.scheduleConfigurationChanged(configCopy);
}
} catch (Exception e) {
}
}
......
}
}
把此方法截取分为两段来看,第一段最主要的方法就是newConfig.update()方法,这个方法判断我们修改了语言列表,如果修改了,就返回大于0的值。第二段就是for循环相关的了,这一段主要就是遍历所有的应用,然后对每个应用调用scheduleConfigurationChanged()方法进行语言切换。
public @Config int updateFrom(@NonNull Configuration delta) {
//初始化变量,用于判断语言是否变化
int changed = 0;
...
if (delta.userSetLocale && (!userSetLocale || ((changed & ActivityInfo.CONFIG_LOCALE) != 0)))
{
changed |= ActivityInfo.CONFIG_LOCALE;
userSetLocale = true;
}
return changed;
}
我们再查看一下
scheduleConfigurationChanged()这个方法,这个方法是通过binder远程调用在
ActivityThread中的方法,这个方法主要执行了updatePendingConfiguration()和queueOrSendMessage()两个方法。其中updatePendingConfiguration()就是用于更新Configuration的,queueOrSendMessage()是通过调用handleConfigurationChanged()实现功能的。
public void scheduleConfigurationChanged(Configuration config) {
updatePendingConfiguration(config);
queueOrSendMessage(H.CONFIGURATION_CHANGED, config);
}
下面是for循环内的
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, "Sending to proc "
+ app.processName + " new config " + mConfiguration);
app.thread.scheduleConfigurationChanged(configCopy);
}
} catch (Exception e) {
}
}
我们再查看一下scheduleConfigurationChanged()这个方法,这个方法是通过binder远程调用在
ActivityThread中的方法,这个方法主要执行了updatePendingConfiguration()和queueOrSendMessage()两个方法。其中updatePendingConfiguration()就是用于更新Configuration的,queueOrSendMessage()是通过调用handleConfigurationChanged()实现功能的。
public void scheduleConfigurationChanged(Configuration config) {
updatePendingConfiguration(config);
queueOrSendMessage(H.CONFIGURATION_CHANGED, config);
}
我们接着查看handleConfigurationChanged()这个方法,在这个方法中主要有applyConfigurationToResourcesLocked()和performConfigurationChanged()。上一个方法主要重新将Configuration的参数配置到资源上面,后一个方法主要是执行Configuration的改变、完成语言的切换。
final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
applyConfigurationToResourcesLocked(config, compat);
if (callbacks != null) {
final int N = callbacks.size();
for (int i=0; i<N; i++) {
performConfigurationChanged(callbacks.get(i), config);
}
}
在applyConfigurationToResuourcesLocked()中,Resources.updateSystemConfiguration()将config更新到Resource上面,在通过while循环删除mActiveResources旧的资源,更新新的资源。
final boolean applyConfigurationToResourcesLocked(Configuration config,
CompatibilityInfo compat) {
DisplayMetrics dm = getDisplayMetricsLocked(null, true);
Resources.updateSystemConfiguration(config, dm, compat);
Iterator<WeakReference<Resources>> it = mActiveResources.values().iterator();
while (it.hasNext()) {
WeakReference<Resources> v = it.next();
Resources r = v.get();
if (r != null) {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
+ r + " config to: " + config);
r.updateConfiguration(config, dm, compat);
} else {
it.remove();
}
}
}
对于performConfigurationChanged()这个方法,判断activity的config和传递过来的config是否一样,如果不一样就将shouldChangeConfig=true,如果shouldChangeConfig=true,就执行activity的onConfigurationChanged(config)方法。
private final void performConfigurationChanged(ComponentCallbacks2 cb, Configuration config) {
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 then don't bother calling
// onConfigurationChanged
int diff = activity.mCurrentConfig.diff(config);
if (diff != 0) {
// 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 ((~activity.mActivityInfo.getRealConfigChanged() & diff) == 0) {
shouldChangeConfig = true;
}
}
}
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Config callback " + cb
+ ": shouldChangeConfig=" + shouldChangeConfig);
if (shouldChangeConfig) {
cb.onConfigurationChanged(config);
}
}
总结
其实就是判断Configuration的locale有没有改变,有改变就清除旧的Resource资源,并将新的config赋予Resource,并重新执行加载Resource资源。