语言切换机制分析

打开本地路径:D:\android\sdk\sources\android-26\com\android\internal\app,其中类LocalePicker的updateLocales(LocaleList locales) 方法便是系统切换语言的入口:

/**
     * 请求系统更新locale列表,开发者需要注意的是,当切换locale时系统看起来会停顿一下     
     */
    public static void updateLocales(LocaleList locales) {
        try {
            final IActivityManager am = ActivityManager.getService();
            final Configuration config = am.getConfiguration();

            config.setLocales(locales);
            config.userSetLocale = true;

            am.updatePersistentConfiguration(config);
            // Trigger the dirty bit for the Settings Provider.
            BackupManager.dataChanged("com.android.providers.settings");
        } catch (RemoteException e) {
            // Intentionally left blank
        }
    }

只要本地locale改变便会调用该方法。ActivityManager.getService()可以看到, 该方法返回的是远程服务对象ActivityManagerServices.java在本地的一个代理,最终调用的是ActivityManagerService.java中的updatePersistentConfiguration()方法。

    @Override
    public void updatePersistentConfiguration(Configuration values) {
        enforceCallingPermission(CHANGE_CONFIGURATION, "updatePersistentConfiguration()");
        enforceWriteSettingsPermission("updatePersistentConfiguration()");
        if (values == null) {
            throw new NullPointerException("Configuration must not be null");
        }

        int userId = UserHandle.getCallingUserId();

        synchronized(this) {
            updatePersistentConfigurationLocked(values, userId);
        }
    }

这个方法首先进行权限的验证,然后调用updatePersistentConfigurationLocked方法。

    private void updatePersistentConfigurationLocked(Configuration values, @UserIdInt int userId) {
        final long origId = Binder.clearCallingIdentity();
        try {
            updateConfigurationLocked(values, null, false, true, userId, false /* deferResume */);
        } finally {
            Binder.restoreCallingIdentity(origId);
        }
    }

    private boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
            boolean initLocale, boolean persistent, int userId, boolean deferResume) {
        return updateConfigurationLocked(values, starting, initLocale, persistent, userId,
                deferResume, null /* result */);
    }

    /**
     * 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 userId is only used when persistent parameter is set to true to persist configuration
     *               for that particular user
     */
    private boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
            boolean initLocale, boolean persistent, int userId, boolean deferResume,
            UpdateConfigurationResult result) {
        int changes = 0;
        boolean kept = true;

        if (mWindowManager != null) {
            mWindowManager.deferSurfaceLayout();
        }
        try {
            if (values != null) {
                changes = updateGlobalConfiguration(values, initLocale, persistent, userId,
                        deferResume);
            }

            kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
        } finally {
            if (mWindowManager != null) {
                mWindowManager.continueSurfaceLayout();
            }
        }

        if (result != null) {
            result.changes = changes;
            result.activityRelaunched = !kept;
        }
        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做了什么操作

/** Update default (global) configuration and notify listeners about changes. */
    private int updateGlobalConfiguration(@NonNull Configuration values, boolean initLocale,
            boolean persistent, int userId, boolean deferResume) {
        mTempConfig.setTo(getGlobalConfiguration());
        final int changes = mTempConfig.updateFrom(values);
        if (changes == 0) {
            // Since calling to Activity.setRequestedOrientation leads to freezing the window with
            // setting WindowManagerService.mWaitingForConfig to true, it is important that we call
            // performDisplayOverrideConfigUpdate in order to send the new display configuration
            // (even if there are no actual changes) to unfreeze the window.
            performDisplayOverrideConfigUpdate(values, deferResume, DEFAULT_DISPLAY);
            return 0;
        }

        if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.i(TAG_CONFIGURATION,
                "Updating global configuration to: " + values);

        EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes);

        if (!initLocale && !values.getLocales().isEmpty() && values.userSetLocale) {
            final LocaleList locales = values.getLocales();
            int bestLocaleIndex = 0;
            if (locales.size() > 1) {
                if (mSupportedSystemLocales == null) {
                    mSupportedSystemLocales = Resources.getSystem().getAssets().getLocales();
                }
                bestLocaleIndex = Math.max(0, locales.getFirstMatchIndex(mSupportedSystemLocales));
            }
            SystemProperties.set("persist.sys.locale",
                    locales.get(bestLocaleIndex).toLanguageTag());
            LocaleList.setDefault(locales, bestLocaleIndex);
            mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG,
                    locales.get(bestLocaleIndex)));
        }

        mConfigurationSeq = Math.max(++mConfigurationSeq, 1);
        mTempConfig.seq = mConfigurationSeq;

        // Update stored global config and notify everyone about the change.
        mStackSupervisor.onConfigurationChanged(mTempConfig);

        Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + mTempConfig);
        // TODO(multi-display): Update UsageEvents#Event to include displayId.
        mUsageStatsService.reportConfigurationChange(mTempConfig,
                mUserController.getCurrentUserIdLocked());

        // TODO: If our config changes, should we auto dismiss any currently showing dialogs?
        mShowDialogs = shouldShowDialogs(mTempConfig);

        AttributeCache ac = AttributeCache.instance();
        if (ac != null) {
            ac.updateConfiguration(mTempConfig);
        }

        // 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(mTempConfig);

        // We need another copy of global config because we're scheduling some calls instead of
        // running them in place. We need to be sure that object we send will be handled unchanged.
        final Configuration configCopy = new Configuration(mTempConfig);
        if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {
            Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);
            msg.obj = configCopy;
            msg.arg1 = userId;
            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_CONFIGURATION, "Sending to proc "
                            + app.processName + " new config " + configCopy);
                    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
                | Intent.FLAG_RECEIVER_FOREGROUND
                | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
        broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
                AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
                UserHandle.USER_ALL);
        if ((changes & ActivityInfo.CONFIG_LOCALE) != 0) {
            intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
                    | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
                    | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
            if (initLocale || !mProcessesReady) {
                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
            }
            broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
                    AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
                    UserHandle.USER_ALL);
        }

        // Override configuration of the default display duplicates global config, so we need to
        // update it also. This will also notify WindowManager about changes.
        performDisplayOverrideConfigUpdate(mStackSupervisor.getConfiguration(), deferResume,
                DEFAULT_DISPLAY);

        return changes;
    }

从方法的注释可以知道其作用是更新全局存储的config,并通知相关监听类。

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_CONFIGURATION, "Sending to proc "
                            + app.processName + " new config " + configCopy);
                    app.thread.scheduleConfigurationChanged(configCopy);
                }
            } catch (Exception e) {
            }
        }

其中这段代码会遍历程序进程记录,然后调用app.thread.scheduleConfigurationChanged(configCopy);来通知config改变。

app.thread是ApplicationThreadNative类型的,然后调用其scheduleConfigurationChanged()方法。

这里先贴上一段25的源码,效果应该是差不多的吧,哈哈!

    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来做的

未完。。。

猜你喜欢

转载自blog.csdn.net/weixin_34314962/article/details/87030674