Loading core/java/android/app/ResourcesManager.java +167 −95 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.content.res.AssetManager; import android.content.res.CompatResources; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Flags; import android.content.res.Resources; import android.content.res.ResourcesImpl; import android.content.res.ResourcesKey; Loading Loading @@ -138,16 +139,22 @@ public class ResourcesManager { private final ArrayMap<String, SharedLibraryAssets> mSharedLibAssetsMap = new ArrayMap<>(); @VisibleForTesting public ArrayMap<String, SharedLibraryAssets> getRegisteredResourcePaths() { return mSharedLibAssetsMap; } /** * The internal function to register the resources paths of a package (e.g. a shared library). * This will collect the package resources' paths from its ApplicationInfo and add them to all * existing and future contexts while the application is running. */ public void registerResourcePaths(@NonNull String uniqueId, @NonNull ApplicationInfo appInfo) { SharedLibraryAssets sharedLibAssets = new SharedLibraryAssets(appInfo.sourceDir, appInfo.splitSourceDirs, appInfo.sharedLibraryFiles, appInfo.resourceDirs, appInfo.overlayPaths); if (!Flags.registerResourcePaths()) { return; } final var sharedLibAssets = new SharedLibraryAssets(appInfo); synchronized (mLock) { if (mSharedLibAssetsMap.containsKey(uniqueId)) { Slog.v(TAG, "Package resources' paths for uniqueId: " + uniqueId Loading @@ -155,18 +162,37 @@ public class ResourcesManager { return; } mSharedLibAssetsMap.put(uniqueId, sharedLibAssets); appendLibAssetsLocked(sharedLibAssets.getAllAssetPaths()); Slog.v(TAG, "The following resources' paths have been added: " + Arrays.toString(sharedLibAssets.getAllAssetPaths())); appendLibAssetsLocked(sharedLibAssets); Slog.v(TAG, "The following library key has been added: " + sharedLibAssets.getResourcesKey()); } } /** * Apply the registered library paths to the passed impl object * @return the hash code for the current version of the registered paths */ public int updateResourceImplWithRegisteredLibs(@NonNull ResourcesImpl impl) { if (!Flags.registerResourcePaths()) { return 0; } final var collector = new PathCollector(null); final int size = mSharedLibAssetsMap.size(); for (int i = 0; i < size; i++) { final var libraryKey = mSharedLibAssetsMap.valueAt(i).getResourcesKey(); collector.appendKey(libraryKey); } impl.getAssets().addPresetApkKeys(extractApkKeys(collector.collectedKey())); return size; } private static class ApkKey { public static class ApkKey { public final String path; public final boolean sharedLib; public final boolean overlay; ApkKey(String path, boolean sharedLib, boolean overlay) { public ApkKey(String path, boolean sharedLib, boolean overlay) { this.path = path; this.sharedLib = sharedLib; this.overlay = overlay; Loading @@ -190,6 +216,12 @@ public class ResourcesManager { return this.path.equals(other.path) && this.sharedLib == other.sharedLib && this.overlay == other.overlay; } @Override public String toString() { return "ApkKey[" + (sharedLib ? "lib" : "app") + (overlay ? ", overlay" : "") + ": " + path + "]"; } } /** Loading Loading @@ -505,7 +537,10 @@ public class ResourcesManager { return "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap"; } private @NonNull ApkAssets loadApkAssets(@NonNull final ApkKey key) throws IOException { /** * Loads the ApkAssets object for the passed key, or picks the one from the cache if available. */ public @NonNull ApkAssets loadApkAssets(@NonNull final ApkKey key) throws IOException { ApkAssets apkAssets; // Optimistically check if this ApkAssets exists somewhere else. Loading Loading @@ -747,8 +782,8 @@ public class ResourcesManager { private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked( @NonNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier) { ResourcesImpl impl = findResourcesImplForKeyLocked(key); // ResourcesImpl also need to be recreated if its shared library count is not up-to-date. if (impl == null || impl.getSharedLibCount() != mSharedLibAssetsMap.size()) { // ResourcesImpl also need to be recreated if its shared library hash is not up-to-date. if (impl == null || impl.getAppliedSharedLibsHash() != mSharedLibAssetsMap.size()) { impl = createResourcesImpl(key, apkSupplier); if (impl != null) { mResourceImpls.put(key, new WeakReference<>(impl)); Loading Loading @@ -1533,55 +1568,108 @@ public class ResourcesManager { } } private void appendLibAssetsLocked(String[] libAssets) { synchronized (mLock) { // Record which ResourcesImpl need updating // (and what ResourcesKey they should update to). final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>(); /** * A utility class to collect resources paths into a ResourcesKey object: * - Separates the libraries and the overlays into different sets as those are loaded in * different ways. * - Allows to start with an existing original key object, and copies all non-path related * properties into the final one. * - Preserves the path order while dropping all duplicates in an efficient manner. */ private static class PathCollector { public final ResourcesKey originalKey; public final ArrayList<String> orderedLibs = new ArrayList<>(); public final ArraySet<String> libsSet = new ArraySet<>(); public final ArrayList<String> orderedOverlays = new ArrayList<>(); public final ArraySet<String> overlaysSet = new ArraySet<>(); static void appendNewPath(@NonNull String path, @NonNull ArraySet<String> uniquePaths, @NonNull ArrayList<String> orderedPaths) { if (uniquePaths.add(path)) { orderedPaths.add(path); } } static void appendAllNewPaths(@Nullable String[] paths, @NonNull ArraySet<String> uniquePaths, @NonNull ArrayList<String> orderedPaths) { if (paths == null) return; for (int i = 0, size = paths.length; i < size; i++) { appendNewPath(paths[i], uniquePaths, orderedPaths); } } PathCollector(@Nullable ResourcesKey original) { originalKey = original; if (originalKey != null) { appendKey(originalKey); } } public void appendKey(@NonNull ResourcesKey key) { appendAllNewPaths(key.mLibDirs, libsSet, orderedLibs); appendAllNewPaths(key.mOverlayPaths, overlaysSet, orderedOverlays); } boolean isSameAsOriginal() { if (originalKey == null) { return orderedLibs.isEmpty() && orderedOverlays.isEmpty(); } return ((originalKey.mLibDirs == null && orderedLibs.isEmpty()) || (originalKey.mLibDirs != null && originalKey.mLibDirs.length == orderedLibs.size())) && ((originalKey.mOverlayPaths == null && orderedOverlays.isEmpty()) || (originalKey.mOverlayPaths != null && originalKey.mOverlayPaths.length == orderedOverlays.size())); } @NonNull ResourcesKey collectedKey() { return new ResourcesKey( originalKey == null ? null : originalKey.mResDir, originalKey == null ? null : originalKey.mSplitResDirs, orderedOverlays.toArray(new String[0]), orderedLibs.toArray(new String[0]), originalKey == null ? 0 : originalKey.mDisplayId, originalKey == null ? null : originalKey.mOverrideConfiguration, originalKey == null ? null : originalKey.mCompatInfo, originalKey == null ? null : originalKey.mLoaders); } } /** * Takes the original resources key and the one containing a set of library paths and overlays * to append, and combines them together. In case when the original key already contains all * those paths this function returns null, otherwise it makes a new ResourcesKey object. */ private @Nullable ResourcesKey createNewResourceKeyIfNeeded( @NonNull ResourcesKey original, @NonNull ResourcesKey library) { final var collector = new PathCollector(original); collector.appendKey(library); return collector.isSameAsOriginal() ? null : collector.collectedKey(); } /** * Append the newly registered shared library asset paths to all existing resources objects. */ private void appendLibAssetsLocked(@NonNull SharedLibraryAssets libAssets) { // Record the ResourcesImpl's that need updating, and what ResourcesKey they should // update to. final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>(); final int implCount = mResourceImpls.size(); for (int i = 0; i < implCount; i++) { final ResourcesKey key = mResourceImpls.keyAt(i); final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; if (impl == null) { Slog.w(TAG, "Found a ResourcesImpl which is null, skip it and continue to " + "append shared library assets for next ResourcesImpl."); Slog.w(TAG, "Found a null ResourcesImpl, skipped."); continue; } var newDirs = new ArrayList<String>(); var dirsSet = new ArraySet<String>(); if (key.mLibDirs != null) { final int dirsLength = key.mLibDirs.length; for (int k = 0; k < dirsLength; k++) { newDirs.add(key.mLibDirs[k]); dirsSet.add(key.mLibDirs[k]); } } final int assetsLength = libAssets.length; for (int j = 0; j < assetsLength; j++) { if (dirsSet.add(libAssets[j])) { newDirs.add(libAssets[j]); } } String[] newLibAssets = newDirs.toArray(new String[0]); if (!Arrays.equals(newLibAssets, key.mLibDirs)) { updatedResourceKeys.put(impl, new ResourcesKey( key.mResDir, key.mSplitResDirs, key.mOverlayPaths, newLibAssets, key.mDisplayId, key.mOverrideConfiguration, key.mCompatInfo, key.mLoaders)); final var newKey = createNewResourceKeyIfNeeded(key, libAssets.getResourcesKey()); if (newKey != null) { updatedResourceKeys.put(impl, newKey); } } redirectAllResourcesToNewImplLocked(updatedResourceKeys); } } private void applyNewResourceDirsLocked(@Nullable final String[] oldSourceDirs, @NonNull final ApplicationInfo appInfo) { Loading Loading @@ -1718,8 +1806,9 @@ public class ResourcesManager { } } // Another redirect function which will loop through all Resources and reload ResourcesImpl // if it needs a shared library asset paths update. // Another redirect function which will loop through all Resources in the process, even the ones // the app created outside of the regular Android Runtime, and reload their ResourcesImpl if it // needs a shared library asset paths update. private void redirectAllResourcesToNewImplLocked( @NonNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys) { cleanupReferences(mAllResourceReferences, mAllResourceReferencesQueue); Loading Loading @@ -1835,52 +1924,35 @@ public class ResourcesManager { } } @VisibleForTesting public static class SharedLibraryAssets { private final String[] mAssetPaths; SharedLibraryAssets(String sourceDir, String[] splitSourceDirs, String[] sharedLibraryFiles, String[] resourceDirs, String[] overlayPaths) { mAssetPaths = collectAssetPaths(sourceDir, splitSourceDirs, sharedLibraryFiles, resourceDirs, overlayPaths); } private @NonNull String[] collectAssetPaths(String sourceDir, String[] splitSourceDirs, String[] sharedLibraryFiles, String[] resourceDirs, String[] overlayPaths) { final String[][] inputLists = { splitSourceDirs, sharedLibraryFiles, resourceDirs, overlayPaths }; final ArraySet<String> assetPathSet = new ArraySet<>(); final List<String> assetPathList = new ArrayList<>(); if (sourceDir != null) { assetPathSet.add(sourceDir); assetPathList.add(sourceDir); } for (int i = 0; i < inputLists.length; i++) { if (inputLists[i] != null) { for (int j = 0; j < inputLists[i].length; j++) { if (assetPathSet.add(inputLists[i][j])) { assetPathList.add(inputLists[i][j]); } } } } return assetPathList.toArray(new String[0]); private final ResourcesKey mResourcesKey; private SharedLibraryAssets(ApplicationInfo appInfo) { // We're loading all library's files as shared libs, regardless where they are in // its own ApplicationInfo. final var collector = new PathCollector(null); PathCollector.appendNewPath(appInfo.sourceDir, collector.libsSet, collector.orderedLibs); PathCollector.appendAllNewPaths(appInfo.splitSourceDirs, collector.libsSet, collector.orderedLibs); PathCollector.appendAllNewPaths(appInfo.sharedLibraryFiles, collector.libsSet, collector.orderedLibs); PathCollector.appendAllNewPaths(appInfo.resourceDirs, collector.overlaysSet, collector.orderedOverlays); PathCollector.appendAllNewPaths(appInfo.overlayPaths, collector.overlaysSet, collector.orderedOverlays); mResourcesKey = collector.collectedKey(); } /** * @return all the asset paths of this collected in this class. * @return the resources key for this library assets. */ public @NonNull String[] getAllAssetPaths() { return mAssetPaths; public @NonNull ResourcesKey getResourcesKey() { return mResourcesKey; } } public @NonNull ArrayMap<String, SharedLibraryAssets> getSharedLibAssetsMap() { return new ArrayMap<>(mSharedLibAssetsMap); } /** * Add all resources references to the list which is designed to help to append shared library * asset paths. This is invoked in Resources constructor to include all Resources instances. Loading core/java/android/content/res/AssetManager.java +29 −37 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package android.content.res; import static android.content.res.Resources.ID_NULL; import static android.app.ResourcesManager.ApkKey; import android.annotation.AnyRes; import android.annotation.ArrayRes; Loading @@ -26,6 +27,7 @@ import android.annotation.Nullable; import android.annotation.StringRes; import android.annotation.StyleRes; import android.annotation.TestApi; import android.app.ResourcesManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo; import android.content.res.Configuration.NativeConfig; Loading Loading @@ -265,7 +267,7 @@ public final class AssetManager implements AutoCloseable { } sSystemApkAssetsSet = new ArraySet<>(apkAssets); sSystemApkAssets = apkAssets.toArray(new ApkAssets[apkAssets.size()]); sSystemApkAssets = apkAssets.toArray(new ApkAssets[0]); if (sSystem == null) { sSystem = new AssetManager(true /*sentinel*/); } Loading Loading @@ -449,7 +451,7 @@ public final class AssetManager implements AutoCloseable { @Deprecated @UnsupportedAppUsage public int addAssetPath(String path) { return addAssetPathInternal(List.of(path), false, false, false); return addAssetPathInternal(List.of(new ApkKey(path, false, false)), false); } /** Loading @@ -459,7 +461,7 @@ public final class AssetManager implements AutoCloseable { @Deprecated @UnsupportedAppUsage public int addAssetPathAsSharedLibrary(String path) { return addAssetPathInternal(List.of(path), false, true, false); return addAssetPathInternal(List.of(new ApkKey(path, true, false)), false); } /** Loading @@ -469,27 +471,26 @@ public final class AssetManager implements AutoCloseable { @Deprecated @UnsupportedAppUsage public int addOverlayPath(String path) { return addAssetPathInternal(List.of(path), true, false, false); return addAssetPathInternal(List.of(new ApkKey(path, false, true)), false); } /** * @hide */ public void addSharedLibraryPaths(@NonNull List<String> paths) { addAssetPathInternal(paths, false, true, true); public void addPresetApkKeys(@NonNull List<ApkKey> keys) { addAssetPathInternal(keys, true); } private int addAssetPathInternal(List<String> paths, boolean overlay, boolean appAsLib, boolean presetAssets) { Objects.requireNonNull(paths, "paths"); if (paths.isEmpty()) { private int addAssetPathInternal(List<ApkKey> apkKeys, boolean presetAssets) { Objects.requireNonNull(apkKeys, "apkKeys"); if (apkKeys.isEmpty()) { return 0; } synchronized (this) { ensureOpenLocked(); // See if we already have some of the paths loaded. // See if we already have some of the apkKeys loaded. final int originalAssetsCount = mApkAssets.length; // Getting an assets' path is a relatively expensive operation, cache them. Loading @@ -498,22 +499,22 @@ public final class AssetManager implements AutoCloseable { assetPaths.put(mApkAssets[i].getAssetPath(), i); } final ArrayList<String> newPaths = new ArrayList<>(paths.size()); final var newKeys = new ArrayList<ApkKey>(apkKeys.size()); int lastFoundIndex = -1; for (int i = 0, pathsSize = paths.size(); i < pathsSize; i++) { final var path = paths.get(i); final int index = assetPaths.getOrDefault(path, -1); if (index < 0) { newPaths.add(path); for (int i = 0, pathsSize = apkKeys.size(); i < pathsSize; i++) { final var key = apkKeys.get(i); final var index = assetPaths.get(key.path); if (index == null) { newKeys.add(key); } else { lastFoundIndex = index; } } if (newPaths.isEmpty()) { if (newKeys.isEmpty()) { return lastFoundIndex + 1; } final var newAssets = loadAssets(newPaths, overlay, appAsLib); final var newAssets = loadAssets(newKeys); if (newAssets.isEmpty()) { return 0; } Loading Loading @@ -557,28 +558,19 @@ public final class AssetManager implements AutoCloseable { return newAssetsArray; } private static @NonNull ArrayList<ApkAssets> loadAssets(@NonNull ArrayList<String> paths, boolean overlay, boolean appAsLib) { final int pathsSize = paths.size(); private static @NonNull ArrayList<ApkAssets> loadAssets(@NonNull ArrayList<ApkKey> keys) { final int pathsSize = keys.size(); final var loadedAssets = new ArrayList<ApkAssets>(pathsSize); final var resourcesManager = ResourcesManager.getInstance(); for (int i = 0; i < pathsSize; i++) { final var path = paths.get(i); final var key = keys.get(i); try { final ApkAssets assets; if (overlay || path.endsWith(".frro")) { // TODO(b/70343104): This hardcoded path will be removed once // addAssetPathInternal is deleted. final String idmapPath = "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap"; assets = ApkAssets.loadOverlayFromPath(idmapPath, 0 /* flags */); } else { assets = ApkAssets.loadFromPath(path, appAsLib ? ApkAssets.PROPERTY_DYNAMIC : 0); } loadedAssets.add(assets); // ResourcesManager has a cache of loaded assets, ensuring we don't open the same // file repeatedly, which is useful for the common overlays and registered // shared libraries. loadedAssets.add(resourcesManager.loadApkAssets(key)); } catch (IOException e) { Log.w(TAG, "Failed to load asset, path = " + path, e); Log.w(TAG, "Failed to load asset, key = " + key, e); } } return loadedAssets; Loading core/java/android/content/res/ResourcesImpl.java +7 −27 Original line number Diff line number Diff line Loading @@ -29,7 +29,6 @@ import android.annotation.StyleRes; import android.annotation.StyleableRes; import android.app.LocaleConfig; import android.app.ResourcesManager; import android.app.ResourcesManager.SharedLibraryAssets; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.Config; Loading @@ -48,8 +47,6 @@ import android.os.Build; import android.os.LocaleList; import android.os.ParcelFileDescriptor; import android.os.Trace; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; Loading @@ -72,7 +69,6 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Locale; Loading Loading @@ -149,10 +145,9 @@ public class ResourcesImpl { // Cyclical cache used for recently-accessed XML files. private int mLastCachedXmlBlockIndex = -1; // The number of shared libraries registered within this ResourcesImpl, which is designed to // help to determine whether this ResourcesImpl is outdated on shared library information and // needs to be replaced. private int mSharedLibCount; // The hash that allows to detect when the shared libraries applied to this object have changed, // and it is outdated and needs to be replaced. private final int mAppliedSharedLibsHash; private final int[] mCachedXmlBlockCookies = new int[XML_BLOCK_CACHE_SIZE]; private final String[] mCachedXmlBlockFiles = new String[XML_BLOCK_CACHE_SIZE]; private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[XML_BLOCK_CACHE_SIZE]; Loading Loading @@ -206,23 +201,8 @@ public class ResourcesImpl { public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics, @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) { mAssets = assets; if (Flags.registerResourcePaths()) { final ArraySet<String> uniquePaths = new ArraySet<>(); final ArrayList<String> orderedPaths = new ArrayList<>(); final ArrayMap<String, SharedLibraryAssets> sharedLibMap = ResourcesManager.getInstance().getSharedLibAssetsMap(); final int size = sharedLibMap.size(); for (int i = 0; i < size; i++) { final var paths = sharedLibMap.valueAt(i).getAllAssetPaths(); for (int j = 0; j < paths.length; j++) { if (uniquePaths.add(paths[j])) { orderedPaths.add(paths[j]); } } } assets.addSharedLibraryPaths(orderedPaths); mSharedLibCount = size; } mAppliedSharedLibsHash = ResourcesManager.getInstance().updateResourceImplWithRegisteredLibs(this); mMetrics.setToDefaults(); mDisplayAdjustments = displayAdjustments; mConfiguration.setToDefaults(); Loading Loading @@ -1625,7 +1605,7 @@ public class ResourcesImpl { } } public int getSharedLibCount() { return mSharedLibCount; public int getAppliedSharedLibsHash() { return mAppliedSharedLibsHash; } } core/tests/coretests/src/android/content/res/ResourcesManagerTest.java +4 −12 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
core/java/android/app/ResourcesManager.java +167 −95 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.content.res.AssetManager; import android.content.res.CompatResources; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Flags; import android.content.res.Resources; import android.content.res.ResourcesImpl; import android.content.res.ResourcesKey; Loading Loading @@ -138,16 +139,22 @@ public class ResourcesManager { private final ArrayMap<String, SharedLibraryAssets> mSharedLibAssetsMap = new ArrayMap<>(); @VisibleForTesting public ArrayMap<String, SharedLibraryAssets> getRegisteredResourcePaths() { return mSharedLibAssetsMap; } /** * The internal function to register the resources paths of a package (e.g. a shared library). * This will collect the package resources' paths from its ApplicationInfo and add them to all * existing and future contexts while the application is running. */ public void registerResourcePaths(@NonNull String uniqueId, @NonNull ApplicationInfo appInfo) { SharedLibraryAssets sharedLibAssets = new SharedLibraryAssets(appInfo.sourceDir, appInfo.splitSourceDirs, appInfo.sharedLibraryFiles, appInfo.resourceDirs, appInfo.overlayPaths); if (!Flags.registerResourcePaths()) { return; } final var sharedLibAssets = new SharedLibraryAssets(appInfo); synchronized (mLock) { if (mSharedLibAssetsMap.containsKey(uniqueId)) { Slog.v(TAG, "Package resources' paths for uniqueId: " + uniqueId Loading @@ -155,18 +162,37 @@ public class ResourcesManager { return; } mSharedLibAssetsMap.put(uniqueId, sharedLibAssets); appendLibAssetsLocked(sharedLibAssets.getAllAssetPaths()); Slog.v(TAG, "The following resources' paths have been added: " + Arrays.toString(sharedLibAssets.getAllAssetPaths())); appendLibAssetsLocked(sharedLibAssets); Slog.v(TAG, "The following library key has been added: " + sharedLibAssets.getResourcesKey()); } } /** * Apply the registered library paths to the passed impl object * @return the hash code for the current version of the registered paths */ public int updateResourceImplWithRegisteredLibs(@NonNull ResourcesImpl impl) { if (!Flags.registerResourcePaths()) { return 0; } final var collector = new PathCollector(null); final int size = mSharedLibAssetsMap.size(); for (int i = 0; i < size; i++) { final var libraryKey = mSharedLibAssetsMap.valueAt(i).getResourcesKey(); collector.appendKey(libraryKey); } impl.getAssets().addPresetApkKeys(extractApkKeys(collector.collectedKey())); return size; } private static class ApkKey { public static class ApkKey { public final String path; public final boolean sharedLib; public final boolean overlay; ApkKey(String path, boolean sharedLib, boolean overlay) { public ApkKey(String path, boolean sharedLib, boolean overlay) { this.path = path; this.sharedLib = sharedLib; this.overlay = overlay; Loading @@ -190,6 +216,12 @@ public class ResourcesManager { return this.path.equals(other.path) && this.sharedLib == other.sharedLib && this.overlay == other.overlay; } @Override public String toString() { return "ApkKey[" + (sharedLib ? "lib" : "app") + (overlay ? ", overlay" : "") + ": " + path + "]"; } } /** Loading Loading @@ -505,7 +537,10 @@ public class ResourcesManager { return "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap"; } private @NonNull ApkAssets loadApkAssets(@NonNull final ApkKey key) throws IOException { /** * Loads the ApkAssets object for the passed key, or picks the one from the cache if available. */ public @NonNull ApkAssets loadApkAssets(@NonNull final ApkKey key) throws IOException { ApkAssets apkAssets; // Optimistically check if this ApkAssets exists somewhere else. Loading Loading @@ -747,8 +782,8 @@ public class ResourcesManager { private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked( @NonNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier) { ResourcesImpl impl = findResourcesImplForKeyLocked(key); // ResourcesImpl also need to be recreated if its shared library count is not up-to-date. if (impl == null || impl.getSharedLibCount() != mSharedLibAssetsMap.size()) { // ResourcesImpl also need to be recreated if its shared library hash is not up-to-date. if (impl == null || impl.getAppliedSharedLibsHash() != mSharedLibAssetsMap.size()) { impl = createResourcesImpl(key, apkSupplier); if (impl != null) { mResourceImpls.put(key, new WeakReference<>(impl)); Loading Loading @@ -1533,55 +1568,108 @@ public class ResourcesManager { } } private void appendLibAssetsLocked(String[] libAssets) { synchronized (mLock) { // Record which ResourcesImpl need updating // (and what ResourcesKey they should update to). final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>(); /** * A utility class to collect resources paths into a ResourcesKey object: * - Separates the libraries and the overlays into different sets as those are loaded in * different ways. * - Allows to start with an existing original key object, and copies all non-path related * properties into the final one. * - Preserves the path order while dropping all duplicates in an efficient manner. */ private static class PathCollector { public final ResourcesKey originalKey; public final ArrayList<String> orderedLibs = new ArrayList<>(); public final ArraySet<String> libsSet = new ArraySet<>(); public final ArrayList<String> orderedOverlays = new ArrayList<>(); public final ArraySet<String> overlaysSet = new ArraySet<>(); static void appendNewPath(@NonNull String path, @NonNull ArraySet<String> uniquePaths, @NonNull ArrayList<String> orderedPaths) { if (uniquePaths.add(path)) { orderedPaths.add(path); } } static void appendAllNewPaths(@Nullable String[] paths, @NonNull ArraySet<String> uniquePaths, @NonNull ArrayList<String> orderedPaths) { if (paths == null) return; for (int i = 0, size = paths.length; i < size; i++) { appendNewPath(paths[i], uniquePaths, orderedPaths); } } PathCollector(@Nullable ResourcesKey original) { originalKey = original; if (originalKey != null) { appendKey(originalKey); } } public void appendKey(@NonNull ResourcesKey key) { appendAllNewPaths(key.mLibDirs, libsSet, orderedLibs); appendAllNewPaths(key.mOverlayPaths, overlaysSet, orderedOverlays); } boolean isSameAsOriginal() { if (originalKey == null) { return orderedLibs.isEmpty() && orderedOverlays.isEmpty(); } return ((originalKey.mLibDirs == null && orderedLibs.isEmpty()) || (originalKey.mLibDirs != null && originalKey.mLibDirs.length == orderedLibs.size())) && ((originalKey.mOverlayPaths == null && orderedOverlays.isEmpty()) || (originalKey.mOverlayPaths != null && originalKey.mOverlayPaths.length == orderedOverlays.size())); } @NonNull ResourcesKey collectedKey() { return new ResourcesKey( originalKey == null ? null : originalKey.mResDir, originalKey == null ? null : originalKey.mSplitResDirs, orderedOverlays.toArray(new String[0]), orderedLibs.toArray(new String[0]), originalKey == null ? 0 : originalKey.mDisplayId, originalKey == null ? null : originalKey.mOverrideConfiguration, originalKey == null ? null : originalKey.mCompatInfo, originalKey == null ? null : originalKey.mLoaders); } } /** * Takes the original resources key and the one containing a set of library paths and overlays * to append, and combines them together. In case when the original key already contains all * those paths this function returns null, otherwise it makes a new ResourcesKey object. */ private @Nullable ResourcesKey createNewResourceKeyIfNeeded( @NonNull ResourcesKey original, @NonNull ResourcesKey library) { final var collector = new PathCollector(original); collector.appendKey(library); return collector.isSameAsOriginal() ? null : collector.collectedKey(); } /** * Append the newly registered shared library asset paths to all existing resources objects. */ private void appendLibAssetsLocked(@NonNull SharedLibraryAssets libAssets) { // Record the ResourcesImpl's that need updating, and what ResourcesKey they should // update to. final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>(); final int implCount = mResourceImpls.size(); for (int i = 0; i < implCount; i++) { final ResourcesKey key = mResourceImpls.keyAt(i); final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; if (impl == null) { Slog.w(TAG, "Found a ResourcesImpl which is null, skip it and continue to " + "append shared library assets for next ResourcesImpl."); Slog.w(TAG, "Found a null ResourcesImpl, skipped."); continue; } var newDirs = new ArrayList<String>(); var dirsSet = new ArraySet<String>(); if (key.mLibDirs != null) { final int dirsLength = key.mLibDirs.length; for (int k = 0; k < dirsLength; k++) { newDirs.add(key.mLibDirs[k]); dirsSet.add(key.mLibDirs[k]); } } final int assetsLength = libAssets.length; for (int j = 0; j < assetsLength; j++) { if (dirsSet.add(libAssets[j])) { newDirs.add(libAssets[j]); } } String[] newLibAssets = newDirs.toArray(new String[0]); if (!Arrays.equals(newLibAssets, key.mLibDirs)) { updatedResourceKeys.put(impl, new ResourcesKey( key.mResDir, key.mSplitResDirs, key.mOverlayPaths, newLibAssets, key.mDisplayId, key.mOverrideConfiguration, key.mCompatInfo, key.mLoaders)); final var newKey = createNewResourceKeyIfNeeded(key, libAssets.getResourcesKey()); if (newKey != null) { updatedResourceKeys.put(impl, newKey); } } redirectAllResourcesToNewImplLocked(updatedResourceKeys); } } private void applyNewResourceDirsLocked(@Nullable final String[] oldSourceDirs, @NonNull final ApplicationInfo appInfo) { Loading Loading @@ -1718,8 +1806,9 @@ public class ResourcesManager { } } // Another redirect function which will loop through all Resources and reload ResourcesImpl // if it needs a shared library asset paths update. // Another redirect function which will loop through all Resources in the process, even the ones // the app created outside of the regular Android Runtime, and reload their ResourcesImpl if it // needs a shared library asset paths update. private void redirectAllResourcesToNewImplLocked( @NonNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys) { cleanupReferences(mAllResourceReferences, mAllResourceReferencesQueue); Loading Loading @@ -1835,52 +1924,35 @@ public class ResourcesManager { } } @VisibleForTesting public static class SharedLibraryAssets { private final String[] mAssetPaths; SharedLibraryAssets(String sourceDir, String[] splitSourceDirs, String[] sharedLibraryFiles, String[] resourceDirs, String[] overlayPaths) { mAssetPaths = collectAssetPaths(sourceDir, splitSourceDirs, sharedLibraryFiles, resourceDirs, overlayPaths); } private @NonNull String[] collectAssetPaths(String sourceDir, String[] splitSourceDirs, String[] sharedLibraryFiles, String[] resourceDirs, String[] overlayPaths) { final String[][] inputLists = { splitSourceDirs, sharedLibraryFiles, resourceDirs, overlayPaths }; final ArraySet<String> assetPathSet = new ArraySet<>(); final List<String> assetPathList = new ArrayList<>(); if (sourceDir != null) { assetPathSet.add(sourceDir); assetPathList.add(sourceDir); } for (int i = 0; i < inputLists.length; i++) { if (inputLists[i] != null) { for (int j = 0; j < inputLists[i].length; j++) { if (assetPathSet.add(inputLists[i][j])) { assetPathList.add(inputLists[i][j]); } } } } return assetPathList.toArray(new String[0]); private final ResourcesKey mResourcesKey; private SharedLibraryAssets(ApplicationInfo appInfo) { // We're loading all library's files as shared libs, regardless where they are in // its own ApplicationInfo. final var collector = new PathCollector(null); PathCollector.appendNewPath(appInfo.sourceDir, collector.libsSet, collector.orderedLibs); PathCollector.appendAllNewPaths(appInfo.splitSourceDirs, collector.libsSet, collector.orderedLibs); PathCollector.appendAllNewPaths(appInfo.sharedLibraryFiles, collector.libsSet, collector.orderedLibs); PathCollector.appendAllNewPaths(appInfo.resourceDirs, collector.overlaysSet, collector.orderedOverlays); PathCollector.appendAllNewPaths(appInfo.overlayPaths, collector.overlaysSet, collector.orderedOverlays); mResourcesKey = collector.collectedKey(); } /** * @return all the asset paths of this collected in this class. * @return the resources key for this library assets. */ public @NonNull String[] getAllAssetPaths() { return mAssetPaths; public @NonNull ResourcesKey getResourcesKey() { return mResourcesKey; } } public @NonNull ArrayMap<String, SharedLibraryAssets> getSharedLibAssetsMap() { return new ArrayMap<>(mSharedLibAssetsMap); } /** * Add all resources references to the list which is designed to help to append shared library * asset paths. This is invoked in Resources constructor to include all Resources instances. Loading
core/java/android/content/res/AssetManager.java +29 −37 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package android.content.res; import static android.content.res.Resources.ID_NULL; import static android.app.ResourcesManager.ApkKey; import android.annotation.AnyRes; import android.annotation.ArrayRes; Loading @@ -26,6 +27,7 @@ import android.annotation.Nullable; import android.annotation.StringRes; import android.annotation.StyleRes; import android.annotation.TestApi; import android.app.ResourcesManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo; import android.content.res.Configuration.NativeConfig; Loading Loading @@ -265,7 +267,7 @@ public final class AssetManager implements AutoCloseable { } sSystemApkAssetsSet = new ArraySet<>(apkAssets); sSystemApkAssets = apkAssets.toArray(new ApkAssets[apkAssets.size()]); sSystemApkAssets = apkAssets.toArray(new ApkAssets[0]); if (sSystem == null) { sSystem = new AssetManager(true /*sentinel*/); } Loading Loading @@ -449,7 +451,7 @@ public final class AssetManager implements AutoCloseable { @Deprecated @UnsupportedAppUsage public int addAssetPath(String path) { return addAssetPathInternal(List.of(path), false, false, false); return addAssetPathInternal(List.of(new ApkKey(path, false, false)), false); } /** Loading @@ -459,7 +461,7 @@ public final class AssetManager implements AutoCloseable { @Deprecated @UnsupportedAppUsage public int addAssetPathAsSharedLibrary(String path) { return addAssetPathInternal(List.of(path), false, true, false); return addAssetPathInternal(List.of(new ApkKey(path, true, false)), false); } /** Loading @@ -469,27 +471,26 @@ public final class AssetManager implements AutoCloseable { @Deprecated @UnsupportedAppUsage public int addOverlayPath(String path) { return addAssetPathInternal(List.of(path), true, false, false); return addAssetPathInternal(List.of(new ApkKey(path, false, true)), false); } /** * @hide */ public void addSharedLibraryPaths(@NonNull List<String> paths) { addAssetPathInternal(paths, false, true, true); public void addPresetApkKeys(@NonNull List<ApkKey> keys) { addAssetPathInternal(keys, true); } private int addAssetPathInternal(List<String> paths, boolean overlay, boolean appAsLib, boolean presetAssets) { Objects.requireNonNull(paths, "paths"); if (paths.isEmpty()) { private int addAssetPathInternal(List<ApkKey> apkKeys, boolean presetAssets) { Objects.requireNonNull(apkKeys, "apkKeys"); if (apkKeys.isEmpty()) { return 0; } synchronized (this) { ensureOpenLocked(); // See if we already have some of the paths loaded. // See if we already have some of the apkKeys loaded. final int originalAssetsCount = mApkAssets.length; // Getting an assets' path is a relatively expensive operation, cache them. Loading @@ -498,22 +499,22 @@ public final class AssetManager implements AutoCloseable { assetPaths.put(mApkAssets[i].getAssetPath(), i); } final ArrayList<String> newPaths = new ArrayList<>(paths.size()); final var newKeys = new ArrayList<ApkKey>(apkKeys.size()); int lastFoundIndex = -1; for (int i = 0, pathsSize = paths.size(); i < pathsSize; i++) { final var path = paths.get(i); final int index = assetPaths.getOrDefault(path, -1); if (index < 0) { newPaths.add(path); for (int i = 0, pathsSize = apkKeys.size(); i < pathsSize; i++) { final var key = apkKeys.get(i); final var index = assetPaths.get(key.path); if (index == null) { newKeys.add(key); } else { lastFoundIndex = index; } } if (newPaths.isEmpty()) { if (newKeys.isEmpty()) { return lastFoundIndex + 1; } final var newAssets = loadAssets(newPaths, overlay, appAsLib); final var newAssets = loadAssets(newKeys); if (newAssets.isEmpty()) { return 0; } Loading Loading @@ -557,28 +558,19 @@ public final class AssetManager implements AutoCloseable { return newAssetsArray; } private static @NonNull ArrayList<ApkAssets> loadAssets(@NonNull ArrayList<String> paths, boolean overlay, boolean appAsLib) { final int pathsSize = paths.size(); private static @NonNull ArrayList<ApkAssets> loadAssets(@NonNull ArrayList<ApkKey> keys) { final int pathsSize = keys.size(); final var loadedAssets = new ArrayList<ApkAssets>(pathsSize); final var resourcesManager = ResourcesManager.getInstance(); for (int i = 0; i < pathsSize; i++) { final var path = paths.get(i); final var key = keys.get(i); try { final ApkAssets assets; if (overlay || path.endsWith(".frro")) { // TODO(b/70343104): This hardcoded path will be removed once // addAssetPathInternal is deleted. final String idmapPath = "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap"; assets = ApkAssets.loadOverlayFromPath(idmapPath, 0 /* flags */); } else { assets = ApkAssets.loadFromPath(path, appAsLib ? ApkAssets.PROPERTY_DYNAMIC : 0); } loadedAssets.add(assets); // ResourcesManager has a cache of loaded assets, ensuring we don't open the same // file repeatedly, which is useful for the common overlays and registered // shared libraries. loadedAssets.add(resourcesManager.loadApkAssets(key)); } catch (IOException e) { Log.w(TAG, "Failed to load asset, path = " + path, e); Log.w(TAG, "Failed to load asset, key = " + key, e); } } return loadedAssets; Loading
core/java/android/content/res/ResourcesImpl.java +7 −27 Original line number Diff line number Diff line Loading @@ -29,7 +29,6 @@ import android.annotation.StyleRes; import android.annotation.StyleableRes; import android.app.LocaleConfig; import android.app.ResourcesManager; import android.app.ResourcesManager.SharedLibraryAssets; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.Config; Loading @@ -48,8 +47,6 @@ import android.os.Build; import android.os.LocaleList; import android.os.ParcelFileDescriptor; import android.os.Trace; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; Loading @@ -72,7 +69,6 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Locale; Loading Loading @@ -149,10 +145,9 @@ public class ResourcesImpl { // Cyclical cache used for recently-accessed XML files. private int mLastCachedXmlBlockIndex = -1; // The number of shared libraries registered within this ResourcesImpl, which is designed to // help to determine whether this ResourcesImpl is outdated on shared library information and // needs to be replaced. private int mSharedLibCount; // The hash that allows to detect when the shared libraries applied to this object have changed, // and it is outdated and needs to be replaced. private final int mAppliedSharedLibsHash; private final int[] mCachedXmlBlockCookies = new int[XML_BLOCK_CACHE_SIZE]; private final String[] mCachedXmlBlockFiles = new String[XML_BLOCK_CACHE_SIZE]; private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[XML_BLOCK_CACHE_SIZE]; Loading Loading @@ -206,23 +201,8 @@ public class ResourcesImpl { public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics, @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) { mAssets = assets; if (Flags.registerResourcePaths()) { final ArraySet<String> uniquePaths = new ArraySet<>(); final ArrayList<String> orderedPaths = new ArrayList<>(); final ArrayMap<String, SharedLibraryAssets> sharedLibMap = ResourcesManager.getInstance().getSharedLibAssetsMap(); final int size = sharedLibMap.size(); for (int i = 0; i < size; i++) { final var paths = sharedLibMap.valueAt(i).getAllAssetPaths(); for (int j = 0; j < paths.length; j++) { if (uniquePaths.add(paths[j])) { orderedPaths.add(paths[j]); } } } assets.addSharedLibraryPaths(orderedPaths); mSharedLibCount = size; } mAppliedSharedLibsHash = ResourcesManager.getInstance().updateResourceImplWithRegisteredLibs(this); mMetrics.setToDefaults(); mDisplayAdjustments = displayAdjustments; mConfiguration.setToDefaults(); Loading Loading @@ -1625,7 +1605,7 @@ public class ResourcesImpl { } } public int getSharedLibCount() { return mSharedLibCount; public int getAppliedSharedLibsHash() { return mAppliedSharedLibsHash; } }
core/tests/coretests/src/android/content/res/ResourcesManagerTest.java +4 −12 File changed.Preview size limit exceeded, changes collapsed. Show changes