平台:Android7.1.1 高通MSM8937 骁龙430 红米手机
一、现象
频繁切换语言后快速设置中的运营商名称(中国移动,中国联通,中国电信)不更新或者更新不及时。
二、分析
快速设置中的运营商名称是在SystemUI中处理的,具体处理代码在MobileSignalController.java中
/**
* Updates the network's name based on incoming spn and plmn.
*/
void updateNetworkName(boolean showSpn, String spn, String dataSpn,
boolean showPlmn, String plmn) {
mLastShowSpn = showSpn;
mLastSpn = spn;
mLastDataSpn = dataSpn;
mLastShowPlmn = showPlmn;
mLastPlmn = plmn;
if (CHATTY) {
Log.d("CarrierLabel", "updateNetworkName showSpn=" + showSpn
+ " spn=" + spn + " dataSpn=" + dataSpn
+ " showPlmn=" + showPlmn + " plmn=" + plmn);
}
if (mConfig.showLocale) {
if (showSpn && !TextUtils.isEmpty(spn)) {
spn = getLocalString(spn);
}
if (showSpn && !TextUtils.isEmpty(dataSpn)) {
dataSpn = getLocalString(dataSpn);
}
if (showPlmn && !TextUtils.isEmpty(plmn)) {
plmn = getLocalString(plmn);
}
}
}
private String getLocalString(String originalString) {
return android.util.NativeTextHelper.getLocalString(mContext, originalString,
com.android.internal.R.array.origin_carrier_names,
com.android.internal.R.array.locale_carrier_names);
}
true
- CHINA\u0020\u0020MOBILE
- CMCC
- CHN-UNICOM
- China Mobile
- China Unicom
- China Telecom
- CHN-CT
- 中国移动
- 中国联通
- 中国电信
- 中國移動
- 中國聯通
- 中國電信
- Searching for Service
- China_Mobile
- China_Mobile
- China_Unicom
- China_Mobile
- China_Unicom
- China_Telecom
- China_Telecom
- China_Mobile
- China_Unicom
- China_Telecom
- China_Mobile
- China_Unicom
- China_Telecom
- roamingTextSearching
可以看到在getLocalString方法中传入的origin_carrier_names和locale_carrier_names在配置文件中,
再来看一下具体是怎么获取到最终的string的,getLocalString方法调用了frameworks/base/core/java/android/util/NativeTextHelper.java中的getLocalString方法,最终实现代码如下:
/**
* parse the string to current language.
*
* @param context base context of the application
* @param originalString original string
* @param defPackage the target package where the local language strings
* defined
* @param originNamesId the id of the original string array.
* @param localNamesId the id of the local string keys.
* @return local language string
*/
private static final String getLocalString(Context context, String originalString,
String defPackage, int originNamesId, int localNamesId) {
String[] origNames = context.getResources().getStringArray(originNamesId);
String[] localNames = context.getResources().getStringArray(localNamesId);
for (int i = 0; i < origNames.length; i++) {
if (origNames[i].equalsIgnoreCase(originalString)) {
return context.getString(context.getResources().getIdentifier(localNames[i],
"string", defPackage));
}
}
return originalString;
}
在MobileSignalController.java中的getLocalString方法打log发现最终原因是在切换第一语言时SystemUI所在Context的Configuration的Locale有时候会更新不及时,这样就导致了Context的语言环境有问题,如此获取的string自然是当前Context的语言环境,既然知道原因了,那么只需修复SystemUI Context的语言环境即可。
三、解决方案
修复语言环境的方法就是获取当前系统的语言,然后使用Configuration.setLocale或者Configuration.setLocales方法设置进去,再更新SystemUI的Configuration,而系统切换语言是在frameworks/base/core/java/com/android/internal/app/LocalePicker.java中处理的,
/**
* Requests the system to update the list of system locales.
* 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 updateLocales(LocaleList locales) {
try {
final IActivityManager am = ActivityManagerNative.getDefault();
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
}
}
切换语言后会setLocales(locales),然后调用updatePersistentConfiguration(config)方法,最终会调用到AMS的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 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) {
int changes = 0;
if (mWindowManager != null) {
mWindowManager.deferSurfaceLayout();
}
if (values != null) {
Configuration newConfig = new Configuration(mConfiguration);
changes = newConfig.updateFrom(values);
if (changes != 0) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.i(TAG_CONFIGURATION,
"Updating 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++;
if (mConfigurationSeq <= 0) {
mConfigurationSeq = 1;
}
newConfig.seq = mConfigurationSeq;
mConfiguration = newConfig;
Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + newConfig);
mUsageStatsService.reportConfigurationChange(newConfig,
mUserController.getCurrentUserIdLocked());
//mUsageStatsService.noteStartConfig(newConfig);
final Configuration configCopy = new Configuration(mConfiguration);
// TODO: If our config changes, should we auto dismiss any currently
// showing dialogs?
mShowDialogs = shouldShowDialogs(newConfig, mInVrMode);
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);
msg.arg1 = userId;
mHandler.sendMessage(msg);
}
final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
if (isDensityChange) {
// Reset the unsupported display size dialog.
mUiHandler.sendEmptyMessage(SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG);
killAllBackgroundProcessesExcept(Build.VERSION_CODES.N,
ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
}
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 " + 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
| Intent.FLAG_RECEIVER_FOREGROUND);
broadcastIntentLocked(null, null, intent, null, null, 0, null, null,
null, AppOpsManager.OP_NONE, null, false, false,
MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
if ((changes&ActivityInfo.CONFIG_LOCALE) != 0) {
intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
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, Process.SYSTEM_UID, UserHandle.USER_ALL);
}
}
// Update the configuration with WM first and check if any of the stacks need to be
// resized due to the configuration change. If so, resize the stacks now and do any
// relaunches if necessary. This way we don't need to relaunch again below in
// ensureActivityConfigurationLocked().
if (mWindowManager != null) {
final int[] resizedStacks = mWindowManager.setNewConfiguration(mConfiguration);
if (resizedStacks != null) {
for (int stackId : resizedStacks) {
final Rect newBounds = mWindowManager.getBoundsForNewConfiguration(stackId);
mStackSupervisor.resizeStackLocked(
stackId, newBounds, null, null, false, false, deferResume);
}
}
}
}
boolean kept = true;
final ActivityStack mainStack = mStackSupervisor.getFocusedStack();
// mainStack is null during startup.
if (mainStack != null) {
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 = mainStack.topRunningActivityLocked();
}
if (starting != null) {
kept = mainStack.ensureActivityConfigurationLocked(starting, changes, false);
// And we need to make sure at this point that all other activities
// are made visible with the correct configuration.
mStackSupervisor.ensureActivitiesVisibleLocked(starting, changes,
!PRESERVE_WINDOWS);
}
}
if (mWindowManager != null) {
mWindowManager.continueSurfaceLayout();
}
return kept;
}
import java.util.Locale;
private Context mContextNew;
private String getLocalString(String originalString) {
String[] str = SystemProperties.get("persist.sys.locale").split("-");
if (str.length == 1) {
mContext.getResources().getConfiguration().setLocale(new Locale(str[0]));
} else if (str.length > 1) {
mContext.getResources().getConfiguration().setLocale(new Locale(str[0], str[1]));
}
mContextNew = mContext.createConfigurationContext(mContext.getResources().getConfiguration());
return android.util.NativeTextHelper.getLocalString(mContextNew, originalString,
com.android.internal.R.array.origin_carrier_names,
com.android.internal.R.array.locale_carrier_names);
}
也可以改成:
import java.util.Locale;
private String getLocalString(String originalString) {
String[] str = SystemProperties.get("persist.sys.locale").split("-");
if (str.length == 1) {
mContext.getResources().getConfiguration().setLocale(new Locale(str[0]));
} else if (str.length > 1) {
mContext.getResources().getConfiguration().setLocale(new Locale(str[0], str[1]));
}
mContext.getResources().updateConfiguration(mContext.getResources().getConfiguration(), mContext.getResources().getDisplayMetrics());
return android.util.NativeTextHelper.getLocalString(mContext, originalString,
com.android.internal.R.array.origin_carrier_names,
com.android.internal.R.array.locale_carrier_names);
}
mContext.getResources().getConfiguration().setLocales(Resources.getSystem().getConfiguration().getLocales());
不过这种方式也不是一定保险,因为切换语言后会发送ACTION为LOCALE_CHANGED的广播,其他进程在收到广播后会更新Configuration,发送广播和设置是异步操作的,这也是为什么SystemUI的Context有时候Configuration会没有更新,如果Resources.getSystem().getConfiguration().getLocales()所得到的语言也是没有更新过的,那么SystemUI语言当然也会不准确。
网上还有说用Locale.getDefault()或者LocaleList.getDefault()方法获取的,其中LocaleList.getDefault()方法获取的并不是当前系统所设置顺序的语言,这个方法的注释中有说明,至于Locale.getDefault()这个没有深究,因为现在官方推荐的是使用LocaleList类。
如果实在不行,还可以用反射机制获取系统语言。