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即语言是不是有改变,如果有改变即为要切换语言。执行切换语言的时候,对那些已经运行过的程序,执行一个资源的清除和重新加载的过程,就完成了整个系统的语言切换。