Android 切换系统语言源码分析(上)

Wesley13
• 阅读 755

转载请标明出处: 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 
        }  
    }  
  • 从注释可以看出, 只要本地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 starting 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 * delta 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 only 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). *
    *

    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~ 打哈欠了睡觉了~

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java获得今日零时零分零秒的时间(Date型)
publicDatezeroTime()throwsParseException{    DatetimenewDate();    SimpleDateFormatsimpnewSimpleDateFormat("yyyyMMdd00:00:00");    SimpleDateFormatsimp2newS
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这