addPreferencesFromResource(R.xml.language_settings);
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = super.onCreateView(inflater, container, savedInstanceState);
final ListView list = (ListView) view.findViewById(android.R.id.list);
Utils.forcePrepareCustomPreferencesList(container, view, list, false);
return view;
}
/**
* Constructs an Adapter object containing Locale information. Content is sorted by
* {@link LocaleInfo#label}.
*/
public static ArrayAdapter constructAdapter(Context context) {
return constructAdapter(context, R.layout.locale_picker_item, R.id.locale);
}
public static ArrayAdapter constructAdapter(Context context,
final int layoutId, final int fieldId) {
boolean isInDeveloperMode = Settings.Global.getInt(context.getContentResolver(),
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
//获取系统支持语言的信息
final List localeInfos = getAllAssetLocales(context, isInDeveloperMode);
final LayoutInflater inflater =
(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
return new ArrayAdapter(context, layoutId, fieldId, localeInfos) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
TextView text;
if (convertView == null) {
view = inflater.inflate(layoutId, parent, false);
text = (TextView) view.findViewById(fieldId);
view.setTag(text);
} else {
view = convertView;
text = (TextView) view.getTag();
}
LocaleInfo item = getItem(position);
text.setText(item.toString());
text.setTextLocale(item.getLocale());
return view;
}
};
}
而此方法通过getAllAssetLocales()方法获取系统支持语言的信息:
public static List getAllAssetLocales(Context context, boolean isInDeveloperMode) {
final Resources resources = context.getResources();
//获取系统所支持的语言
final String[] locales = Resources.getSystem().getAssets().getLocales();
List localeList = new ArrayList(locales.length);
Collections.addAll(localeList, locales);
// Don't show the pseudolocales unless we're in developer mode.
if (!isInDeveloperMode) {
localeList.remove("ar-XB");
localeList.remove("en-XA");
}
Collections.sort(localeList);
final String[] specialLocaleCodes = resources.getStringArray(R.array.special_locale_codes);
final String[] specialLocaleNames = resources.getStringArray(R.array.special_locale_names);
final ArrayList localeInfos = new ArrayList(localeList.size());
for (String locale : localeList) {
final Locale l = Locale.forLanguageTag(locale.replace('_', '-'));
if (l == null || "und".equals(l.getLanguage())
|| l.getLanguage().isEmpty() || l.getCountry().isEmpty()) {
continue;
}
if (localeInfos.isEmpty()) {
if (DEBUG) {
Log.v(TAG, "adding initial "+ toTitleCase(l.getDisplayLanguage(l)));
}
localeInfos.add(new LocaleInfo(toTitleCase(l.getDisplayLanguage(l)), l));
} else {
// check previous entry:
// same lang and a country -> upgrade to full name and
// insert ours with full name
// diff lang -> insert ours with lang-only name
final LocaleInfo previous = localeInfos.get(localeInfos.size() - 1);
if (previous.locale.getLanguage().equals(l.getLanguage()) &&
!previous.locale.getLanguage().equals("zz")) {
if (DEBUG) {
Log.v(TAG, "backing up and fixing " + previous.label + " to " +
getDisplayName(previous.locale, specialLocaleCodes, specialLocaleNames));
}
previous.label = toTitleCase(getDisplayName(
previous.locale, specialLocaleCodes, specialLocaleNames));
if (DEBUG) {
Log.v(TAG, " and adding "+ toTitleCase(
getDisplayName(l, specialLocaleCodes, specialLocaleNames)));
}
localeInfos.add(new LocaleInfo(toTitleCase(
getDisplayName(l, specialLocaleCodes, specialLocaleNames)), l));
} else {
String displayName = toTitleCase(l.getDisplayLanguage(l));
if (DEBUG) {
Log.v(TAG, "adding "+displayName);
}
localeInfos.add(new LocaleInfo(displayName, l));
}
}
}
Collections.sort(localeInfos);
return localeInfos;
}
/**
* Get the locales that this asset manager contains data for.
*
* On SDK 21 (Android 5.0: Lollipop) and above, Locale strings are valid
* BCP-47 language tags and can be
* parsed using {@link java.util.Locale#forLanguageTag(String)}.
*
*
On SDK 20 (Android 4.4W: Kitkat for watches) and below, locale strings
* are of the form {@code ll_CC} where {@code ll} is a two letter language code,
* and {@code CC} is a two letter country code.
*/
public native final String[] getLocales();
static jobjectArray android_content_AssetManager_getLocales(JNIEnv* env, jobject clazz)
{
Vector locales;
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return NULL;
}
am->getLocales(&locales);
const int N = locales.size();
jobjectArray result = env->NewObjectArray(N, g_stringClass, NULL);
if (result == NULL) {
return NULL;
}
for (int i=0; iNewStringUTF(locales[i].string());
if (str == NULL) {
return NULL;
}
env->SetObjectArrayElement(result, i, str);
env->DeleteLocalRef(str);
}
return result;
}
通过上面初步的分析,语言的List界面就基本出来了,在getAllAssetLocales()方法中打了个断点,查看了下locales被赋值以后的值:
public static interface LocaleSelectionListener {
// You can add any argument if you really need it...
public void onLocaleSelected(Locale locale);
}
@Override
public void onLocaleSelected(final Locale locale) {
if (Utils.hasMultipleUsers(getActivity())) {
mTargetLocale = locale;
showDialog(DLG_SHOW_GLOBAL_WARNING);
} else {
getActivity().onBackPressed();
LocalePicker.updateLocale(locale);
}
}
/**
* 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();
// Will set userSetLocale to indicate this isn't some passing default - the user
// wants this remembered
config.setLocale(locale);
am.updateConfiguration(config);
// Trigger the dirty bit for the Settings Provider.
BackupManager.dataChanged("com.android.providers.settings");
} catch (RemoteException e) {
// Intentionally left blank
}
}
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);
}
}
/**
* @hide Erase the fields in the Configuration that should be applied
* by the settings.
*/
public static void clearConfiguration(Configuration inoutConfig) {
inoutConfig.fontScale = 0;
}
/**
* 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
*/
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) {
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);
}
mConfigurationSeq++;
if (mConfigurationSeq <= 0) {
mConfigurationSeq = 1;
}
newConfig.seq = mConfigurationSeq;
mConfiguration = newConfig;
Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + newConfig);
mUsageStatsService.reportConfigurationChange(newConfig, mCurrentUserId);
//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);
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
| Intent.FLAG_RECEIVER_FOREGROUND);
broadcastIntentLocked(null, null, intent, null, null, 0, null, null,
null, AppOpsManager.OP_NONE, 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);
broadcastIntentLocked(null, null, intent,
null, null, 0, null, null, null, AppOpsManager.OP_NONE,
false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
}
}
}
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(null);
}
if (starting != null) {
kept = mainStack.ensureActivityConfigurationLocked(starting, changes);
// And we need to make sure at this point that all other activities
// are made visible with the correct configuration.
mStackSupervisor.ensureActivitiesVisibleLocked(starting, changes);
}
}
if (values != null && mWindowManager != null) {
mWindowManager.setNewConfiguration(mConfiguration);
}
return kept;
}
此方法主要做两件事:第一,改变当前的configuration,将新的数据放进去。第二,保证正在运行的应用程序界面更新最新的configuration。先调用updateFrom()方法,遍历configuration包含的属性是否改变,如果有改变就返回一个对应的整数,如果没有改变就返回0。就语言改变而言,根据上面的分析,configuration至少有3个属性发生了改变:fontscale(之前没有设置字体的效果就不会改变)、locale和布局的direction。
/**
* Save the locale. You must be inside a synchronized (this) block.
*/
private void saveLocaleLocked(Locale l, boolean isDiff, boolean isPersist) {
if(isDiff) {
SystemProperties.set("user.language", l.getLanguage());
SystemProperties.set("user.region", l.getCountry());
}
if(isPersist) {
SystemProperties.set("persist.sys.language", l.getLanguage());
SystemProperties.set("persist.sys.country", l.getCountry());
SystemProperties.set("persist.sys.localevar", l.getVariant());
mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG, l));
}
}
if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {
Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);
msg.obj = new Configuration(configCopy);
mHandler.sendMessage(msg);
}
...
case UPDATE_CONFIGURATION_MSG: {
final ContentResolver resolver = mContext.getContentResolver();
Settings.System.putConfiguration(resolver, (Configuration)msg.obj);
} break;
mSystemThread = ActivityThread.currentActivityThread();
//此方法属于ActivityThread
public final void applyConfigurationToResources(Configuration config) {
synchronized (mResourcesManager) {
mResourcesManager.applyConfigurationToResourcesLocked(config, null);
}
}
//此方法属于ResourcesManage
public final boolean applyConfigurationToResourcesLocked(Configuration config,
CompatibilityInfo compat) {
if (mResConfiguration == null) {
mResConfiguration = new Configuration();
}
if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
+ mResConfiguration.seq + ", newSeq=" + config.seq);
return false;
}
int changes = mResConfiguration.updateFrom(config);
flushDisplayMetricsLocked();
DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked(Display.DEFAULT_DISPLAY);
if (compat != null && (mResCompatibilityInfo == null ||
!mResCompatibilityInfo.equals(compat))) {
mResCompatibilityInfo = compat;
changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
| ActivityInfo.CONFIG_SCREEN_SIZE
| ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
}
// set it for java, this also affects newly created Resources
if (config.locale != null) {
Locale.setDefault(config.locale);
}
Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);
ApplicationPackageManager.configurationChanged();
//Slog.i(TAG, "Configuration changed in " + currentPackageName());
Configuration tmpConfig = null;
for (int i=mActiveResources.size()-1; i>=0; i--) {
ResourcesKey key = mActiveResources.keyAt(i);
Resources r = mActiveResources.valueAt(i).get();
if (r != null) {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
+ r + " config to: " + config);
int displayId = key.mDisplayId;
boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
DisplayMetrics dm = defaultDisplayMetrics;
final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
if (!isDefaultDisplay || hasOverrideConfiguration) {
if (tmpConfig == null) {
tmpConfig = new Configuration();
}
tmpConfig.setTo(config);
if (!isDefaultDisplay) {
dm = getDisplayMetricsLocked(displayId);
applyNonDefaultDisplayMetricsToConfigurationLocked(dm, tmpConfig);
}
if (hasOverrideConfiguration) {
tmpConfig.updateFrom(key.mOverrideConfiguration);
}
r.updateConfiguration(tmpConfig, dm, compat);
} else {
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());
mActiveResources.removeAt(i);
}
}
return changes != 0;
}
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) {
}
}
public void scheduleConfigurationChanged(Configuration config) {
updatePendingConfiguration(config);
sendMessage(H.CONFIGURATION_CHANGED, config);
}
case CONFIGURATION_CHANGED:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
mCurDefaultDisplayDpi = ((Configuration)msg.obj).densityDpi;
handleConfigurationChanged((Configuration)msg.obj, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
int configDiff = 0;
synchronized (mResourcesManager) {
if (mPendingConfiguration != null) {
if (!mPendingConfiguration.isOtherSeqNewer(config)) {
config = mPendingConfiguration;
mCurDefaultDisplayDpi = config.densityDpi;
updateDefaultDensity();
}
mPendingConfiguration = null;
}
if (config == null) {
return;
}
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: "
+ config);
mResourcesManager.applyConfigurationToResourcesLocked(config, compat);
if (mConfiguration == null) {
mConfiguration = new Configuration();
}
if (!mConfiguration.isOtherSeqNewer(config) && compat == null) {
return;
}
configDiff = mConfiguration.diff(config);
mConfiguration.updateFrom(config);
config = applyCompatConfiguration(mCurDefaultDisplayDpi);
}
ArrayList callbacks = collectComponentCallbacks(false, config);
freeTextLayoutCachesIfNeeded(configDiff);
if (callbacks != null) {
final int N = callbacks.size();
for (int i=0; i
private static 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);
}
}
}