Android语言切换原理

Android语言切换原理

前言

   之前因为系统有些国家使用的字体乱码的原因,研究了下Android系统字体加载相关的知识,写了一篇Android系统字体加载流程的总结,浅析Android字体加载原理,然而系统的字体与系统当前的语言有密切的关系,因此抽空了解了下Android系统语言切换的流程,写下总结,加深印象。

Android语言切换流程分析

概述

   有过Android开发经验的人,应该都知道Android有一套成熟的国际化机制,通常应用或系统要走出海外,都要进行国际化适配,而Android语言切换依赖于国际化适配,但这里,我们不深入了解Android国际化的原理,有兴趣的读者可以自行Google或者百度,下面对Android语言切换流程进行分析。

Android多语言描述

   有关Android系统本地化的介绍,请查看官网,本地化

   对于Android语言切换接口,在Android 6.0及以前的语言设置都是单一的语言,只能选一种语言,见下图。

6.0系统

   Android7.0系统以上,则是更人性化了,允许用户在设置中选择多个语言,如下图,用户可以根据自己的喜好选择语言列表,并将默认的语言拖拽到首项,设为系统默认语言。

7.0系统

   这么做的目的是为了一些国家使用多种语言,比如印度,印度语是第一母语,英语则为其第二母语,这样的话,在系统捏添加这两个语言后,会加载相应的语言资源,当第一语言没有相应资源时,会去第二语言中查找,而7.0以下的系统就只能加载一种语言下的资源文件,存在很大的限制,这里就不做扩展,有兴趣的读者可以自行了解。

Android多语言切换

   如上所述,Android在不同的系统版本为用户提供不同的语言切换功能,因此在切换流程过中,调用的接口也不同,如Android6.0及以下,设置切换语言的接口调用的是updateLocale(Locale locale),如Android7.0以上,设置切换语言的接口调用的是updateLocales(LocaleList locales),但是大致的流程还是保持一致,多的只是文件存放位置的变化,下面笔者将以Android7.0的流程进行分析,7.0以下的,读者可自行分析。

注:从上面的两个接口,也可以看出,高版本系统与低版本系统加载语言的区别,前者是加载多个语言的列表,后者是加载一个语言。

   如上所述,当用户在设置中选择对应的语言后,Android会首先调frameworks/base/com/android/internal/app/LocalePicker.java中的updateLocales(LocaleList locales)方法。

注:如果你不想用Android7.0以上的语言切换功能,可以考虑自己实现updateLocale(Locale locale)方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 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
}
}

   Google对该方法的介绍是,当更新系统语言列表的时候,就会调用这个方法。从上方展示的源码来看,该方法首先会调用ActivityManagerNative.getDefault()来获取ActivityManagerServices(以下简称AMS)在本地的代理,从而调用AMS中的updatePersistentConfiguration()并传入创建好的配置对象(Configuration)。

注:这里是ActivityManagerNative使用远程代理通过Binder条用AMS的同名方法,由于Android的代理机制十分复杂,这里不继续介绍Android代理机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void updatePersistentConfiguration(Configuration values) {
//强制权限校验
enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
"updateConfiguration()");
//强制写入设置权限
enforceWriteSettingsPermission("updateConfiguration()");
if (values == null) {
throw new NullPointerException("Configuration must not be null");
}
//获取调用用户的ID
int userId = UserHandle.getCallingUserId();
synchronized(this) {
//调用方法
updatePersistentConfigurationLocked(values, userId);
}
}

   可以看出,该方法首先进行了权限校验,权限赋予,然后调用updatePersistentConfigurationLocked()方法,继续看下这个方法。

