首页 > 代码库 > Android平台语言Locale设置流程分析

Android平台语言Locale设置流程分析

  Android系统Setting程序中对于语言设置这块的内容。具体位置有以下两处:

  1)、设置显示语言:Settings -> Language & keyboard -> Select language

  2)、设置输入语言:Settings -> Language & keyboard -> Android keyboard [settings] -> Input languages

  Settings工程中,Settings -> Language & keyboard界面所对应的Java代码和Preference布局如下:

  packages/apps/Settings/src/com/android/settings/LanguageSettings.java

  packages/apps/Settings/res/xml/language_settings.xml

 

  1、Settings -> Language & keyboard -> Select language

  在<android_root>/packages/apps/Settings/res/xml/language_settings.xml中,该模块的Preference布局为:

1 <PreferenceScreen    2   android:key="phone_language"    3   android:title="@string/phone_language">    4   <intent android:action="android.intent.action.MAIN"    5        android:targetPackage="com.android.settings"    6        android:targetClass="com.android.settings.LocalePicker"/>    7 </PreferenceScreen>  

  所以,当用户点击“Settings -> Language & keyboard -> Select language”时,将启动“com.android.settings.LocalePicker”的Activity。其对应的源代码为:
  /packages/apps/Settings/src/com/android/settings/LocalePicker.java
  LocalePicker Activity继承自ListActivity。在它的onCreate()回调中,调用了下面一条语句:
  String[] locales = getAssets().getLocales(); 
  LocalePicker Activity将取得的locale字符串进行了一些处理,然后创建了ArrayAdapter<Loc> adapter,并绑定到ListActivity的ListView上。当用户点击ListView上的Item时,再将选中的locale信息设置到 Android系统中。 选中处理:

 1 @Override 2 public void onLocaleSelected(final Locale locale) { 3     if (Utils.hasMultipleUsers(getActivity())) { 4         mTargetLocale = locale; 5         showDialog(DLG_SHOW_GLOBAL_WARNING); 6     } else { 7         getActivity().onBackPressed(); 8         LocalePicker.updateLocale(locale); 9     }10 }

  处理交给framework/base/core/java/com/android/internal/app/LocalePicker.java处理:

 1 /** 2  * Requests the system to update the system locale. Note that the system looks halted 3  * for a while during the Locale migration, so the caller need to take care of it. 4  */ 5 public static void updateLocale(Locale locale) { 6     try { 7         IActivityManager am = ActivityManagerNative.getDefault(); 8         Configuration config = am.getConfiguration(); 9 10         // Will set userSetLocale to indicate this isn‘t some passing default - the user11         // wants this remembered12         config.setLocale(locale);13 14         am.updateConfiguration(config);15         // Trigger the dirty bit for the Settings Provider.16         BackupManager.dataChanged("com.android.providers.settings");17     } catch (RemoteException e) {18         // Intentionally left blank19     }20 }

  这里先将Locale信息更新到配置文件Configuration config = am.getConfiguration()中,再通过am.updateConfiguration(config)进行实质的处理。

  IActivityManager的实现在ActivityManagerNative中,而ActivityManagerNative是一个abstract类,其实现在ActivityManagerService中。如下关系:
  public abstract class ActivityManagerNative extends Binder implements IActivityManager{}
  public final class ActivityManagerService extends ActivityManagerNative{}

  这里有两个updateConfiguration()方法,分别在ActivityManagerNative.ActivityManagerProxy和ActivityManagerService中:

 1 //ActivityManagerNative.ActivityManagerProxy: 2 public void updateConfiguration(Configuration values) throws RemoteException 3 { 4     Parcel data = http://www.mamicode.com/Parcel.obtain();> 5     Parcel reply = Parcel.obtain(); 6     data.writeInterfaceToken(IActivityManager.descriptor); 7     values.writeToParcel(data, 0); 8     mRemote.transact(UPDATE_CONFIGURATION_TRANSACTION, data, reply, 0); 9     reply.readException();10     data.recycle();11     reply.recycle();12 }13 14 15 //ActivityManagerService:16 public void updateConfiguration(Configuration values) {17     enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,18             "updateConfiguration()");19 20     synchronized(this) {21         if (values == null && mWindowManager != null) {22             // sentinel: fetch the current configuration from the window manager23             values = mWindowManager.computeNewConfiguration();24         }25 26         if (mWindowManager != null) {27             mProcessList.applyDisplaySize(mWindowManager);28         }29 30         final long origId = Binder.clearCallingIdentity();31         if (values != null) {32             Settings.System.clearConfiguration(values);33         }34         updateConfigurationLocked(values, null, false, false);35         Binder.restoreCallingIdentity(origId);36     }37 }

  如上是典型的Binder结构,LocalePicker.java调用Client端的代理,即:
  (1),LocalePicker.updateConfiguration()-->ActivityManagerNative.ActivityManagerProxy.updateConfiguration()
  (2),mRemote.transact(UPDATE_CONFIGURATION_TRANSACTION, data, reply, 0),这里的mRemote即Server端,即:ActivityManagerNative和ActivityManagerService
  (3),mRemote.transact()交给ActivityManagerNative.onTransact(int code, Parcel data, Parcel reply, int flags)
  (4),case UPDATE_CONFIGURATION_TRANSACTION: {updateConfiguration(config)}
  (5),updateConfiguration(config)即ActivityManagerNative.updateConfiguration(config),实现在ActivityManagerService.updateConfiguration(config)

  直接看ActivityManagerService.updateConfiguration(config):

 1 public void updateConfiguration(Configuration values) { 2     enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION, 3             "updateConfiguration()"); 4  5     synchronized(this) { 6         if (values == null && mWindowManager != null) { 7             // sentinel: fetch the current configuration from the window manager 8             values = mWindowManager.computeNewConfiguration(); 9         }10 11         if (mWindowManager != null) {12             mProcessList.applyDisplaySize(mWindowManager);13         }14 15         final long origId = Binder.clearCallingIdentity();16         if (values != null) {17             Settings.System.clearConfiguration(values);18         }19         updateConfigurationLocked(values, null, false, false);20         Binder.restoreCallingIdentity(origId);21     }22 }

  这里处理交给:updateConfigurationLocked(values, null, false, false)

 1 boolean updateConfigurationLocked(Configuration values, 2         ActivityRecord starting, boolean persistent, boolean initLocale) { 3     // do nothing if we are headless 4     if (mHeadless) return true; 5  6     int changes = 0; 7  8     if (values != null) { 9         Configuration newConfig = new Configuration(mConfiguration);10         changes = newConfig.updateFrom(values);11         if (changes != 0) {                12             EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes);13 14             if (values.locale != null && !initLocale) {15                 saveLocaleLocked(values.locale, 16                                  !values.locale.equals(mConfiguration.locale),17                                  values.userSetLocale, values.simSetLocale);    /// M: sim locale feature18             }19 20             mConfigurationSeq++;21             if (mConfigurationSeq <= 0) {22                 mConfigurationSeq = 1;23             }24             newConfig.seq = mConfigurationSeq;25             mConfiguration = newConfig;26             final Configuration configCopy = new Configuration(mConfiguration);27             mShowDialogs = shouldShowDialogs(newConfig);28 29             AttributeCache ac = AttributeCache.instance();30             if (ac != null) {31                 ac.updateConfiguration(configCopy);32             }33             mSystemThread.applyConfigurationToResources(configCopy);34 35             if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {36                 Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);37                 msg.obj = new Configuration(configCopy);38                 mHandler.sendMessage(msg);39             }40     41             for (int i=mLruProcesses.size()-1; i>=0; i--) {42                 ProcessRecord app = mLruProcesses.get(i);43                 try {44                     if (app.thread != null) {45                         if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending to proc "46                                 + app.processName + " new config " + mConfiguration);47                         app.thread.scheduleConfigurationChanged(configCopy);48                     }49                 } catch (Exception e) {50                 }51             }52             Intent intent = new Intent(Intent.ACTION_CONFIGURATION_CHANGED);53             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY54                     | Intent.FLAG_RECEIVER_REPLACE_PENDING55                     /*| Intent.FLAG_RECEIVER_FOREGROUND*/); // downgrade to background56             broadcastIntentLocked(null, null, intent, null, null, 0, null, null,57                     null, AppOpsManager.OP_NONE, false, false, MY_PID,58                     Process.SYSTEM_UID, UserHandle.USER_ALL);59             if ((changes&ActivityInfo.CONFIG_LOCALE) != 0) {60                 intent = new Intent(Intent.ACTION_LOCALE_CHANGED);61                 //intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); // downgrade to background62                 broadcastIntentLocked(null, null, intent,63                         null, null, 0, null, null, null, AppOpsManager.OP_NONE,64                         false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);65             }66         }67     }68 69     boolean kept = true;70     final ActivityStack mainStack = mStackSupervisor.getFocusedStack();71     if (changes != 0 && starting == null) {72         starting = mainStack.topRunningActivityLocked(null);73     }74 75     if (starting != null) {76         kept = mainStack.ensureActivityConfigurationLocked(starting, changes);77         mStackSupervisor.ensureActivitiesVisibleLocked(starting, changes);78     }79     if (values != null && mWindowManager != null) {80         mWindowManager.setNewConfiguration(mConfiguration);81     }82     return kept;83 }

  updateConfigurationLocked中主要做了两件事:
  (1),改变现在的 configuration(这是一个系统配置的类,有兴趣的可以去了解下);
  (2),确保所有正在运行的Activity都运行改变后的configuration。下面可以看看他到底是怎么完成这两件事的。
  首先,通过updateFrom(values)判断是不是真的语言发生了变化,如果改变了,从if条件走,在if里面,前面做一些判断之类的工作,到此也完成了第一步的工作。最重要的是for循环里面的操作,首先得到了所有运行过的app的集合,然后对每个app调用scheduleConfigurationChanged()方法,进行语言的切换工作。

  scheduleConfigurationChanged是在ActivityThread中,这个方执行了 updatePendingConfiguration(config)和 queueOrSendMessage(H.CONFIGURATION_CHANGED, config)两个方法。前面一个方法是更新Configuration;最主要的操作在queueOrSendMessage()里面的handleConfigurationChanged((Configuration)msg.obj, null)方法中。

  接着对handleConfigurationChanged进行分析,从中我们不难发现applyConfigurationToResourcesLocked()这个是一个重新配置资源的函数,performConfigurationChanged(callbacks.get(i), config)这个方法是执行Configuration的改变。即最终完成语言的切换。

  详细的分析下applyConfigurationToResourcesLocked做了哪些工作,updateFrom(config) 把config更新到Configuration中,后面 最主要的是在while () 中做了资源更新和删除就资源的操作。

  performConfigurationChanged方法中,这是完成语言切换的最后一步了,首先判断当前activity的config和新的config是否一样,如果是一样什么都不做;如果不一样,则重启app,重新加载资源达到切换语言。

  总结语言切换的大概流程是,判断configuration中的local即语言是不是有改变,如果有改变即为要切换语言。执行切换语言的时候,对那些已经运行过的程序,执行一个资源的清除和重新加载的过程,就完成了整个系统的语言切换。

  

Android平台语言Locale设置流程分析