转载请标明出处: http://blog.csdn.net/u011974987/article/details/50793343;
本文出自:【二锅头的博客】
以前了解Android的多语言实现很简单,可以在不同的语言环境下使用不同的资源,就做好相应的语言适配就好,但是一直没有实际使用过。 最近公司的项目要用到多国语言切换,并且还是和手机上系统设置里面的语言切换功能一样,于是就上网查了下资料。一般都是在应用类实现多国语言切换,这个是很简单。而我想切换整个系统的语言。由于谷歌没有把系统设置里面的接口给开放出来,所以就只好去查看它的源码了~
-
android语言切换是在:
packages/apps/Settings/com/android/settings/LocalePicker.java
的updateLocale()函数中调用,
源码如下:
1/** * 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. */ 2 public static void updateLocale(Locale locale) { 3 try { 4 IActivityManager am = ActivityManagerNative.getDefault(); 5 Configuration config = am.getConfiguration(); 6 7 config.locale = locale; 8 9 // indicate this isn't some passing default - the user wants this remembered 10 config.userSetLocale = true; 11 12 am.updateConfiguration(config); 13 // Trigger the dirty bit for the Settings Provider. 14 BackupManager.dataChanged("com.android.providers.settings"); 15 } catch (RemoteException e) { 16 // Intentionally left blank 17 } 18 }
-
从注释可以看出, 只要本地local改变就会调用该函数. 查看ActivityManagerNative的getDefault()可以看到, 该函数返回的是远程服务对象ActivityManagerServices.java在本地的一个代理. 最终调用的是ActivityManagerService.java中的updateConfiguration()函数.
public void updateConfiguration(Configuration values) {
enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
"updateConfiguration()");1 synchronized(this) { 2 if (values == null && mWindowManager != null) { 3 // sentinel: fetch the current configuration from the window manager 4 values = mWindowManager.computeNewConfiguration(); 5 } 6 7 if (mWindowManager != null) { 8 mProcessList.applyDisplaySize(mWindowManager); 9 } 10 11 final long origId = Binder.clearCallingIdentity(); 12 if (values != null) { 13 Settings.System.clearConfiguration(values); 14 } 15 updateConfigurationLocked(values, null, false, false); 16 Binder.restoreCallingIdentity(origId); 17 } 18} -
该函数, 首先进行的是权限的校验. 然后调用updateConfigurationLocked()函数.
/** * 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;1 boolean kept = true; 2 3 if (values != null) { 4 Configuration newConfig = new Configuration(mConfiguration); 5 changes = newConfig.updateFrom(values); 6 if (changes != 0) { 7 if (DEBUG_SWITCH || DEBUG_CONFIGURATION) { 8 Slog.i(TAG, "Updating configuration to: " + values); 9 } 10 11 EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes); 12 13 if (values.locale != null && !initLocale) { 14 saveLocaleLocked(values.locale, 15 !values.locale.equals(mConfiguration.locale), 16 values.userSetLocale, values.simSetLocale); 17 } 18 19 20 mConfigurationSeq++; 21 if (mConfigurationSeq <= 0) { 22 mConfigurationSeq = 1; 23 } 24 newConfig.seq = mConfigurationSeq; 25 mConfiguration = newConfig; 26 Slog.i(TAG, "Config changed: " + newConfig); 27 28 final Configuration configCopy = new Configuration(mConfiguration); 29 30 AttributeCache ac = AttributeCache.instance(); 31 if (ac != null) { 32 ac.updateConfiguration(configCopy); 33 } 34 35 // Make sure all resources in our process are updated 36 // right now, so that anyone who is going to retrieve 37 // resource values after we return will be sure to get 38 // the new ones. This is especially important during 39 // boot, where the first config change needs to guarantee 40 // all resources have that config before following boot 41 // code is executed. 42 mSystemThread.applyConfigurationToResources(configCopy); 43 44 if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) { 45 Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG); 46 msg.obj = new Configuration(configCopy); 47 mHandler.sendMessage(msg); 48 } 49 50 for (int i=mLruProcesses.size()-1; i>=0; i--) { 51 ProcessRecord app = mLruProcesses.get(i); 52 try { 53 if (app.thread != null) { 54 if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending to proc " 55 + app.processName + " new config " + mConfiguration); 56 app.thread.scheduleConfigurationChanged(configCopy); 57 } 58 } catch (Exception e) { 59 } 60 } 61 Intent intent = new Intent(Intent.ACTION_CONFIGURATION_CHANGED); 62 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY 63 | Intent.FLAG_RECEIVER_REPLACE_PENDING); 64 broadcastIntentLocked(null, null, intent, null, null, 0, null, null, 65 null, false, false, MY_PID, Process.SYSTEM_UID); 66 if ((changes&ActivityInfo.CONFIG_LOCALE) != 0) { 67 broadcastIntentLocked(null, null, 68 new Intent(Intent.ACTION_LOCALE_CHANGED), 69 null, null, 0, null, null, 70 null, false, false, MY_PID, Process.SYSTEM_UID); 71 } 72 73 } 74 } 75 76 if (changes != 0 && starting == null) { 77 // If the configuration changed, and the caller is not already 78 // in the process of starting an activity, then find the top 79 // activity to check if its configuration needs to change. 80 starting = mMainStack.topRunningActivityLocked(null); 81 } 82 83 if (starting != null) { 84 kept = mMainStack.ensureActivityConfigurationLocked(starting, changes); 85 // And we need to make sure at this point that all other activities 86 // are made visible with the correct configuration. 87 mMainStack.ensureActivitiesVisibleLocked(starting, changes); 88 } 89 90 if (values != null && mWindowManager != null) { 91 mWindowManager.setNewConfiguration(mConfiguration); 92 } 93 94 return kept; 95} -
整个语言切换就在这个函数中完成. 咋一看似乎没感觉到该函数做了哪些事情. 我们首先来看注释: 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这个流程. 继续看代码。
1 for (int i=mLruProcesses.size()-1; i>=0; i--) { 2 ProcessRecord app = mLruProcesses.get(i); 3 try { 4 if (app.thread != null) { 5 if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending to proc " 6 + app.processName + " new config " + mConfiguration); 7 app.thread.scheduleConfigurationChanged(configCopy); 8 } 9 } catch (Exception e) { 10 } 11 } -
首先看到的是mLurProcesses 是ArrayList类型. LRU : Least Recently Used保存所有运行过的进程. ProcessRecord进程类, 一个apk文件运行时会对应一个进程. app.thread. 此处的thread代表的是ApplicationThreadNative.java类型. 然后调用其scheduleConfigurationChanged(); 查看该函数。
1public final void scheduleConfigurationChanged(Configuration config) 2 throws RemoteException { 3 Parcel data = Parcel.obtain(); 4 data.writeInterfaceToken(IApplicationThread.descriptor); 5 config.writeToParcel(data, 0); 6 mRemote.transact(SCHEDULE_CONFIGURATION_CHANGED_TRANSACTION, data, null, 7 IBinder.FLAG_ONEWAY); 8 data.recycle(); 9} -
又是通过binder调用, 所以 , binder在android中是一个很重要的概念. 此处远程调用的是ActivityThread.java中的私有内部内ApplicationThread。
1private class ApplicationThread extends ApplicationThreadNative { 2 private static final String HEAP_COLUMN = "%13s %8s %8s %8s %8s %8s %8s"; 3 private static final String ONE_COUNT_COLUMN = "%21s %8d"; 4 private static final String TWO_COUNT_COLUMNS = "%21s %8d %21s %8d"; 5 private static final String TWO_COUNT_COLUMNS_DB = "%21s %8d %21s %8d"; 6 private static final String DB_INFO_FORMAT = " %8s %8s %14s %14s %s"; 7 8 9 ... 10 public void scheduleConfigurationChanged(Configuration config) { 11 updatePendingConfiguration(config); 12 queueOrSendMessage(H.CONFIGURATION_CHANGED, config); 13 } 14 ...}
-
而ApplicationThread中的handler的CONFIGURATION_CHANGED是调用handleConfigurationChanged()。
1final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) { 2 3 ArrayList<ComponentCallbacks2> callbacks = null; 4 5... ... 6 applyConfigurationToResourcesLocked(config, compat); 7 8 ... 9 10 callbacks = collectComponentCallbacksLocked(false, config); 11 ... 12 13 if (callbacks != null) { 14 final int N = callbacks.size(); 15 for (int i=0; i<N; i++) { 16 performConfigurationChanged(callbacks.get(i), config); 17 } 18 } -
这个函数首先是调用applyConfigurationToResourcesLocked(). 看函数名大概可以猜想到: 将configuration应用到resources.这里configuration改变的是local 本地语言. 那而resources资源包含语言包吗?
1final boolean applyConfigurationToResourcesLocked(Configuration config, 2 CompatibilityInfo compat) { 3 4 int changes = mResConfiguration.updateFrom(config); 5 DisplayMetrics dm = getDisplayMetricsLocked(null, true); 6 7 8 if (compat != null && (mResCompatibilityInfo == null || 9 !mResCompatibilityInfo.equals(compat))) { 10 mResCompatibilityInfo = compat; 11 changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT 12 | ActivityInfo.CONFIG_SCREEN_SIZE 13 | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; 14 } 15 16 ... 17 18 Resources.updateSystemConfiguration(config, dm, compat); 19 20 ... 21 22 Iterator<WeakReference<Resources>> it = 23 mActiveResources.values().iterator(); 24 while (it.hasNext()) { 25 WeakReference<Resources> v = it.next(); 26 Resources r = v.get(); 27 if (r != null) { 28 if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources " 29 + r + " config to: " + config); 30 r.updateConfiguration(config, dm, compat); 31 //Slog.i(TAG, "Updated app resources " + v.getKey() 32 // + " " + r + ": " + r.getConfiguration()); 33 } else { 34 //Slog.i(TAG, "Removing old resources " + v.getKey()); 35 it.remove(); 36 } 37 } 38 39 return changes != 0; 40} -
Resources.updateSystemConfiguration()清除一部分系统资源, 并且将config更新到Resources, 而Resources包含了一个AssetManager对象, 该对象的核心实现是在AssetManager.cpp中完成的. 然后循环清空mActivityResources资源. 再回到handleConfigurationChanged()函数, 执行完updateSystemConfiguration后, 会循环该进程的所有activity:
if (callbacks != null) {
1 final int N = callbacks.size(); 2 for (int i=0; i<N; i++) { 3 performConfigurationChanged(callbacks.get(i), config); 4 } 5 }
再来看performConfigurationChanged的实现:
1private final void performConfigurationChanged( 2 ComponentCallbacks2 cb, Configuration config) { 3 // Only for Activity objects, check that they actually call up to their 4 // superclass implementation. ComponentCallbacks2 is an interface, so 5 // we check the runtime type and act accordingly. 6 Activity activity = (cb instanceof Activity) ? (Activity) cb : null; 7 if (activity != null) { 8 activity.mCalled = false; 9 } 10 11 boolean shouldChangeConfig = false; 12 if ((activity == null) || (activity.mCurrentConfig == null)) { 13 shouldChangeConfig = true; 14 } else { 15 16 // If the new config is the same as the config this Activity 17 // is already running with then don't bother calling 18 // onConfigurationChanged 19 int diff = activity.mCurrentConfig.diff(config); 20 if (diff != 0) { 21 // If this activity doesn't handle any of the config changes 22 // then don't bother calling onConfigurationChanged as we're 23 // going to destroy it. 24 if ((~activity.mActivityInfo.getRealConfigChanged() & diff) == 0) { 25 shouldChangeConfig = true; 26 } 27 } 28 } 29 30 if (DEBUG_CONFIGURATION) Slog.v(TAG, "Config callback " + cb 31 + ": shouldChangeConfig=" + shouldChangeConfig); 32 if (shouldChangeConfig) { 33 cb.onConfigurationChanged(config); 34 35 if (activity != null) { 36 if (!activity.mCalled) { 37 throw new SuperNotCalledException( 38 "Activity " + activity.getLocalClassName() + 39 " did not call through to super.onConfigurationChanged()"); 40 } 41 activity.mConfigChangeFlags = 0; 42 activity.mCurrentConfig = new Configuration(config); 43 } 44 } 45 }
-
该函数判断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;1 mFragments.dispatchConfigurationChanged(newConfig); 2 3 if (mWindow != null) { 4 // Pass the configuration changed event to the window 5 mWindow.onConfigurationChanged(newConfig); 6 } 7 8 if (mActionBar != null) { 9 // Do this last; the action bar will need to access 10 // view changes from above. 11 mActionBar.onConfigurationChanged(newConfig); 12 }}
-
查看注释, 大概意思是: 如果你的activity运行 , 设备信息有改变(即configuration改变)时由系统调用. 如果你在manifest.xml中配置了configChnages属性则表示有你自己来处理configuration change. 否则就重启当前这个activity. 而重启之前, 旧的resources已经被清空, 那么就会装载新的资源, 整个过程就完成了语言切换后 , 能够让所有app使用新的语言。
-
上面这些就是对Android 系统里面的语言切换进行了源码分析,就先分析到这里;有些东西我也不是很看懂,能力有限~
-
明天我们再来分析怎么来实现Android 系统语言切换的功能。 Android 切换系统语言功能实现!
-
O(∩_∩)O~ 打哈欠了睡觉了~