1
2
3
4
5
6
7
8
9
10
11
private void updatePersistentConfigurationLocked(Configuration values, @UserIdInt int userId) {
//清除Binder调用标识
final long origId = Binder.clearCallingIdentity();
try {
//重要方法
updateConfigurationLocked(values, null, false, true, userId, false /* deferResume */);
} finally {
Binder.restoreCallingIdentity(origId);
}
}

   这个方法只是清除了Binder标识,并调用了updateConfigurationLocked()方法,该方法非常重要,注意这里的传参,继续往下看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/**
* 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) {
}
}
...
}

   从Google给的注释说明可以看到,这个方法的最重要的作用如下:

  1. 更新当前系统的配置到最新的配置;
  2. 保证所有的Activity都能更新到改变后的配置。

注:在更新或者清除configuration时,是通过changes位标记法来确认是configuration中的哪一项。

   继续来看Android是如何进行配置更新的。首先调用updateFrom(),方法来更新配置,我们先看看这个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* Copies the fields from delta into this Configuration object, keeping
* track of which ones have changed. Any undefined fields in {@code delta}
* are ignored and not copied in to the current Configuration.
*
* @return a bit mask of the changed fields, as per {@link #diff}
*/
public @Config int updateFrom(@NonNull Configuration delta) {
//变化项
int changed = 0;
...
//当有语言列表变化时,走这
if (!delta.mLocaleList.isEmpty() && !mLocaleList.equals(delta.mLocaleList)) {
changed |= ActivityInfo.CONFIG_LOCALE;
mLocaleList = delta.mLocaleList;
// delta.locale can't be null, since delta.mLocaleList is not empty.
if (!delta.locale.equals(locale)) {
locale = (Locale) delta.locale.clone();
// If locale has changed, then layout direction is also changed ...
changed |= ActivityInfo.CONFIG_LAYOUT_DIRECTION;
// ... and we need to update the layout direction (represented by the first
// 2 most significant bits in screenLayout).
setLayoutDirection(locale);
}
}
...
if (delta.userSetLocale && (!userSetLocale || ((changed & ActivityInfo.CONFIG_LOCALE) != 0)))
{
changed |= ActivityInfo.CONFIG_LOCALE;
userSetLocale = true;
}
return changed;
}

   从以上可以看到,当我们修改了语言列表,那么返回的变化项change一定大于0。继续回到updateConfigurationLocked()方法中,由于change不为0,并且根据前面的传参,updateConfigurationLocked()方法将从变更的国家语言列表中获取默认国家语言下标,然后设置默认国家语言以及默认国家列表,并发送消息通知挂载守护进程国家语言的变化,其中设置默认情况的代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//设置默认国家语言以及国家语言列表
/**
* This may be used directly by system processes to set the default locale list for apps. For
* such uses, the default locale list would always come from the user preferences, but the
* default locale may have been chosen to be a locale other than the first locale in the locale
* list (based on the locales the app supports).
*
* {@hide}
*/
public static void setDefault(@NonNull @Size(min=1) LocaleList locales, int localeIndex) {
if (locales == null) {
throw new NullPointerException("locales is null");
}
if (locales.isEmpty()) {
throw new IllegalArgumentException("locales is empty");
}
synchronized (sLock) {
sLastDefaultLocale = locales.get(localeIndex);
Locale.setDefault(sLastDefaultLocale);
sLastExplicitlySetLocaleList = locales;
sDefaultLocaleList = locales;
if (localeIndex == 0) {
sDefaultAdjustedLocaleList = sDefaultLocaleList;
} else {
sDefaultAdjustedLocaleList = new LocaleList(
sLastDefaultLocale, sDefaultLocaleList);
}
}
}

   再此之后,系统进程会首先通知Configuration改变,所以mSystemThread即系统的ActivityThread类对象调用applyConfigurationToResources()确保自己所在的进程资源更新到最新(主要指framework-res.apk中的资源,也就是上面所说的updateConfigurationLocked()方法的第一个重要作用),以便任何人检索资源的时候拿到的都是最新的资源,然后发送通知更新用户配置。

注:这里是系统更新配置资源,后面将对pplyConfigurationToResources()方法进行描述。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 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);
}

   继续往下看,以下是遍历每个应用,通知其配置的改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//保存所有应用的进程
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();
}

   可以看到,mLruProcesses中保存的是所有运行的进程,Android中,每个应用运行时都对应于一个进程,因此这里包含了所有运行的应用。我们注意到这里循环调用了app.thread.scheduleConfigurationChanged(configCopy),app.thread对应于每个应用的线程,其作用是通知各个应用进程Configuration改变。跳转后会发现,这里其实又是通过binder调用跨进程方法,在这里是调用ActivityThread.java中私有ApplicationThread的方法,查看该方法。

注:ApplicationThread是ActivityThread的内部类,也是一个Binder对象,这边用以等待AMS发送消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;

   这里ApplicationThread接收到AMS的信息后,会发送消息CONFIGURATION_CHANGED给对应应用的ActivityThread。ActivityThread收到CONFIGURATION_CHANGED消息后,其会调用handleConfigurationChanged()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
