From b61e405397200f78b1c652143cba7c751df05a00 Mon Sep 17 00:00:00 2001 From: Adam Lesinski Date: Thu, 19 May 2016 18:23:05 -0700 Subject: Improve performance of LocaleList with Resources We allow each individual Resources object to select the best Locale for the given APK. This allows one update to the configuration instead of multiple updates, once the locale is chosen. The Java locale is selected from the app context's locale. Bug:28625993 Bug:27325465 Change-Id: I99e1e53f522e560f3b80bbd1e1c605f552dbdff0 --- core/java/android/app/ActivityThread.java | 64 ++++++++++++++------ core/java/android/app/ResourcesManager.java | 74 ++---------------------- core/java/android/content/res/Configuration.java | 2 +- core/java/android/content/res/ResourcesImpl.java | 31 +++++++++- core/java/android/os/LocaleList.java | 14 ++++- 5 files changed, 91 insertions(+), 94 deletions(-) (limited to 'core/java') diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index dff07693c82f..30753c1632ff 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -62,6 +62,7 @@ import android.os.DropBoxManager; import android.os.Environment; import android.os.Handler; import android.os.IBinder; +import android.os.LocaleList; import android.os.Looper; import android.os.Message; import android.os.MessageQueue; @@ -130,6 +131,7 @@ import java.text.DateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.TimeZone; @@ -4684,6 +4686,8 @@ public final class ActivityThread { + config); mResourcesManager.applyConfigurationToResourcesLocked(config, compat); + updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(), + mResourcesManager.getConfiguration().getLocales()); if (mConfiguration == null) { mConfiguration = new Configuration(); @@ -4989,6 +4993,24 @@ public final class ActivityThread { return insInfo.nativeLibraryDir; } + /** + * The LocaleList set for the app's resources may have been shuffled so that the preferred + * Locale is at position 0. We must find the index of this preferred Locale in the + * original LocaleList. + */ + private void updateLocaleListFromAppContext(Context context, LocaleList newLocaleList) { + final Locale bestLocale = context.getResources().getConfiguration().getLocales().get(0); + final int newLocaleListSize = newLocaleList.size(); + for (int i = 0; i < newLocaleListSize; i++) { + if (bestLocale.equals(newLocaleList.get(i))) { + LocaleList.setDefault(newLocaleList, i); + return; + } + } + throw new AssertionError("chosen locale " + bestLocale + " must be present in LocaleList: " + + newLocaleList.toLanguageTags()); + } + private void handleBindApplication(AppBindData data) { // Register the UI Thread as a sensitive thread to the runtime. VMRuntime.registerSensitiveThread(); @@ -5047,6 +5069,24 @@ public final class ActivityThread { */ TimeZone.setDefault(null); + /* + * Set the LocaleList. This may change once we create the App Context. + */ + LocaleList.setDefault(data.config.getLocales()); + + synchronized (mResourcesManager) { + /* + * Update the system configuration since its preloaded and might not + * reflect configuration changes. The configuration object passed + * in AppBindData can be safely assumed to be up to date + */ + mResourcesManager.applyConfigurationToResourcesLocked(data.config, data.compatInfo); + mCurDefaultDisplayDpi = data.config.densityDpi; + + // This calls mResourcesManager so keep it within the synchronized block. + applyCompatConfiguration(mCurDefaultDisplayDpi); + } + data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo); /** @@ -5174,25 +5214,8 @@ public final class ActivityThread { } final ContextImpl appContext = ContextImpl.createAppContext(this, data.info); - synchronized (mResourcesManager) { - /* - * Initialize the default locales in this process for the reasons we set the time zone. - * - * We do this through ResourcesManager, since we need to do locale negotiation. - */ - mResourcesManager.setDefaultLocalesLocked(data.config.getLocales()); - - /* - * Update the system configuration since its preloaded and might not - * reflect configuration changes. The configuration object passed - * in AppBindData can be safely assumed to be up to date - */ - mResourcesManager.applyConfigurationToResourcesLocked(data.config, data.compatInfo); - mCurDefaultDisplayDpi = data.config.densityDpi; - - // This calls mResourcesManager so keep it within the synchronized block. - applyCompatConfiguration(mCurDefaultDisplayDpi); - } + updateLocaleListFromAppContext(appContext, + mResourcesManager.getConfiguration().getLocales()); if (!Process.isIsolated() && !"android".equals(appContext.getPackageName())) { // This cache location probably points at credential-encrypted @@ -5895,6 +5918,9 @@ public final class ActivityThread { // immediately, because upon returning the view // hierarchy will be informed about it. if (mResourcesManager.applyConfigurationToResourcesLocked(newConfig, null)) { + updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(), + mResourcesManager.getConfiguration().getLocales()); + // This actually changed the resources! Tell // everyone about it. if (mPendingConfiguration == null || diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index b4e9db86d18c..25a8b66a42aa 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -67,10 +67,6 @@ public class ResourcesManager { } }; - private String[] mSystemLocales = null; - private final HashSet mNonSystemLocales = new HashSet<>(); - private boolean mHasNonSystemLocales = false; - /** * The global compatibility settings. */ @@ -479,12 +475,7 @@ public class ResourcesManager { */ private Resources getOrCreateResources(@Nullable IBinder activityToken, @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) { - final boolean findSystemLocales; - final boolean hasNonSystemLocales; synchronized (this) { - findSystemLocales = (mSystemLocales == null || mSystemLocales.length == 0); - hasNonSystemLocales = mHasNonSystemLocales; - if (DEBUG) { Throwable here = new Throwable(); here.fillInStackTrace(); @@ -538,24 +529,7 @@ public class ResourcesManager { // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now. ResourcesImpl resourcesImpl = createResourcesImpl(key); - final String[] systemLocales = findSystemLocales - ? AssetManager.getSystem().getLocales() : null; - final String[] nonSystemLocales = resourcesImpl.getAssets().getNonSystemLocales(); - - // Avoid checking for non-pseudo-locales if we already know there were some from a previous - // Resources. The default value (for when hasNonSystemLocales is true) doesn't matter, - // since mHasNonSystemLocales will also be true, and thus isPseudoLocalesOnly would not be - // able to affect mHasNonSystemLocales. - final boolean isPseudoLocalesOnly = hasNonSystemLocales || - LocaleList.isPseudoLocalesOnly(nonSystemLocales); - synchronized (this) { - if (mSystemLocales == null || mSystemLocales.length == 0) { - mSystemLocales = systemLocales; - } - mNonSystemLocales.addAll(Arrays.asList(nonSystemLocales)); - mHasNonSystemLocales = mHasNonSystemLocales || !isPseudoLocalesOnly; - ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key); if (existingResourcesImpl != null) { if (DEBUG) { @@ -745,23 +719,6 @@ public class ResourcesManager { } } - /* package */ void setDefaultLocalesLocked(@NonNull LocaleList locales) { - if (mSystemLocales == null) { - throw new RuntimeException("ResourcesManager is not ready to negotiate locales."); - } - final int bestLocale; - if (mHasNonSystemLocales) { - bestLocale = locales.getFirstMatchIndexWithEnglishSupported(mNonSystemLocales); - } else { - // We fallback to system locales if there was no locale specifically supported by the - // assets. This is to properly support apps that only rely on the shared system assets - // and don't need assets of their own. - bestLocale = locales.getFirstMatchIndexWithEnglishSupported(mSystemLocales); - } - // set it for Java, this also affects newly created Resources - LocaleList.setDefault(locales, bestLocale); - } - public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config, @Nullable CompatibilityInfo compat) { try { @@ -786,30 +743,7 @@ public class ResourcesManager { | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; } - Configuration localeAdjustedConfig = config; - final LocaleList configLocales = config.getLocales(); - if (!configLocales.isEmpty()) { - setDefaultLocalesLocked(configLocales); - final LocaleList adjustedLocales = LocaleList.getAdjustedDefault(); - if (adjustedLocales - != configLocales) { // has the same result as .equals() in this case - // The first locale in the list was not chosen. So we create a modified - // configuration with the adjusted locales (which moves the chosen locale to the - // front). - localeAdjustedConfig = new Configuration(); - localeAdjustedConfig.setTo(config); - localeAdjustedConfig.setLocales(adjustedLocales); - // Also adjust the locale list in mResConfiguration, so that the Resources - // created later would have the same locale list. - if (!mResConfiguration.getLocales().equals(adjustedLocales)) { - mResConfiguration.setLocales(adjustedLocales); - changes |= ActivityInfo.CONFIG_LOCALE; - } - } - } - - Resources.updateSystemConfiguration(localeAdjustedConfig, defaultDisplayMetrics, - compat); + Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat); ApplicationPackageManager.configurationChanged(); //Slog.i(TAG, "Configuration changed in " + currentPackageName()); @@ -821,7 +755,7 @@ public class ResourcesManager { ResourcesImpl r = mResourceImpls.valueAt(i).get(); if (r != null) { if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources " - + r + " config to: " + localeAdjustedConfig); + + r + " config to: " + config); int displayId = key.mDisplayId; boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); DisplayMetrics dm = defaultDisplayMetrics; @@ -830,7 +764,7 @@ public class ResourcesManager { if (tmpConfig == null) { tmpConfig = new Configuration(); } - tmpConfig.setTo(localeAdjustedConfig); + tmpConfig.setTo(config); if (!isDefaultDisplay) { dm = getDisplayMetrics(displayId); applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig); @@ -840,7 +774,7 @@ public class ResourcesManager { } r.updateConfiguration(tmpConfig, dm, compat); } else { - r.updateConfiguration(localeAdjustedConfig, dm, compat); + r.updateConfiguration(config, dm, compat); } //Slog.i(TAG, "Updated app resources " + v.getKey() // + " " + r + ": " + r.getConfiguration()); diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index c1aac8584acb..f6445e6cd090 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -1446,7 +1446,7 @@ public final class Configuration implements Parcelable, Comparable 1) { + // The LocaleList has changed. We must query the AssetManager's available + // Locales and figure out the best matching Locale in the new LocaleList. + String[] availableLocales = mAssets.getNonSystemLocales(); + if (LocaleList.isPseudoLocalesOnly(availableLocales)) { + // No app defined locales, so grab the system locales. + availableLocales = mAssets.getLocales(); + if (LocaleList.isPseudoLocalesOnly(availableLocales)) { + availableLocales = null; + } + } + + if (availableLocales != null) { + final Locale bestLocale = locales.getFirstMatchWithEnglishSupported( + availableLocales); + if (bestLocale != null && bestLocale != locales.get(0)) { + mConfiguration.setLocales(new LocaleList(bestLocale, locales)); + } + } + } + } + if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) { mMetrics.densityDpi = mConfiguration.densityDpi; mMetrics.density = @@ -370,7 +395,7 @@ public class ResourcesImpl { } mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc, - adjustLanguageTag(locales.get(0).toLanguageTag()), + adjustLanguageTag(mConfiguration.getLocales().get(0).toLanguageTag()), mConfiguration.orientation, mConfiguration.touchscreen, mConfiguration.densityDpi, mConfiguration.keyboard, diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java index 8136796574e0..60b618a2f689 100644 --- a/core/java/android/os/LocaleList.java +++ b/core/java/android/os/LocaleList.java @@ -408,6 +408,14 @@ public final class LocaleList implements Parcelable { false /* assume English is not supported */); } + /** + * {@hide} + */ + public int getFirstMatchIndex(String[] supportedLocales) { + return computeFirstMatchIndex(Arrays.asList(supportedLocales), + false /* assume English is not supported */); + } + /** * Same as getFirstMatch(), but with English assumed to be supported, even if it's not. * {@hide} @@ -437,7 +445,11 @@ public final class LocaleList implements Parcelable { * Assumes that there is no repetition in the input. * {@hide} */ - public static boolean isPseudoLocalesOnly(String[] supportedLocales) { + public static boolean isPseudoLocalesOnly(@Nullable String[] supportedLocales) { + if (supportedLocales == null) { + return true; + } + if (supportedLocales.length > NUM_PSEUDO_LOCALES + 1) { // This is for optimization. Since there's no repetition in the input, if we have more // than the number of pseudo-locales plus one for the empty string, it's guaranteed -- cgit v1.2.3