android语言切换是在packages/apps/Settings/com/android/settings/LocalePicker.java的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.locale = locale;
- // indicate this isn't some passing default - the user wants this remembered
- 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
- }
- }
- public void updateConfiguration(Configuration values) {
- enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
- "updateConfiguration()");
- synchronized(this) {
- if (values == null && mWindowManager != null) {
- // sentinel: fetch the current configuration from the window manager
- 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);
- }
- }
- /**
- * 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;
- 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);
- }
- // 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) {
- }
- }
- 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) {
- // If the configuration changed, and the caller is not already
- // in the process of starting an activity, then find the top
- // activity to check if its configuration needs to change.
- starting = mMainStack.topRunningActivityLocked(null);
- }
- if (starting != null) {
- kept = mMainStack.ensureActivityConfigurationLocked(starting, changes);
- // And we need to make sure at this point that all other activities
- // are made visible with the correct configuration.
- 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做了什么操作.
- /**
- * Copy the fields from delta into this Configuration object, keeping
- * track of which ones have changed. Any undefined fields in
- * <var>delta</var> are ignored and not copied in to the current
- * Configuration.
- * @return Returns a bit mask of the changed fields, as per
- * {@link #diff}.
- */
- 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);
- //Slog.i(TAG, "Updated app resources " + v.getKey()
- // + " " + r + ": " + r.getConfiguration());
- } else {
- //Slog.i(TAG, "Removing old resources " + v.getKey());
- 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) {
- // 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 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);
- 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);
- /**
- * 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>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) {
- 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运行 , 设备信息有改变(即configuration改变)时由系统调用. 如果你在manifest.xml中配置了configChnages属性则表示有你自己来处理configuration change. 否则就重启当前这个activity. 而重启之前, 旧的resources已经被清空, 那么就会装载新的资源, 整个过程就完成了语言切换后 , 能够让所有app使用新的语言. 语言切换流程大概分为三步:
第一步: 判断configuration的local是否已经改变, 如果改变则将local更新到当前的configuration
第二步: 清空旧的资源.
第三步: 重启所有所有进程并加装新资源.
由于个人知识水平有限, 有些地方不免有些纰漏, 希望大牛多多指点. 也希望有共同兴趣爱好的人进行技术交流.