int configDiff = 0;
synchronized (mResourcesManager) {
...
//将资源配置修改应用到资源中
mResourcesManager.applyConfigurationToResourcesLocked(config, compat);
//更新语言列表
updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(),
mResourcesManager.getConfiguration().getLocales());
...
}
//组件修改回调对象
ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(false, config);
freeTextLayoutCachesIfNeeded(configDiff);
if (callbacks != null) {framework-res.apk
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;
//Activity回调响应
performConfigurationChangedForActivity(mActivities.get(a.getActivityToken()),
config, REPORT_TO_ACTIVITY);
} else {
//其他回调响应
performConfigurationChanged(cb, null, config, null, REPORT_TO_ACTIVITY);
}
}
}
}

   在handleConfigurationChanged()方法中,也会调用applyConfigurationToResourcesLocked()方法,去更新每个应用的配置资源(也就是上述updateConfigurationLocked()方法的第二个重要作用)。

   也就是说,不管是系统资源还是应用资源的更新都要调用applyConfigurationToResourcesLocked()方法。按字面意思,大概的作用就是将资源配置修改应用到资源中,而资源文件就包含语言资源文件、图片资源、布局资源等,查看其代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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);
}
}

   按照上面的说法,系统进程以及应用进程会分别调用applyConfigurationToResourcesLocked()方法来更新配置。

   当系统进程执行该方法时,Resources.updateSystemConfiguration()会更新系统资源配置(frameworks-res.apk),在执行完ApplicationPackageManager.configurationChanged()方法后,会清除进程的资源缓存,如Icon与String,并移除其他旧的资源,最终加载新的系统资源。

注:这里并未对更新细节进行详细描述,如有兴趣可以自行研究下。

   当应用进程执行该方法时,会通知应用进程更新资源配置,并且实现ComponentCallbacks2接口的组件,如Activity、Services、Application等会被记录,并在handleConfigurationChanged()中被遍历回调通知更新资源配置,因此我们再回到handleConfigurationChanged()方法中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//组件修改回调对象
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;
//Activity回调响应
performConfigurationChangedForActivity(mActivities.get(a.getActivityToken()),
config, REPORT_TO_ACTIVITY);
} else {
//其他回调响应
performConfigurationChanged(cb, null, config, null, REPORT_TO_ACTIVITY);
}
}
}

   从上面可以看出,在回调中,如果是Activity,则回调performConfigurationChangedForActivity()方法,如果是Services、Application等,回调performConfigurationChanged()方法,按注释解释,这样做是为了Activity在更新配置时重写配置和避免没有修改时回调onConfigurationChanged()方法,但不管是什么组件,最终都是调用performConfigurationChanged()方法,我们来具体看下这个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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);
}
}
}

   从上面代码注释,可以看到,只有Activity组件才会实现这个方法,并且只有当配置修改不是全局时,Activity会在回调前调用updateResourcesForActivity()方法来更新配置资源,最后回调onConfigurationChanged()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* Called by the system when the device configuration changes while your
* activity is running. Note that this will <em>only</em> be called if
* you have selected configurations you would like to handle with the
* {@link android.R.attr#configChanges} attribute in your manifest. If
* any configuration change occurs that is not selected to be reported
* by that attribute, then instead of reporting it the system will stop
* and restart the activity (to have it launched with the new
* configuration).
* <p>
* <p>At the time that this function has been called, your Resources
* object will have been updated to return resource values matching the
* new configuration.
*
* @param newConfig The new device configuration.
*/
public void onConfigurationChanged(Configuration newConfig) {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onConfigurationChanged " + this + ": " + newConfig);
mCalled = true;
mFragments.dispatchConfigurationChanged(newConfig);
if (mWindow != null) {
// Pass the configuration changed event to the window
mWindow.onConfigurationChanged(newConfig);
}
if (mActionBar != null) {
// Do this last; the action bar will need to access
// view changes from above.
mActionBar.onConfigurationChanged(newConfig);
}
}

   以上注释大概的意思是,当你的Activity在运行时,有设备配置发生了变化,系统就会调用这个方法。如果你在manifest中配置了configChanges属性,则表示由你自己处理配置修改,否则就会重启这个Activity,并且会加载新的资源,这样就让系统以及应用加载完新的资源,完成了语言的切换。

总结

   从整个流程来看,Android字体切换的流程如下:

  1. 当切换或添加新的语言时,会生成新的Configuration来替换原来的Configuration,并且修改项是可追寻的;
  2. 根据最新的Configuration来更新系统资源以及应用资源;
  3. 重启所有的Activity并更新到最新的资源;
  4. 完成语言切换。

   以下为语言切换流程大致的时序图。

这里写图片描述

注:由于个人能力有限以及时间关系,有遗漏或错误的地方,还请批评指出,谢谢。

参考博客

   wqhjfree
   七号大蒜


版权声明:本文为博主原创文章,转载请注明出处KidSea

小额赞助,鼓励作者写出更好的文章