Android语言切换的源码分析

本文深入解析了Android系统中语言切换的工作原理,从源码层面详细介绍了如何通过更新Configuration对象来改变应用程序的语言设置。

前言

前篇阐述了Android语言切换的方法,那么这一篇文章我们就深入了解一下Android语言切换的原理吧。

Android语言切换的方法


正文

1、进入语言切换的入口类LocalePicker,找到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.setLocale(locale);
            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
        }
    }

当locale发生改变的时候就会请求系统更新语言设置。ActivityManagerNative.getDefault()获取了ActivityManagerService的代理类,然后调这个类的updateConfiguration(config)的方法。

public void updateConfiguration(Configuration values) {  
        //进行权限校验
        enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,  
                "updateConfiguration()");  
  
        synchronized(this) {
            final long origId = Binder.clearCallingIdentity();  
            if (values != null) {  
                Settings.System.clearConfiguration(values);  
            }  
            updateConfigurationLocked(values, null, false, false);  
            Binder.restoreCallingIdentity(origId);  
        }  
    }  

首先会进行权限的校验,然后清除Binder调用标志,再通过Settings.System.clearConfiguration(values)清除原有Configuration参数配置,然后再赋予新的参数配置。最后调用updateConfigurationLocaked()方法。

第一段主要方法newConfig.updateFrom(values)

/** 
     * 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;  
      
        if (values != null) {  
            Configuration newConfig = new Configuration(mConfiguration);  
            changes = newConfig.updateFrom(values);  
            if (changes != 0) {  
               
               .....   
              
                mConfigurationSeq++;  
                if (mConfigurationSeq <= 0) {  
                    mConfigurationSeq = 1;  
                }  
                newConfig.seq = mConfigurationSeq;  
                mConfiguration = newConfig;  
               
  
                final Configuration configCopy = new Configuration(mConfiguration);  
  
                AttributeCache ac = AttributeCache.instance();  
                if (ac != null) {  
                    ac.updateConfiguration(configCopy);  
                }  
  
第二段主要关注for循环
                // 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) {  
                    }  
                }  
                ......
            }  
        }  
把此方法截取分为两段来看,第一段最主要的方法就是newConfig.update()方法,这个方法判断我们修改了语言列表,如果修改了,就返回大于0的值。第二段就是for循环相关的了,这一段主要就是遍历所有的应用,然后对每个应用调用scheduleConfigurationChanged()方法进行语言切换。

  public @Config int updateFrom(@NonNull Configuration delta) {
        //初始化变量,用于判断语言是否变化
        int changed = 0;
  
        ...

        if (delta.userSetLocale && (!userSetLocale || ((changed & ActivityInfo.CONFIG_LOCALE) != 0)))
        {
            changed |= ActivityInfo.CONFIG_LOCALE;
            userSetLocale = true;
        }
	return changed;
    }
 我们再查看一下
scheduleConfigurationChanged()这个方法,这个方法是通过binder远程调用在 

ActivityThread中的方法,这个方法主要执行了updatePendingConfiguration()和queueOrSendMessage()两个方法。其中updatePendingConfiguration()就是用于更新Configuration的,queueOrSendMessage()是通过调用handleConfigurationChanged()实现功能的。

public void scheduleConfigurationChanged(Configuration config) {  
            updatePendingConfiguration(config);  
            queueOrSendMessage(H.CONFIGURATION_CHANGED, config);  
        }  
下面是for循环内的

 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) {  
                    }  
                }  
我们再查看一下scheduleConfigurationChanged()这个方法,这个方法是通过binder远程调用在
ActivityThread中的方法,这个方法主要执行了updatePendingConfiguration()和queueOrSendMessage()两个方法。其中updatePendingConfiguration()就是用于更新Configuration的,queueOrSendMessage()是通过调用handleConfigurationChanged()实现功能的。

public void scheduleConfigurationChanged(Configuration config) {  
            updatePendingConfiguration(config);  
            queueOrSendMessage(H.CONFIGURATION_CHANGED, config);  
        }  
我们接着查看handleConfigurationChanged()这个方法,在这个方法中主要有applyConfigurationToResourcesLocked()和performConfigurationChanged()。上一个方法主要重新将Configuration的参数配置到资源上面,后一个方法主要是执行Configuration的改变、完成语言的切换。
final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {  
   
       applyConfigurationToResourcesLocked(config, compat);  
       
       if (callbacks != null) {  
           final int N = callbacks.size();  
           for (int i=0; i<N; i++) {  
               performConfigurationChanged(callbacks.get(i), config);  
           }  
       }  

在applyConfigurationToResuourcesLocked()中,Resources.updateSystemConfiguration()将config更新到Resource上面,在通过while循环删除mActiveResources旧的资源,更新新的资源。
final boolean applyConfigurationToResourcesLocked(Configuration config,  
            CompatibilityInfo compat) {  
      
        DisplayMetrics dm = getDisplayMetricsLocked(null, true);  
 
  
        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);  
       
            } else {  
                it.remove();  
            }  
        }  
   
    }  
对于performConfigurationChanged()这个方法,判断activity的config和传递过来的config是否一样,如果不一样就将shouldChangeConfig=true,如果shouldChangeConfig=true,就执行activity的onConfigurationChanged(config)方法。
private final void performConfigurationChanged(ComponentCallbacks2 cb, Configuration config) {  
     
        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);  
   
        }  
    }  

总结


其实就是判断Configuration的locale有没有改变,有改变就清除旧的Resource资源,并将新的config赋予Resource,并重新执行加载Resource资源。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值