转载请标明出处: http://blog.csdn.net/u011974987/article/details/50793343;
本文出自:【Xiho的博客】

以前了解Android的多语言实现很简单,可以在不同的语言环境下使用不同的资源,就做好相应的语言适配就好,但是一直没有实际使用过。 最近公司的项目要用到多国语言切换,并且还是和手机上系统设置里面的语言切换功能一样,于是就上网查了下资料。一般都是在应用类实现多国语言切换,这个是很简单。而我想切换整个系统的语言。由于谷歌没有把系统设置里面的接口给开放出来,所以就只好去查看它的源码了~

  • android语言切换是在:
packages/apps/Settings/com/android/settings/LocalePicker.java

的updateLocale()函数中调用,
源码如下:

/** * 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. */      public static void updateLocale(Locale locale) {          try {              IActivityManager am = ActivityManagerNative.getDefault();              Configuration config = am.getConfiguration();              config.locale = locale;              // indicate this isn't some passing default - the user wants this remembered             config.userSetLocale = true;              am.updateConfiguration(config);              // Trigger the dirty bit for the Settings Provider.             BackupManager.dataChanged("com.android.providers.settings");          } catch (RemoteException e) {              // Intentionally left blank         }      }  
  • 从注释可以看出, 只要本地local改变就会调用该函数. 查看ActivityManagerNative的getDefault()可以看到, 该函数返回的是远程服务对象ActivityManagerServices.java在本地的一个代理. 最终调用的是ActivityManagerService.java中的updateConfiguration()函数.
public void updateConfiguration(Configuration values) {          enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,                  "updateConfiguration()");          synchronized(this) {              if (values == null && mWindowManager != null) {                  // sentinel: fetch the current configuration from the window manager                 values = mWindowManager.computeNewConfiguration();              }              if (mWindowManager != null) {                  mProcessList.applyDisplaySize(mWindowManager);              }              final long origId = Binder.clearCallingIdentity();              if (values != null) {                  Settings.System.clearConfiguration(values);              }              updateConfigurationLocked(values, null, false, false);              Binder.restoreCallingIdentity(origId);          }      }  
  • 该函数, 首先进行的是权限的校验. 然后调用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;          boolean kept = true;          if (values != null) {              Configuration newConfig = new Configuration(mConfiguration);              changes = newConfig.updateFrom(values);              if (changes != 0) {                  if (DEBUG_SWITCH || DEBUG_CONFIGURATION) {                      Slog.i(TAG, "Updating configuration to: " + values);                  }                  EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes);                  if (values.locale != null && !initLocale) {                      saveLocaleLocked(values.locale,                                        !values.locale.equals(mConfiguration.locale),                                       values.userSetLocale, values.simSetLocale);                  }                  mConfigurationSeq++;                  if (mConfigurationSeq <= 0) {                      mConfigurationSeq = 1;                  }                  newConfig.seq = mConfigurationSeq;                  mConfiguration = newConfig;                  Slog.i(TAG, "Config changed: " + newConfig);                  final Configuration configCopy = new Configuration(mConfiguration);                  AttributeCache ac = AttributeCache.instance();                  if (ac != null) {                      ac.updateConfiguration(configCopy);                  }                  // Make sure all resources in our process are updated                 // right now, so that anyone who is going to retrieve                 // resource values after we return will be sure to get                 // the new ones. This is especially important during                 // boot, where the first config change needs to guarantee                 // all resources have that config before following boot                 // code is executed.                 mSystemThread.applyConfigurationToResources(configCopy);                  if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {                      Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);                      msg.obj = new Configuration(configCopy);                      mHandler.sendMessage(msg);                  }                  for (int i=mLruProcesses.size()-1; i>=0; i--) {                      ProcessRecord app = mLruProcesses.get(i);                      try {                          if (app.thread != null) {                              if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending to proc "                                      + app.processName + " new config " + mConfiguration);                              app.thread.scheduleConfigurationChanged(configCopy);                          }                      } catch (Exception e) {                      }                  }                  Intent intent = new Intent(Intent.ACTION_CONFIGURATION_CHANGED);                  intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY                          | Intent.FLAG_RECEIVER_REPLACE_PENDING);                  broadcastIntentLocked(null, null, intent, null, null, 0, null, null,                          null, false, false, MY_PID, Process.SYSTEM_UID);                  if ((changes&ActivityInfo.CONFIG_LOCALE) != 0) {                      broadcastIntentLocked(null, null,                              new Intent(Intent.ACTION_LOCALE_CHANGED),                              null, null, 0, null, null,                              null, false, false, MY_PID, Process.SYSTEM_UID);                  }              }          }          if (changes != 0 && starting == null) {              // If the configuration changed, and the caller is not already             // in the process of starting an activity, then find the top             // activity to check if its configuration needs to change.             starting = mMainStack.topRunningActivityLocked(null);          }          if (starting != null) {              kept = mMainStack.ensureActivityConfigurationLocked(starting, changes);              // And we need to make sure at this point that all other activities             // are made visible with the correct configuration.             mMainStack.ensureActivitiesVisibleLocked(starting, changes);          }          if (values != null && mWindowManager != null) {              mWindowManager.setNewConfiguration(mConfiguration);          }          return kept;      }  
  • 整个语言切换就在这个函数中完成. 咋一看似乎没感觉到该函数做了哪些事情. 我们首先来看注释: 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这个流程. 继续看代码。
        for (int i=mLruProcesses.size()-1; i>=0; i--) {                      ProcessRecord app = mLruProcesses.get(i);                      try {                          if (app.thread != null) {                              if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending to proc "                                      + app.processName + " new config " + mConfiguration);                              app.thread.scheduleConfigurationChanged(configCopy);                          }                      } catch (Exception e) {                      }                  }  
  • 首先看到的是mLurProcesses 是ArrayList类型. LRU : Least Recently Used保存所有运行过的进程. ProcessRecord进程类, 一个apk文件运行时会对应一个进程. app.thread. 此处的thread代表的是ApplicationThreadNative.java类型. 然后调用其scheduleConfigurationChanged(); 查看该函数。
    public final void scheduleConfigurationChanged(Configuration config)              throws RemoteException {          Parcel data = Parcel.obtain();         data.writeInterfaceToken(IApplicationThread.descriptor);         config.writeToParcel(data, 0);         mRemote.transact(SCHEDULE_CONFIGURATION_CHANGED_TRANSACTION, data, null,                 IBinder.FLAG_ONEWAY);          data.recycle();     }  
  • 又是通过binder调用, 所以 , binder在android中是一个很重要的概念. 此处远程调用的是ActivityThread.java中的私有内部内ApplicationThread。
    private class ApplicationThread extends ApplicationThreadNative {          private static final String HEAP_COLUMN = "%13s %8s %8s %8s %8s %8s %8s";          private static final String ONE_COUNT_COLUMN = "%21s %8d";          private static final String TWO_COUNT_COLUMNS = "%21s %8d %21s %8d";          private static final String TWO_COUNT_COLUMNS_DB = "%21s %8d %21s %8d";          private static final String DB_INFO_FORMAT = " %8s %8s %14s %14s %s";          ...          public void scheduleConfigurationChanged(Configuration config) {              updatePendingConfiguration(config);              queueOrSendMessage(H.CONFIGURATION_CHANGED, config);          }          ...  }  
  • 而ApplicationThread中的handler的CONFIGURATION_CHANGED是调用handleConfigurationChanged()。
    final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {         ArrayList<ComponentCallbacks2> callbacks = null;      ...         ...         applyConfigurationToResourcesLocked(config, compat);         ...         callbacks = collectComponentCallbacksLocked(false, config);         ...         if (callbacks != null) {             final int N = callbacks.size();             for (int i=0; i<N; i++) {                 performConfigurationChanged(callbacks.get(i), config);             }         }  
  • 这个函数首先是调用applyConfigurationToResourcesLocked(). 看函数名大概可以猜想到: 将configuration应用到resources.这里configuration改变的是local 本地语言. 那而resources资源包含语言包吗?
    final boolean applyConfigurationToResourcesLocked(Configuration config,              CompatibilityInfo compat) {          int changes = mResConfiguration.updateFrom(config);          DisplayMetrics dm = getDisplayMetricsLocked(null, true);          if (compat != null && (mResCompatibilityInfo == null ||                  !mResCompatibilityInfo.equals(compat))) {              mResCompatibilityInfo = compat;              changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT                      | ActivityInfo.CONFIG_SCREEN_SIZE                      | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;          }          ...          Resources.updateSystemConfiguration(config, dm, compat);          ...          Iterator<WeakReference<Resources>> it =              mActiveResources.values().iterator();          while (it.hasNext()) {              WeakReference<Resources> v = it.next();              Resources r = v.get();              if (r != null) {                  if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "                          + r + " config to: " + config);                  r.updateConfiguration(config, dm, compat);                  //Slog.i(TAG, "Updated app resources " + v.getKey()                  //        + " " + r + ": " + r.getConfiguration());              } else {                  //Slog.i(TAG, "Removing old resources " + v.getKey());                  it.remove();              }          }          return changes != 0;      }  
  • Resources.updateSystemConfiguration()清除一部分系统资源, 并且将config更新到Resources, 而Resources包含了一个AssetManager对象, 该对象的核心实现是在AssetManager.cpp中完成的. 然后循环清空mActivityResources资源. 再回到handleConfigurationChanged()函数, 执行完updateSystemConfiguration后, 会循环该进程的所有activity:

if (callbacks != null) {

        final int N = callbacks.size();        for (int i=0; i<N; i++) {            performConfigurationChanged(callbacks.get(i), config);        }    }

再来看performConfigurationChanged的实现:

private final void performConfigurationChanged(              ComponentCallbacks2 cb, Configuration config) {          // Only for Activity objects, check that they actually call up to their         // superclass implementation. ComponentCallbacks2 is an interface, so         // we check the runtime type and act accordingly.         Activity activity = (cb instanceof Activity) ? (Activity) cb : null;          if (activity != null) {              activity.mCalled = false;          }          boolean shouldChangeConfig = false;          if ((activity == null) || (activity.mCurrentConfig == null)) {              shouldChangeConfig = true;          } else {              // If the new config is the same as the config this Activity             // is already running with then don't bother calling             // onConfigurationChanged             int diff = activity.mCurrentConfig.diff(config);              if (diff != 0) {                  // If this activity doesn't handle any of the config changes                 // then don't bother calling onConfigurationChanged as we're                 // going to destroy it.                 if ((~activity.mActivityInfo.getRealConfigChanged() & diff) == 0) {                      shouldChangeConfig = true;                  }              }          }          if (DEBUG_CONFIGURATION) Slog.v(TAG, "Config callback " + cb                  + ": shouldChangeConfig=" + shouldChangeConfig);          if (shouldChangeConfig) {              cb.onConfigurationChanged(config);              if (activity != null) {                  if (!activity.mCalled) {                      throw new SuperNotCalledException(                              "Activity " + activity.getLocalClassName() +                          " did not call through to super.onConfigurationChanged()");                  }                  activity.mConfigChangeFlags = 0;                  activity.mCurrentConfig = new Configuration(config);              }          }      }  
  • 该函数判断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;         mFragments.dispatchConfigurationChanged(newConfig);         if (mWindow != null) {             // Pass the configuration changed event to the window             mWindow.onConfigurationChanged(newConfig);         }         if (mActionBar != null) {             // Do this last; the action bar will need to access             // view changes from above.             mActionBar.onConfigurationChanged(newConfig);         }     }  
  • 查看注释, 大概意思是: 如果你的activity运行 , 设备信息有改变(即configuration改变)时由系统调用. 如果你在manifest.xml中配置了configChnages属性则表示有你自己来处理configuration change. 否则就重启当前这个activity. 而重启之前, 旧的resources已经被清空, 那么就会装载新的资源, 整个过程就完成了语言切换后 , 能够让所有app使用新的语言。
  • 上面这些就是对Android 系统里面的语言切换进行了源码分析,就先分析到这里;有些东西我也不是很看懂,能力有限~
  • 明天我们再来分析怎么来实现Android 系统语言切换的功能。 Android 切换系统语言功能实现!
  • O(∩_∩)O~ 打哈欠了睡觉了~

更多相关文章

  1. Android的WiFi子系统架构
  2. Android音频系统探究——从SoundPool到AudioHardware
  3. Android多语言支持以及各国语言Values文件夹命名规则
  4. 让你自己写的Android的Launcher成为系统中第一个启动的,也是唯一
  5. 一、Android体系及系统架构
  6. Android学习心得之二——Android系统架构图及简单的系统架构介绍
  7. Android获取系统的硬件信息、系统版本以及如何检测ROM类型

随机推荐

  1. Android 启动过程详解
  2. (二)Android事件分发机制 - ViewGroup篇
  3. Android字体(一)
  4. Android消息循环实现原理分析
  5. Google Play Store 应用无法安装解决方案
  6. android bitmap compress(图片压缩)
  7. Ubuntu安装Android的SDK
  8. Android(安卓)greenDao开源数据库框架
  9. Android(安卓)Display System -- Surface
  10. Android(安卓)UI界面刷新与交互