首页 > 代码库 > 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设置流程分析