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 user
11         // wants this remembered
12         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 blank
19     }
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 = 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 manager
23             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 feature
18             }
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_ONLY
54                     | Intent.FLAG_RECEIVER_REPLACE_PENDING
55                     /*| Intent.FLAG_RECEIVER_FOREGROUND*/); // downgrade to background
56             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 background
62                 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即语言是不是有改变,如果有改变即为要切换语言。执行切换语言的时候,对那些已经运行过的程序,执行一个资源的清除和重新加载的过程,就完成了整个系统的语言切换。

  

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。