Android 切換系統語言源碼分析

來自: http://blog.csdn.net/u011974987/article/details/50793343

以前了解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 
    }  
}  </pre> 
  • 從注釋可以看出, 只要本地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);  
    }  
}  </pre> 
  • 該函數, 首先進行的是權限的校驗. 然后調用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;  
}  </pre> 
  • 整個語言切換就在這個函數中完成. 咋一看似乎沒感覺到該函數做了哪些事情. 我們首先來看注釋: 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;  
}  </pre> 
  • 因為語言改變了, 那么 (!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) {  
                }  
            }  </pre> 
  • 首先看到的是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。
  • </ul>

        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);  
        }  
        ...  
    

    } </pre>

    • 而ApplicationThread中的handler的CONFIGURATION_CHANGED是調用handleConfigurationChanged()。
    • </ul>

          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);  
             }  
         }  </pre> 
      
      • 這個函數首先是調用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;  
      }  </pre> 
      
      • 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);  
              }  
          }  
      }  </pre> 
      
      • 該函數判斷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);  
         }  
      

      } </pre>

      • 查看注釋, 大概意思是: 如果你的activity運行 , 設備信息有改變(即configuration改變)時由系統調用. 如果你在manifest.xml中配置了configChnages屬性則表示有你自己來處理configuration change. 否則就重啟當前這個activity. 而重啟之前, 舊的resources已經被清空, 那么就會裝載新的資源, 整個過程就完成了語言切換后 , 能夠讓所有app使用新的語言。
      • 上面這些就是對Android 系統里面的語言切換進行了源碼分析,就先分析到這里;有些東西我也不是很看懂,能力有限~
      • 明天我們再來分析怎么來實現Android 系統語言切換的功能。 Android 切換系統語言功能實現
      • O(∩_∩)O~ 打哈欠了睡覺了~
      • </ul> </div>

 本文由用戶 ChastityRyd 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!