android语言切换是在packages/apps/Settings/com/android/settings/LocalePicker.java的updateLocale()函数中调用.
-
-
-
-
- public static void updateLocale(Locale locale) {
- try {
- IActivityManager am = ActivityManagerNative.getDefault();
- Configuration config = am.getConfiguration();
-
- config.locale = locale;
-
-
- config.userSetLocale = true;
-
- am.updateConfiguration(config);
-
- BackupManager.dataChanged("com.android.providers.settings");
- } catch (RemoteException e) {
-
- }
- }
从注释可以看出, 只要本地local改变就会调用该函数. 查看ActivityManagerNative的getDefault()可以看到, 该函数返回的是远程服务对象ActivityManagerServices.java在本地的一个代理. 最终调用的是ActivityManagerService.java中的updateConfiguration()函数.
- public void updateConfiguration(Configuration values) {
- enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
- "updateConfiguration()");
-
- synchronized(this) {
- if (values == null && mWindowManager != null) {
-
- values = mWindowManager.computeNewConfiguration();
- }
-
- if (mWindowManager != null) {
- mProcessList.applyDisplaySize(mWindowManager);
- }
-
- final long origId = Binder.clearCallingIdentity();
- if (values != null) {
- Settings.System.clearConfiguration(values);
- }
- updateConfigurationLocked(values, null, false, false);
- Binder.restoreCallingIdentity(origId);
- }
- }
该函数, 首先进行的是权限的校验. 然后调用updateConfigurationLocked()函数.
-
-
-
-
-
-
-
-
- public boolean updateConfigurationLocked(Configuration values,
- ActivityRecord starting, boolean persistent, boolean initLocale) {
- int changes = 0;
-
- boolean kept = true;
-
- if (values != null) {
- Configuration newConfig = new Configuration(mConfiguration);
- changes = newConfig.updateFrom(values);
- if (changes != 0) {
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) {
- Slog.i(TAG, "Updating configuration to: " + values);
- }
-
- EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes);
-
- if (values.locale != null && !initLocale) {
- saveLocaleLocked(values.locale,
- !values.locale.equals(mConfiguration.locale),
- values.userSetLocale, values.simSetLocale);
- }
-
-
- mConfigurationSeq++;
- if (mConfigurationSeq <= 0) {
- mConfigurationSeq = 1;
- }
- newConfig.seq = mConfigurationSeq;
- mConfiguration = newConfig;
- Slog.i(TAG, "Config changed: " + newConfig);
-
- final Configuration configCopy = new Configuration(mConfiguration);
-
- AttributeCache ac = AttributeCache.instance();
- if (ac != null) {
- ac.updateConfiguration(configCopy);
- }
-
-
-
-
-
-
-
-
- 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) {
- }
- }
- Intent intent = new Intent(Intent.ACTION_CONFIGURATION_CHANGED);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
- | Intent.FLAG_RECEIVER_REPLACE_PENDING);
- broadcastIntentLocked(null, null, intent, null, null, 0, null, null,
- null, false, false, MY_PID, Process.SYSTEM_UID);
- if ((changes&ActivityInfo.CONFIG_LOCALE) != 0) {
- broadcastIntentLocked(null, null,
- new Intent(Intent.ACTION_LOCALE_CHANGED),
- null, null, 0, null, null,
- null, false, false, MY_PID, Process.SYSTEM_UID);
- }
-
- }
- }
-
- if (changes != 0 && starting == null) {
-
-
-
- starting = mMainStack.topRunningActivityLocked(null);
- }
-
- if (starting != null) {
- kept = mMainStack.ensureActivityConfigurationLocked(starting, changes);
-
-
- mMainStack.ensureActivitiesVisibleLocked(starting, changes);
- }
-
- if (values != null && mWindowManager != null) {
- mWindowManager.setNewConfiguration(mConfiguration);
- }
-
- return kept;
- }
整个语言切换就在这个函数中完成. 咋一看似乎没感觉到该函数做了哪些事情. 我们首先来看注释: Do either or both things: (1) change the current configuration, and (2)
make sure the given activity is running with the (now) current. configuration大概意思是: 这个函数做了两件事情. (1). 改变当前的configuration. 意思就是让改变的configuration更新到当前configuration. (2) 确保所有正在运行的activity都能更新改变后的configuration.(这点是关键.) . 我们按照这个思路看看android是如何更新configuration. 查看代码 , 首先看到 这个函数首先判断values是否为空, 这里values肯定不为空的, 然后changes = newConfig.updateFrom(values); 我们看看updateFrom做了什么操作.
-
-
-
-
-
-
-
-
- public int updateFrom(Configuration delta) {
- int changed = 0;
- ...
- if (delta.locale != null
- && (locale == null || !locale.equals(delta.locale))) {
- changed |= ActivityInfo.CONFIG_LOCALE;
- locale = delta.locale != null
- ? (Locale) delta.locale.clone() : null;
- textLayoutDirection = LocaleUtil.getLayoutDirectionFromLocale(locale);
- }
- if (delta.userSetLocale && (!userSetLocale || ((changed & ActivityInfo.CONFIG_LOCALE) != 0)))
- {
- userSetLocale = true;
- changed |= ActivityInfo.CONFIG_LOCALE;
- }
- ...
- return changed;
- }
因为语言改变了, 那么 (!locale.equals(delta.locale)) 是true. changed 大于0, 然后return changed. 回到ActivityManagerService.java的updateConfigurationLocked函数, 因为changed不为0 , 所以走if这个流程. 继续看代码
- 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) {
- }
- }
首先看到的是mLurProcesses 是ArrayList<ProcessRecord>类型. LRU : Least Recently Used保存所有运行过的进程. ProcessRecord进程类, 一个apk文件运行时会对应一个进程. app.thread. 此处的thread代表的是ApplicationThreadNative.java类型. 然后调用其scheduleConfigurationChanged(); 查看该函数
- 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调用, 所以 , binder在android中是一个很重要的概念. 此处远程调用的是ActivityThread.java中的私有内部内ApplicationThread
- private class ApplicationThread extends ApplicationThreadNative {
- private static final String HEAP_COLUMN = "%13s %8s %8s %8s %8s %8s %8s";
- private static final String ONE_COUNT_COLUMN = "%21s %8d";
- private static final String TWO_COUNT_COLUMNS = "%21s %8d %21s %8d";
- private static final String TWO_COUNT_COLUMNS_DB = "%21s %8d %21s %8d";
- private static final String DB_INFO_FORMAT = " %8s %8s %14s %14s %s";
-
-
- ...
- public void scheduleConfigurationChanged(Configuration config) {
- updatePendingConfiguration(config);
- queueOrSendMessage(H.CONFIGURATION_CHANGED, config);
- }
- ...
- }
而ApplicationThread中的handler的CONFIGURATION_CHANGED是调用handleConfigurationChanged()
- final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
-
- ArrayList<ComponentCallbacks2> callbacks = null;
-
- ... ...
- applyConfigurationToResourcesLocked(config, compat);
-
- ...
-
- callbacks = collectComponentCallbacksLocked(false, config);
- ...
-
- if (callbacks != null) {
- final int N = callbacks.size();
- for (int i=0; i<N; i++) {
- performConfigurationChanged(callbacks.get(i), config);
- }
- }
这个函数首先是调用applyConfigurationToResourcesLocked(). 看函数名大概可以推测: 将configuration应用到resources.这里configuration改变的是local 本地语言. 那而resources资源包含不就包含了语言, 图片这些资源吗.
- final boolean applyConfigurationToResourcesLocked(Configuration config,
- CompatibilityInfo compat) {
-
- int changes = mResConfiguration.updateFrom(config);
- DisplayMetrics dm = getDisplayMetricsLocked(null, true);
-
-
- if (compat != null && (mResCompatibilityInfo == null ||
- !mResCompatibilityInfo.equals(compat))) {
- mResCompatibilityInfo = compat;
- changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
- | ActivityInfo.CONFIG_SCREEN_SIZE
- | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
- }
-
- ...
-
- 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();
- }
- }
-
- return changes != 0;
- }
Resources.updateSystemConfiguration()清除一部分系统资源, 并且将config更新到Resources, 而Resources包含了一个AssetManager对象, 该对象的核心实现是在AssetManager.cpp中完成的. 然后循环清空mActivityResources资源. 再回到handleConfigurationChanged()函数, 执行完updateSystemConfiguration后, 会循环该进程的所有activity:
if (callbacks != null) {
final int N = callbacks.size();
for (int i=0; i<N; i++) {
performConfigurationChanged(callbacks.get(i), config);
}
}
再来看performConfigurationChanged的实现:
- private final void performConfigurationChanged(
- ComponentCallbacks2 cb, Configuration config) {
-
-
-
- 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 {
-
-
-
-
- int diff = activity.mCurrentConfig.diff(config);
- if (diff != 0) {
-
-
-
- if ((~activity.mActivityInfo.getRealConfigChanged() & diff) == 0) {
- shouldChangeConfig = true;
- }
- }
- }
-
- if (DEBUG_CONFIGURATION) Slog.v(TAG, "Config callback " + cb
- + ": shouldChangeConfig=" + shouldChangeConfig);
- if (shouldChangeConfig) {
- cb.onConfigurationChanged(config);
-
- if (activity != null) {
- if (!activity.mCalled) {
- throw new SuperNotCalledException(
- "Activity " + activity.getLocalClassName() +
- " did not call through to super.onConfigurationChanged()");
- }
- activity.mConfigChangeFlags = 0;
- activity.mCurrentConfig = new Configuration(config);
- }
- }
- }
该函数判断configuration是否改变, 如果改变那么shouldChangeConfig为true. 然后调用activity的onConfigurationChange(config);
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- public void onConfigurationChanged(Configuration newConfig) {
- mCalled = true;
-
- mFragments.dispatchConfigurationChanged(newConfig);
-
- if (mWindow != null) {
-
- mWindow.onConfigurationChanged(newConfig);
- }
-
- if (mActionBar != null) {
-
-
- mActionBar.onConfigurationChanged(newConfig);
- }
- }
查看注释, 大概意思是: 如果你的activity运行 , 设备信息有改变(即configuration改变)时由系统调用. 如果你在manifest.xml中配置了configChnages属性则表示有你自己来处理configuration change. 否则就重启当前这个activity. 而重启之前, 旧的resources已经被清空, 那么就会装载新的资源, 整个过程就完成了语言切换后 , 能够让所有app使用新的语言. 语言切换流程大概分为三步:
第一步: 判断configuration的local是否已经改变, 如果改变则将local更新到当前的configuration
第二步: 清空旧的资源.
第三步: 重启所有所有进程并加装新资源.
由于个人知识水平有限, 有些地方不免有些纰漏, 希望大牛多多指点. 也希望有共同兴趣爱好的人进行技术交流.