Loading core/java/android/app/Activity.java +0 −2 Original line number Diff line number Diff line Loading @@ -6330,8 +6330,6 @@ public class Activity extends ContextThemeWrapper } else { writer.print(prefix); writer.println("No AutofillManager"); } ResourcesManager.getInstance().dump(prefix, writer); } /** Loading core/java/android/app/ResourcesManager.java +30 −168 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import static android.app.ActivityThread.DEBUG_CONFIGURATION; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.ActivityInfo; import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.content.res.CompatResources; import android.content.res.CompatibilityInfo; Loading @@ -35,7 +34,6 @@ import android.os.Trace; import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.Log; import android.util.LruCache; import android.util.Pair; import android.util.Slog; import android.view.Display; Loading @@ -43,13 +41,9 @@ import android.view.DisplayAdjustments; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; import java.io.IOException; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; import java.util.Objects; import java.util.WeakHashMap; import java.util.function.Predicate; Loading @@ -65,7 +59,12 @@ public class ResourcesManager { * Predicate that returns true if a WeakReference is gc'ed. */ private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate = weakRef -> weakRef == null || weakRef.get() == null; new Predicate<WeakReference<Resources>>() { @Override public boolean test(WeakReference<Resources> weakRef) { return weakRef == null || weakRef.get() == null; } }; /** * The global compatibility settings. Loading @@ -90,48 +89,6 @@ public class ResourcesManager { */ private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>(); private static class ApkKey { public final String path; public final boolean sharedLib; public final boolean overlay; ApkKey(String path, boolean sharedLib, boolean overlay) { this.path = path; this.sharedLib = sharedLib; this.overlay = overlay; } @Override public int hashCode() { int result = 1; result = 31 * result + this.path.hashCode(); result = 31 * result + Boolean.hashCode(this.sharedLib); result = 31 * result + Boolean.hashCode(this.overlay); return result; } @Override public boolean equals(Object obj) { if (!(obj instanceof ApkKey)) { return false; } ApkKey other = (ApkKey) obj; return this.path.equals(other.path) && this.sharedLib == other.sharedLib && this.overlay == other.overlay; } } /** * The ApkAssets we are caching and intend to hold strong references to. */ private final LruCache<ApkKey, ApkAssets> mLoadedApkAssets = new LruCache<>(15); /** * The ApkAssets that are being referenced in the wild that we can reuse, even if they aren't * in our LRU cache. Bonus resources :) */ private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>(); /** * Resources and base configuration override associated with an Activity. */ Loading Loading @@ -303,41 +260,6 @@ public class ResourcesManager { } } private @NonNull ApkAssets loadApkAssets(String path, boolean sharedLib, boolean overlay) throws IOException { final ApkKey newKey = new ApkKey(path, sharedLib, overlay); ApkAssets apkAssets = mLoadedApkAssets.get(newKey); if (apkAssets != null) { return apkAssets; } // Optimistically check if this ApkAssets exists somewhere else. final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey); if (apkAssetsRef != null) { apkAssets = apkAssetsRef.get(); if (apkAssets != null) { mLoadedApkAssets.put(newKey, apkAssets); return apkAssets; } else { // Clean up the reference. mCachedApkAssets.remove(newKey); } } // We must load this from disk. if (overlay) { final String idmapPath = "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap"; apkAssets = ApkAssets.loadOverlayFromPath(idmapPath, false /*system*/); } else { apkAssets = ApkAssets.loadFromPath(path, false /*system*/, sharedLib); } mLoadedApkAssets.put(newKey, apkAssets); mCachedApkAssets.put(newKey, new WeakReference<>(apkAssets)); return apkAssets; } /** * Creates an AssetManager from the paths within the ResourcesKey. * Loading @@ -348,15 +270,13 @@ public class ResourcesManager { */ @VisibleForTesting protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) { final ArrayList<ApkAssets> apkAssets = new ArrayList<>(); AssetManager assets = new AssetManager(); // resDir can be null if the 'android' package is creating a new Resources object. // This is fine, since each AssetManager automatically loads the 'android' package // already. if (key.mResDir != null) { try { apkAssets.add(loadApkAssets(key.mResDir, false /*sharedLib*/, false /*overlay*/)); } catch (IOException e) { if (assets.addAssetPath(key.mResDir) == 0) { Log.e(TAG, "failed to add asset path " + key.mResDir); return null; } Loading @@ -364,10 +284,7 @@ public class ResourcesManager { if (key.mSplitResDirs != null) { for (final String splitResDir : key.mSplitResDirs) { try { apkAssets.add(loadApkAssets(splitResDir, false /*sharedLib*/, false /*overlay*/)); } catch (IOException e) { if (assets.addAssetPath(splitResDir) == 0) { Log.e(TAG, "failed to add split asset path " + splitResDir); return null; } Loading @@ -376,13 +293,7 @@ public class ResourcesManager { if (key.mOverlayDirs != null) { for (final String idmapPath : key.mOverlayDirs) { try { apkAssets.add(loadApkAssets(idmapPath, false /*sharedLib*/, true /*overlay*/)); } catch (IOException e) { Log.w(TAG, "failed to add overlay path " + idmapPath); // continue. } assets.addOverlayPath(idmapPath); } } Loading @@ -391,77 +302,16 @@ public class ResourcesManager { if (libDir.endsWith(".apk")) { // Avoid opening files we know do not have resources, // like code-only .jar files. try { apkAssets.add(loadApkAssets(libDir, true /*sharedLib*/, false /*overlay*/)); } catch (IOException e) { if (assets.addAssetPathAsSharedLibrary(libDir) == 0) { Log.w(TAG, "Asset path '" + libDir + "' does not exist or contains no resources."); // continue. } } } } AssetManager assets = new AssetManager(); assets.setApkAssets(apkAssets.toArray(new ApkAssets[apkAssets.size()]), false /*invalidateCaches*/); return assets; } private static <T> int countLiveReferences(Collection<WeakReference<T>> collection) { int count = 0; for (WeakReference<T> ref : collection) { final T value = ref != null ? ref.get() : null; if (value != null) { count++; } } return count; } /** * @hide */ public void dump(String prefix, PrintWriter printWriter) { synchronized (this) { IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); for (int i = 0; i < prefix.length() / 2; i++) { pw.increaseIndent(); } pw.println("ResourcesManager:"); pw.increaseIndent(); pw.print("cached apks: total="); pw.print(mLoadedApkAssets.size()); pw.print(" created="); pw.print(mLoadedApkAssets.createCount()); pw.print(" evicted="); pw.print(mLoadedApkAssets.evictionCount()); pw.print(" hit="); pw.print(mLoadedApkAssets.hitCount()); pw.print(" miss="); pw.print(mLoadedApkAssets.missCount()); pw.print(" max="); pw.print(mLoadedApkAssets.maxSize()); pw.println(); pw.print("total apks: "); pw.println(countLiveReferences(mCachedApkAssets.values())); pw.print("resources: "); int references = countLiveReferences(mResourceReferences); for (ActivityResources activityResources : mActivityResourceReferences.values()) { references += countLiveReferences(activityResources.activityResources); } pw.println(references); pw.print("resource impls: "); pw.println(countLiveReferences(mResourceImpls.values())); } } private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) { Configuration config; final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY); Loading Loading @@ -780,6 +630,7 @@ public class ResourcesManager { // We will create the ResourcesImpl object outside of holding this lock. } } // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now. ResourcesImpl resourcesImpl = createResourcesImpl(key); Loading @@ -787,8 +638,19 @@ public class ResourcesManager { return null; } synchronized (this) { ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key); if (existingResourcesImpl != null) { if (DEBUG) { Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl + " new impl=" + resourcesImpl); } resourcesImpl.getAssets().close(); resourcesImpl = existingResourcesImpl; } else { // Add this ResourcesImpl to the cache. mResourceImpls.put(key, new WeakReference<>(resourcesImpl)); } final Resources resources; if (activityToken != null) { Loading core/java/android/content/pm/PackageParser.java +29 −16 Original line number Diff line number Diff line Loading @@ -1311,6 +1311,24 @@ public class PackageParser { } } private static int loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags) throws PackageParserException { if ((flags & PARSE_MUST_BE_APK) != 0 && !isApkPath(apkPath)) { throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, "Invalid package file: " + apkPath); } // The AssetManager guarantees uniqueness for asset paths, so if this asset path // already exists in the AssetManager, addAssetPath will only return the cookie // assigned to it. int cookie = assets.addAssetPath(apkPath); if (cookie == 0) { throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, "Failed adding asset path: " + apkPath); } return cookie; } private Package parseBaseApk(File apkFile, AssetManager assets, int flags) throws PackageParserException { final String apkPath = apkFile.getAbsolutePath(); Loading @@ -1326,15 +1344,13 @@ public class PackageParser { if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath); final int cookie = loadApkIntoAssetManager(assets, apkPath, flags); Resources res = null; XmlResourceParser parser = null; try { final int cookie = assets.findCookieForPath(apkPath); if (cookie == 0) { throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, "Failed adding asset path: " + apkPath); } res = new Resources(assets, mMetrics, null); parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); final Resources res = new Resources(assets, mMetrics, null); final String[] outError = new String[1]; final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError); Loading Loading @@ -1369,18 +1385,15 @@ public class PackageParser { if (DEBUG_JAR) Slog.d(TAG, "Scanning split APK: " + apkPath); final int cookie = loadApkIntoAssetManager(assets, apkPath, flags); final Resources res; XmlResourceParser parser = null; try { // This must always succeed, as the path has been added to the AssetManager before. final int cookie = assets.findCookieForPath(apkPath); if (cookie == 0) { throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, "Failed adding asset path: " + apkPath); } parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); res = new Resources(assets, mMetrics, null); assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); final String[] outError = new String[1]; pkg = parseSplitApk(pkg, res, parser, flags, splitIndex, outError); Loading Loading @@ -1582,9 +1595,9 @@ public class PackageParser { int flags) throws PackageParserException { final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath(); ApkAssets apkAssets = null; XmlResourceParser parser = null; try { final ApkAssets apkAssets; try { apkAssets = fd != null ? ApkAssets.loadFromFd(fd, debugPathName, false, false) Loading Loading @@ -1621,7 +1634,7 @@ public class PackageParser { "Failed to parse " + apkPath, e); } finally { IoUtils.closeQuietly(parser); // TODO(b/72056911): Implement and call close() on ApkAssets. IoUtils.closeQuietly(apkAssets); } } Loading core/java/android/content/pm/split/DefaultSplitAssetLoader.java +36 −39 Original line number Diff line number Diff line Loading @@ -15,13 +15,10 @@ */ package android.content.pm.split; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.PackageParser.ParseFlags; import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.os.Build; Loading @@ -29,8 +26,6 @@ import com.android.internal.util.ArrayUtils; import libcore.io.IoUtils; import java.io.IOException; /** * Loads the base and split APKs into a single AssetManager. * @hide Loading @@ -38,66 +33,68 @@ import java.io.IOException; public class DefaultSplitAssetLoader implements SplitAssetLoader { private final String mBaseCodePath; private final String[] mSplitCodePaths; private final @ParseFlags int mFlags; private final int mFlags; private AssetManager mCachedAssetManager; public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, @ParseFlags int flags) { public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, int flags) { mBaseCodePath = pkg.baseCodePath; mSplitCodePaths = pkg.splitCodePaths; mFlags = flags; } private static ApkAssets loadApkAssets(String path, @ParseFlags int flags) throws PackageParserException { if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) { throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, "Invalid package file: " + path); private static void loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags) throws PackageParser.PackageParserException { if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(apkPath)) { throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, "Invalid package file: " + apkPath); } try { return ApkAssets.loadFromPath(path); } catch (IOException e) { throw new PackageParserException(INSTALL_FAILED_INVALID_APK, "Failed to load APK at path " + path, e); if (assets.addAssetPath(apkPath) == 0) { throw new PackageParser.PackageParserException( INSTALL_PARSE_FAILED_BAD_MANIFEST, "Failed adding asset path: " + apkPath); } } @Override public AssetManager getBaseAssetManager() throws PackageParserException { public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException { if (mCachedAssetManager != null) { return mCachedAssetManager; } ApkAssets[] apkAssets = new ApkAssets[(mSplitCodePaths != null ? mSplitCodePaths.length : 0) + 1]; // Load the base. int splitIdx = 0; apkAssets[splitIdx++] = loadApkAssets(mBaseCodePath, mFlags); AssetManager assets = new AssetManager(); try { assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); loadApkIntoAssetManager(assets, mBaseCodePath, mFlags); // Load any splits. if (!ArrayUtils.isEmpty(mSplitCodePaths)) { for (String apkPath : mSplitCodePaths) { apkAssets[splitIdx++] = loadApkAssets(apkPath, mFlags); loadApkIntoAssetManager(assets, apkPath, mFlags); } } AssetManager assets = new AssetManager(); assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); assets.setApkAssets(apkAssets, false /*invalidateCaches*/); mCachedAssetManager = assets; assets = null; return mCachedAssetManager; } finally { if (assets != null) { IoUtils.closeQuietly(assets); } } } @Override public AssetManager getSplitAssetManager(int splitIdx) throws PackageParserException { public AssetManager getSplitAssetManager(int splitIdx) throws PackageParser.PackageParserException { return getBaseAssetManager(); } @Override public void close() throws Exception { if (mCachedAssetManager != null) { IoUtils.closeQuietly(mCachedAssetManager); } } } core/java/android/content/pm/split/SplitAssetDependencyLoader.java +42 −46 Original line number Diff line number Diff line Loading @@ -15,21 +15,17 @@ */ package android.content.pm.split; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; import android.annotation.NonNull; import android.content.pm.PackageManager; import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.PackageParser.ParseFlags; import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.os.Build; import android.util.SparseArray; import libcore.io.IoUtils; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; Loading @@ -38,15 +34,17 @@ import java.util.Collections; * is to be used when an application opts-in to isolated split loading. * @hide */ public class SplitAssetDependencyLoader extends SplitDependencyLoader<PackageParserException> public class SplitAssetDependencyLoader extends SplitDependencyLoader<PackageParser.PackageParserException> implements SplitAssetLoader { private final String[] mSplitPaths; private final @ParseFlags int mFlags; private final ApkAssets[][] mCachedSplitApks; private final AssetManager[] mCachedAssetManagers; private final int mFlags; private String[][] mCachedPaths; private AssetManager[] mCachedAssetManagers; public SplitAssetDependencyLoader(PackageParser.PackageLite pkg, SparseArray<int[]> dependencies, @ParseFlags int flags) { SparseArray<int[]> dependencies, int flags) { super(dependencies); // The base is inserted into index 0, so we need to shift all the splits by 1. Loading @@ -55,7 +53,7 @@ public class SplitAssetDependencyLoader extends SplitDependencyLoader<PackagePar System.arraycopy(pkg.splitCodePaths, 0, mSplitPaths, 1, pkg.splitCodePaths.length); mFlags = flags; mCachedSplitApks = new ApkAssets[mSplitPaths.length][]; mCachedPaths = new String[mSplitPaths.length][]; mCachedAssetManagers = new AssetManager[mSplitPaths.length]; } Loading @@ -64,60 +62,58 @@ public class SplitAssetDependencyLoader extends SplitDependencyLoader<PackagePar return mCachedAssetManagers[splitIdx] != null; } private static ApkAssets loadApkAssets(String path, @ParseFlags int flags) throws PackageParserException { if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) { throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, "Invalid package file: " + path); private static AssetManager createAssetManagerWithPaths(String[] assetPaths, int flags) throws PackageParser.PackageParserException { final AssetManager assets = new AssetManager(); try { assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); for (String assetPath : assetPaths) { if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(assetPath)) { throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, "Invalid package file: " + assetPath); } try { return ApkAssets.loadFromPath(path); } catch (IOException e) { throw new PackageParserException(PackageManager.INSTALL_FAILED_INVALID_APK, "Failed to load APK at path " + path, e); if (assets.addAssetPath(assetPath) == 0) { throw new PackageParser.PackageParserException( INSTALL_PARSE_FAILED_BAD_MANIFEST, "Failed adding asset path: " + assetPath); } } private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) { final AssetManager assets = new AssetManager(); assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); assets.setApkAssets(apkAssets, false /*invalidateCaches*/); return assets; } catch (Throwable e) { IoUtils.closeQuietly(assets); throw e; } } @Override protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices, int parentSplitIdx) throws PackageParserException { final ArrayList<ApkAssets> assets = new ArrayList<>(); // Include parent ApkAssets. int parentSplitIdx) throws PackageParser.PackageParserException { final ArrayList<String> assetPaths = new ArrayList<>(); if (parentSplitIdx >= 0) { Collections.addAll(assets, mCachedSplitApks[parentSplitIdx]); Collections.addAll(assetPaths, mCachedPaths[parentSplitIdx]); } // Include this ApkAssets. assets.add(loadApkAssets(mSplitPaths[splitIdx], mFlags)); // Load and include all config splits for this feature. assetPaths.add(mSplitPaths[splitIdx]); for (int configSplitIdx : configSplitIndices) { assets.add(loadApkAssets(mSplitPaths[configSplitIdx], mFlags)); assetPaths.add(mSplitPaths[configSplitIdx]); } // Cache the results. mCachedSplitApks[splitIdx] = assets.toArray(new ApkAssets[assets.size()]); mCachedAssetManagers[splitIdx] = createAssetManagerWithAssets(mCachedSplitApks[splitIdx]); mCachedPaths[splitIdx] = assetPaths.toArray(new String[assetPaths.size()]); mCachedAssetManagers[splitIdx] = createAssetManagerWithPaths(mCachedPaths[splitIdx], mFlags); } @Override public AssetManager getBaseAssetManager() throws PackageParserException { public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException { loadDependenciesForSplit(0); return mCachedAssetManagers[0]; } @Override public AssetManager getSplitAssetManager(int idx) throws PackageParserException { public AssetManager getSplitAssetManager(int idx) throws PackageParser.PackageParserException { // Since we insert the base at position 0, and PackageParser keeps splits separate from // the base, we need to adjust the index. loadDependenciesForSplit(idx + 1); Loading Loading
core/java/android/app/Activity.java +0 −2 Original line number Diff line number Diff line Loading @@ -6330,8 +6330,6 @@ public class Activity extends ContextThemeWrapper } else { writer.print(prefix); writer.println("No AutofillManager"); } ResourcesManager.getInstance().dump(prefix, writer); } /** Loading
core/java/android/app/ResourcesManager.java +30 −168 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import static android.app.ActivityThread.DEBUG_CONFIGURATION; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.ActivityInfo; import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.content.res.CompatResources; import android.content.res.CompatibilityInfo; Loading @@ -35,7 +34,6 @@ import android.os.Trace; import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.Log; import android.util.LruCache; import android.util.Pair; import android.util.Slog; import android.view.Display; Loading @@ -43,13 +41,9 @@ import android.view.DisplayAdjustments; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; import java.io.IOException; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; import java.util.Objects; import java.util.WeakHashMap; import java.util.function.Predicate; Loading @@ -65,7 +59,12 @@ public class ResourcesManager { * Predicate that returns true if a WeakReference is gc'ed. */ private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate = weakRef -> weakRef == null || weakRef.get() == null; new Predicate<WeakReference<Resources>>() { @Override public boolean test(WeakReference<Resources> weakRef) { return weakRef == null || weakRef.get() == null; } }; /** * The global compatibility settings. Loading @@ -90,48 +89,6 @@ public class ResourcesManager { */ private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>(); private static class ApkKey { public final String path; public final boolean sharedLib; public final boolean overlay; ApkKey(String path, boolean sharedLib, boolean overlay) { this.path = path; this.sharedLib = sharedLib; this.overlay = overlay; } @Override public int hashCode() { int result = 1; result = 31 * result + this.path.hashCode(); result = 31 * result + Boolean.hashCode(this.sharedLib); result = 31 * result + Boolean.hashCode(this.overlay); return result; } @Override public boolean equals(Object obj) { if (!(obj instanceof ApkKey)) { return false; } ApkKey other = (ApkKey) obj; return this.path.equals(other.path) && this.sharedLib == other.sharedLib && this.overlay == other.overlay; } } /** * The ApkAssets we are caching and intend to hold strong references to. */ private final LruCache<ApkKey, ApkAssets> mLoadedApkAssets = new LruCache<>(15); /** * The ApkAssets that are being referenced in the wild that we can reuse, even if they aren't * in our LRU cache. Bonus resources :) */ private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>(); /** * Resources and base configuration override associated with an Activity. */ Loading Loading @@ -303,41 +260,6 @@ public class ResourcesManager { } } private @NonNull ApkAssets loadApkAssets(String path, boolean sharedLib, boolean overlay) throws IOException { final ApkKey newKey = new ApkKey(path, sharedLib, overlay); ApkAssets apkAssets = mLoadedApkAssets.get(newKey); if (apkAssets != null) { return apkAssets; } // Optimistically check if this ApkAssets exists somewhere else. final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey); if (apkAssetsRef != null) { apkAssets = apkAssetsRef.get(); if (apkAssets != null) { mLoadedApkAssets.put(newKey, apkAssets); return apkAssets; } else { // Clean up the reference. mCachedApkAssets.remove(newKey); } } // We must load this from disk. if (overlay) { final String idmapPath = "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap"; apkAssets = ApkAssets.loadOverlayFromPath(idmapPath, false /*system*/); } else { apkAssets = ApkAssets.loadFromPath(path, false /*system*/, sharedLib); } mLoadedApkAssets.put(newKey, apkAssets); mCachedApkAssets.put(newKey, new WeakReference<>(apkAssets)); return apkAssets; } /** * Creates an AssetManager from the paths within the ResourcesKey. * Loading @@ -348,15 +270,13 @@ public class ResourcesManager { */ @VisibleForTesting protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) { final ArrayList<ApkAssets> apkAssets = new ArrayList<>(); AssetManager assets = new AssetManager(); // resDir can be null if the 'android' package is creating a new Resources object. // This is fine, since each AssetManager automatically loads the 'android' package // already. if (key.mResDir != null) { try { apkAssets.add(loadApkAssets(key.mResDir, false /*sharedLib*/, false /*overlay*/)); } catch (IOException e) { if (assets.addAssetPath(key.mResDir) == 0) { Log.e(TAG, "failed to add asset path " + key.mResDir); return null; } Loading @@ -364,10 +284,7 @@ public class ResourcesManager { if (key.mSplitResDirs != null) { for (final String splitResDir : key.mSplitResDirs) { try { apkAssets.add(loadApkAssets(splitResDir, false /*sharedLib*/, false /*overlay*/)); } catch (IOException e) { if (assets.addAssetPath(splitResDir) == 0) { Log.e(TAG, "failed to add split asset path " + splitResDir); return null; } Loading @@ -376,13 +293,7 @@ public class ResourcesManager { if (key.mOverlayDirs != null) { for (final String idmapPath : key.mOverlayDirs) { try { apkAssets.add(loadApkAssets(idmapPath, false /*sharedLib*/, true /*overlay*/)); } catch (IOException e) { Log.w(TAG, "failed to add overlay path " + idmapPath); // continue. } assets.addOverlayPath(idmapPath); } } Loading @@ -391,77 +302,16 @@ public class ResourcesManager { if (libDir.endsWith(".apk")) { // Avoid opening files we know do not have resources, // like code-only .jar files. try { apkAssets.add(loadApkAssets(libDir, true /*sharedLib*/, false /*overlay*/)); } catch (IOException e) { if (assets.addAssetPathAsSharedLibrary(libDir) == 0) { Log.w(TAG, "Asset path '" + libDir + "' does not exist or contains no resources."); // continue. } } } } AssetManager assets = new AssetManager(); assets.setApkAssets(apkAssets.toArray(new ApkAssets[apkAssets.size()]), false /*invalidateCaches*/); return assets; } private static <T> int countLiveReferences(Collection<WeakReference<T>> collection) { int count = 0; for (WeakReference<T> ref : collection) { final T value = ref != null ? ref.get() : null; if (value != null) { count++; } } return count; } /** * @hide */ public void dump(String prefix, PrintWriter printWriter) { synchronized (this) { IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); for (int i = 0; i < prefix.length() / 2; i++) { pw.increaseIndent(); } pw.println("ResourcesManager:"); pw.increaseIndent(); pw.print("cached apks: total="); pw.print(mLoadedApkAssets.size()); pw.print(" created="); pw.print(mLoadedApkAssets.createCount()); pw.print(" evicted="); pw.print(mLoadedApkAssets.evictionCount()); pw.print(" hit="); pw.print(mLoadedApkAssets.hitCount()); pw.print(" miss="); pw.print(mLoadedApkAssets.missCount()); pw.print(" max="); pw.print(mLoadedApkAssets.maxSize()); pw.println(); pw.print("total apks: "); pw.println(countLiveReferences(mCachedApkAssets.values())); pw.print("resources: "); int references = countLiveReferences(mResourceReferences); for (ActivityResources activityResources : mActivityResourceReferences.values()) { references += countLiveReferences(activityResources.activityResources); } pw.println(references); pw.print("resource impls: "); pw.println(countLiveReferences(mResourceImpls.values())); } } private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) { Configuration config; final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY); Loading Loading @@ -780,6 +630,7 @@ public class ResourcesManager { // We will create the ResourcesImpl object outside of holding this lock. } } // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now. ResourcesImpl resourcesImpl = createResourcesImpl(key); Loading @@ -787,8 +638,19 @@ public class ResourcesManager { return null; } synchronized (this) { ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key); if (existingResourcesImpl != null) { if (DEBUG) { Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl + " new impl=" + resourcesImpl); } resourcesImpl.getAssets().close(); resourcesImpl = existingResourcesImpl; } else { // Add this ResourcesImpl to the cache. mResourceImpls.put(key, new WeakReference<>(resourcesImpl)); } final Resources resources; if (activityToken != null) { Loading
core/java/android/content/pm/PackageParser.java +29 −16 Original line number Diff line number Diff line Loading @@ -1311,6 +1311,24 @@ public class PackageParser { } } private static int loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags) throws PackageParserException { if ((flags & PARSE_MUST_BE_APK) != 0 && !isApkPath(apkPath)) { throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, "Invalid package file: " + apkPath); } // The AssetManager guarantees uniqueness for asset paths, so if this asset path // already exists in the AssetManager, addAssetPath will only return the cookie // assigned to it. int cookie = assets.addAssetPath(apkPath); if (cookie == 0) { throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, "Failed adding asset path: " + apkPath); } return cookie; } private Package parseBaseApk(File apkFile, AssetManager assets, int flags) throws PackageParserException { final String apkPath = apkFile.getAbsolutePath(); Loading @@ -1326,15 +1344,13 @@ public class PackageParser { if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath); final int cookie = loadApkIntoAssetManager(assets, apkPath, flags); Resources res = null; XmlResourceParser parser = null; try { final int cookie = assets.findCookieForPath(apkPath); if (cookie == 0) { throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, "Failed adding asset path: " + apkPath); } res = new Resources(assets, mMetrics, null); parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); final Resources res = new Resources(assets, mMetrics, null); final String[] outError = new String[1]; final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError); Loading Loading @@ -1369,18 +1385,15 @@ public class PackageParser { if (DEBUG_JAR) Slog.d(TAG, "Scanning split APK: " + apkPath); final int cookie = loadApkIntoAssetManager(assets, apkPath, flags); final Resources res; XmlResourceParser parser = null; try { // This must always succeed, as the path has been added to the AssetManager before. final int cookie = assets.findCookieForPath(apkPath); if (cookie == 0) { throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, "Failed adding asset path: " + apkPath); } parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); res = new Resources(assets, mMetrics, null); assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); final String[] outError = new String[1]; pkg = parseSplitApk(pkg, res, parser, flags, splitIndex, outError); Loading Loading @@ -1582,9 +1595,9 @@ public class PackageParser { int flags) throws PackageParserException { final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath(); ApkAssets apkAssets = null; XmlResourceParser parser = null; try { final ApkAssets apkAssets; try { apkAssets = fd != null ? ApkAssets.loadFromFd(fd, debugPathName, false, false) Loading Loading @@ -1621,7 +1634,7 @@ public class PackageParser { "Failed to parse " + apkPath, e); } finally { IoUtils.closeQuietly(parser); // TODO(b/72056911): Implement and call close() on ApkAssets. IoUtils.closeQuietly(apkAssets); } } Loading
core/java/android/content/pm/split/DefaultSplitAssetLoader.java +36 −39 Original line number Diff line number Diff line Loading @@ -15,13 +15,10 @@ */ package android.content.pm.split; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.PackageParser.ParseFlags; import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.os.Build; Loading @@ -29,8 +26,6 @@ import com.android.internal.util.ArrayUtils; import libcore.io.IoUtils; import java.io.IOException; /** * Loads the base and split APKs into a single AssetManager. * @hide Loading @@ -38,66 +33,68 @@ import java.io.IOException; public class DefaultSplitAssetLoader implements SplitAssetLoader { private final String mBaseCodePath; private final String[] mSplitCodePaths; private final @ParseFlags int mFlags; private final int mFlags; private AssetManager mCachedAssetManager; public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, @ParseFlags int flags) { public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, int flags) { mBaseCodePath = pkg.baseCodePath; mSplitCodePaths = pkg.splitCodePaths; mFlags = flags; } private static ApkAssets loadApkAssets(String path, @ParseFlags int flags) throws PackageParserException { if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) { throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, "Invalid package file: " + path); private static void loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags) throws PackageParser.PackageParserException { if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(apkPath)) { throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, "Invalid package file: " + apkPath); } try { return ApkAssets.loadFromPath(path); } catch (IOException e) { throw new PackageParserException(INSTALL_FAILED_INVALID_APK, "Failed to load APK at path " + path, e); if (assets.addAssetPath(apkPath) == 0) { throw new PackageParser.PackageParserException( INSTALL_PARSE_FAILED_BAD_MANIFEST, "Failed adding asset path: " + apkPath); } } @Override public AssetManager getBaseAssetManager() throws PackageParserException { public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException { if (mCachedAssetManager != null) { return mCachedAssetManager; } ApkAssets[] apkAssets = new ApkAssets[(mSplitCodePaths != null ? mSplitCodePaths.length : 0) + 1]; // Load the base. int splitIdx = 0; apkAssets[splitIdx++] = loadApkAssets(mBaseCodePath, mFlags); AssetManager assets = new AssetManager(); try { assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); loadApkIntoAssetManager(assets, mBaseCodePath, mFlags); // Load any splits. if (!ArrayUtils.isEmpty(mSplitCodePaths)) { for (String apkPath : mSplitCodePaths) { apkAssets[splitIdx++] = loadApkAssets(apkPath, mFlags); loadApkIntoAssetManager(assets, apkPath, mFlags); } } AssetManager assets = new AssetManager(); assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); assets.setApkAssets(apkAssets, false /*invalidateCaches*/); mCachedAssetManager = assets; assets = null; return mCachedAssetManager; } finally { if (assets != null) { IoUtils.closeQuietly(assets); } } } @Override public AssetManager getSplitAssetManager(int splitIdx) throws PackageParserException { public AssetManager getSplitAssetManager(int splitIdx) throws PackageParser.PackageParserException { return getBaseAssetManager(); } @Override public void close() throws Exception { if (mCachedAssetManager != null) { IoUtils.closeQuietly(mCachedAssetManager); } } }
core/java/android/content/pm/split/SplitAssetDependencyLoader.java +42 −46 Original line number Diff line number Diff line Loading @@ -15,21 +15,17 @@ */ package android.content.pm.split; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; import android.annotation.NonNull; import android.content.pm.PackageManager; import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.PackageParser.ParseFlags; import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.os.Build; import android.util.SparseArray; import libcore.io.IoUtils; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; Loading @@ -38,15 +34,17 @@ import java.util.Collections; * is to be used when an application opts-in to isolated split loading. * @hide */ public class SplitAssetDependencyLoader extends SplitDependencyLoader<PackageParserException> public class SplitAssetDependencyLoader extends SplitDependencyLoader<PackageParser.PackageParserException> implements SplitAssetLoader { private final String[] mSplitPaths; private final @ParseFlags int mFlags; private final ApkAssets[][] mCachedSplitApks; private final AssetManager[] mCachedAssetManagers; private final int mFlags; private String[][] mCachedPaths; private AssetManager[] mCachedAssetManagers; public SplitAssetDependencyLoader(PackageParser.PackageLite pkg, SparseArray<int[]> dependencies, @ParseFlags int flags) { SparseArray<int[]> dependencies, int flags) { super(dependencies); // The base is inserted into index 0, so we need to shift all the splits by 1. Loading @@ -55,7 +53,7 @@ public class SplitAssetDependencyLoader extends SplitDependencyLoader<PackagePar System.arraycopy(pkg.splitCodePaths, 0, mSplitPaths, 1, pkg.splitCodePaths.length); mFlags = flags; mCachedSplitApks = new ApkAssets[mSplitPaths.length][]; mCachedPaths = new String[mSplitPaths.length][]; mCachedAssetManagers = new AssetManager[mSplitPaths.length]; } Loading @@ -64,60 +62,58 @@ public class SplitAssetDependencyLoader extends SplitDependencyLoader<PackagePar return mCachedAssetManagers[splitIdx] != null; } private static ApkAssets loadApkAssets(String path, @ParseFlags int flags) throws PackageParserException { if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) { throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, "Invalid package file: " + path); private static AssetManager createAssetManagerWithPaths(String[] assetPaths, int flags) throws PackageParser.PackageParserException { final AssetManager assets = new AssetManager(); try { assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); for (String assetPath : assetPaths) { if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(assetPath)) { throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, "Invalid package file: " + assetPath); } try { return ApkAssets.loadFromPath(path); } catch (IOException e) { throw new PackageParserException(PackageManager.INSTALL_FAILED_INVALID_APK, "Failed to load APK at path " + path, e); if (assets.addAssetPath(assetPath) == 0) { throw new PackageParser.PackageParserException( INSTALL_PARSE_FAILED_BAD_MANIFEST, "Failed adding asset path: " + assetPath); } } private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) { final AssetManager assets = new AssetManager(); assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); assets.setApkAssets(apkAssets, false /*invalidateCaches*/); return assets; } catch (Throwable e) { IoUtils.closeQuietly(assets); throw e; } } @Override protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices, int parentSplitIdx) throws PackageParserException { final ArrayList<ApkAssets> assets = new ArrayList<>(); // Include parent ApkAssets. int parentSplitIdx) throws PackageParser.PackageParserException { final ArrayList<String> assetPaths = new ArrayList<>(); if (parentSplitIdx >= 0) { Collections.addAll(assets, mCachedSplitApks[parentSplitIdx]); Collections.addAll(assetPaths, mCachedPaths[parentSplitIdx]); } // Include this ApkAssets. assets.add(loadApkAssets(mSplitPaths[splitIdx], mFlags)); // Load and include all config splits for this feature. assetPaths.add(mSplitPaths[splitIdx]); for (int configSplitIdx : configSplitIndices) { assets.add(loadApkAssets(mSplitPaths[configSplitIdx], mFlags)); assetPaths.add(mSplitPaths[configSplitIdx]); } // Cache the results. mCachedSplitApks[splitIdx] = assets.toArray(new ApkAssets[assets.size()]); mCachedAssetManagers[splitIdx] = createAssetManagerWithAssets(mCachedSplitApks[splitIdx]); mCachedPaths[splitIdx] = assetPaths.toArray(new String[assetPaths.size()]); mCachedAssetManagers[splitIdx] = createAssetManagerWithPaths(mCachedPaths[splitIdx], mFlags); } @Override public AssetManager getBaseAssetManager() throws PackageParserException { public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException { loadDependenciesForSplit(0); return mCachedAssetManagers[0]; } @Override public AssetManager getSplitAssetManager(int idx) throws PackageParserException { public AssetManager getSplitAssetManager(int idx) throws PackageParser.PackageParserException { // Since we insert the base at position 0, and PackageParser keeps splits separate from // the base, we need to adjust the index. loadDependenciesForSplit(idx + 1); Loading