From c4f2605b0cbde5813bf4030d9209c62cd0839f81 Mon Sep 17 00:00:00 2001 From: felkachang Date: Fri, 4 Nov 2022 08:49:02 +0000 Subject: [PATCH] Fix drawable-state cache expired issue Context: The list of Preloaded drawables is defined in /system/framework/framework-res.apk. It describes a set of drawables to be loaded as the cache. The cache includes the drawables states for each preloaded drawable. For example: we have preloadSet( res/drawable/a.png res/drawable/b.png res/drawable/c_vector_drawable.xml). Because both of loading png and decoding xml to be a vector drawable take I/O and parsing time, in order to prevent app processes from doing again, the drawable state describes where is the decoded image of Bitmap in memory and the data structure of VectorDrawable. When some process wants to load R.drawable.a, it intends to load res/drawable/a.png, it's already in the cache because Zygote has preloaded as the drawable state for res/drawable/a.png. The Resources clones another drawable state and creates a new Drawable instance back to the app. Solution: ResourcesPerfWorkloads sets a special version of the framework resources package but it doesn't rebuild the drawable state cache according to the special framework resources package. When the drawable state cache belonging to the old framework resources package doesn't match the new framework resources package, accessing the preloaded drawables may trigger this kind of issue. The solution is to clear the static caches in ResourcesImpl and load the cache defined in the specified framework resources package. This patch also moves the preload resources tasks from ZygoteInit.java to Resources.java. Bug: 193826688 Test: atest ResourcesPerfWorkloads Change-Id: I0fa25612bf82dd810d1d9443068a9a302a61d53d --- core/java/android/content/res/Resources.java | 98 +++++++++++++++++++ .../android/content/res/ResourcesImpl.java | 18 ++++ .../com/android/internal/os/ZygoteInit.java | 95 +----------------- 3 files changed, 117 insertions(+), 94 deletions(-) diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 9b169499b41fc..c8dbfc9148476 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -53,6 +53,7 @@ import android.graphics.drawable.Drawable.ConstantState; import android.graphics.drawable.DrawableInflater; import android.os.Build; import android.os.Bundle; +import android.os.SystemClock; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AttributeSet; @@ -132,6 +133,11 @@ public class Resources { private static final Object sSync = new Object(); private final Object mUpdateLock = new Object(); + /** + * Controls whether we should preload resources during zygote init. + */ + private static final boolean PRELOAD_RESOURCES = true; + // Used by BridgeResources in layoutlib @UnsupportedAppUsage static Resources mSystem = null; @@ -2666,6 +2672,98 @@ public class Resources { } } + /** + * Load in commonly used resources, so they can be shared across processes. + * + * These tend to be a few Kbytes, but are frequently in the 20-40K range, and occasionally even + * larger. + * @hide + */ + @UnsupportedAppUsage + public static void preloadResources() { + try { + final Resources sysRes = Resources.getSystem(); + sysRes.startPreloading(); + if (PRELOAD_RESOURCES) { + Log.i(TAG, "Preloading resources..."); + + long startTime = SystemClock.uptimeMillis(); + TypedArray ar = sysRes.obtainTypedArray( + com.android.internal.R.array.preloaded_drawables); + int numberOfEntries = preloadDrawables(sysRes, ar); + ar.recycle(); + Log.i(TAG, "...preloaded " + numberOfEntries + " resources in " + + (SystemClock.uptimeMillis() - startTime) + "ms."); + + startTime = SystemClock.uptimeMillis(); + ar = sysRes.obtainTypedArray( + com.android.internal.R.array.preloaded_color_state_lists); + numberOfEntries = preloadColorStateLists(sysRes, ar); + ar.recycle(); + Log.i(TAG, "...preloaded " + numberOfEntries + " resources in " + + (SystemClock.uptimeMillis() - startTime) + "ms."); + + if (sysRes.getBoolean( + com.android.internal.R.bool.config_freeformWindowManagement)) { + startTime = SystemClock.uptimeMillis(); + ar = sysRes.obtainTypedArray( + com.android.internal.R.array.preloaded_freeform_multi_window_drawables); + numberOfEntries = preloadDrawables(sysRes, ar); + ar.recycle(); + Log.i(TAG, "...preloaded " + numberOfEntries + " resource in " + + (SystemClock.uptimeMillis() - startTime) + "ms."); + } + } + sysRes.finishPreloading(); + } catch (RuntimeException e) { + Log.w(TAG, "Failure preloading resources", e); + } + } + + private static int preloadColorStateLists(Resources resources, TypedArray ar) { + final int numberOfEntries = ar.length(); + for (int i = 0; i < numberOfEntries; i++) { + int id = ar.getResourceId(i, 0); + + if (id != 0) { + if (resources.getColorStateList(id, null) == null) { + throw new IllegalArgumentException( + "Unable to find preloaded color resource #0x" + + Integer.toHexString(id) + + " (" + ar.getString(i) + ")"); + } + } + } + return numberOfEntries; + } + + private static int preloadDrawables(Resources resources, TypedArray ar) { + final int numberOfEntries = ar.length(); + for (int i = 0; i < numberOfEntries; i++) { + int id = ar.getResourceId(i, 0); + + if (id != 0) { + if (resources.getDrawable(id, null) == null) { + throw new IllegalArgumentException( + "Unable to find preloaded drawable resource #0x" + + Integer.toHexString(id) + + " (" + ar.getString(i) + ")"); + } + } + } + return numberOfEntries; + } + + /** + * Clear the cache when the framework resources packages is changed. + * @hide + */ + @VisibleForTesting + public static void resetPreloadDrawableStateCache() { + ResourcesImpl.resetDrawableStateCache(); + preloadResources(); + } + /** @hide */ public void dump(PrintWriter pw, String prefix) { pw.println(prefix + "class=" + getClass()); diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index 3bb237ac12287..480e43e2f5d11 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -52,6 +52,7 @@ import android.util.TypedValue; import android.util.Xml; import android.view.DisplayAdjustments; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.GrowingArrayUtils; import libcore.util.NativeAllocationRegistry; @@ -158,6 +159,23 @@ public class ResourcesImpl { sPreloadedDrawables[1] = new LongSparseArray<>(); } + /** + * Clear the cache when the framework resources packages is changed. + * + * It's only used in the test initial function instead of regular app behaviors. It doesn't + * guarantee the thread-safety so mark this with @VisibleForTesting. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + static void resetDrawableStateCache() { + synchronized (sSync) { + sPreloadedDrawables[0].clear(); + sPreloadedDrawables[1].clear(); + sPreloadedColorDrawables.clear(); + sPreloadedComplexColors.clear(); + sPreloaded = false; + } + } + /** * Creates a new ResourcesImpl object with CompatibilityInfo. * diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 85cb15bdd9061..b3938b4b33fbe 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -26,7 +26,6 @@ import android.app.ApplicationLoaders; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.SharedLibraryInfo; import android.content.res.Resources; -import android.content.res.TypedArray; import android.os.Build; import android.os.Environment; import android.os.IInstalld; @@ -101,22 +100,11 @@ public class ZygoteInit { // --usap-socket-name parameter. private static final String SOCKET_NAME_ARG = "--socket-name="; - /** - * Used to pre-load resources. - */ - @UnsupportedAppUsage - private static Resources mResources; - /** * The path of a file that contains classes to preload. */ private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes"; - /** - * Controls whether we should preload resources during zygote init. - */ - private static final boolean PRELOAD_RESOURCES = true; - private static final int UNPRIVILEGED_UID = 9999; private static final int UNPRIVILEGED_GID = 9999; @@ -143,7 +131,7 @@ public class ZygoteInit { cacheNonBootClasspathClassLoaders(); bootTimingsTraceLog.traceEnd(); // CacheNonBootClasspathClassLoaders bootTimingsTraceLog.traceBegin("PreloadResources"); - preloadResources(); + Resources.preloadResources(); bootTimingsTraceLog.traceEnd(); // PreloadResources Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadAppProcessHALs"); nativePreloadAppProcessHALs(); @@ -413,87 +401,6 @@ public class ZygoteInit { }); } - /** - * Load in commonly used resources, so they can be shared across processes. - * - * These tend to be a few Kbytes, but are frequently in the 20-40K range, and occasionally even - * larger. - */ - private static void preloadResources() { - try { - mResources = Resources.getSystem(); - mResources.startPreloading(); - if (PRELOAD_RESOURCES) { - Log.i(TAG, "Preloading resources..."); - - long startTime = SystemClock.uptimeMillis(); - TypedArray ar = mResources.obtainTypedArray( - com.android.internal.R.array.preloaded_drawables); - int N = preloadDrawables(ar); - ar.recycle(); - Log.i(TAG, "...preloaded " + N + " resources in " - + (SystemClock.uptimeMillis() - startTime) + "ms."); - - startTime = SystemClock.uptimeMillis(); - ar = mResources.obtainTypedArray( - com.android.internal.R.array.preloaded_color_state_lists); - N = preloadColorStateLists(ar); - ar.recycle(); - Log.i(TAG, "...preloaded " + N + " resources in " - + (SystemClock.uptimeMillis() - startTime) + "ms."); - - if (mResources.getBoolean( - com.android.internal.R.bool.config_freeformWindowManagement)) { - startTime = SystemClock.uptimeMillis(); - ar = mResources.obtainTypedArray( - com.android.internal.R.array.preloaded_freeform_multi_window_drawables); - N = preloadDrawables(ar); - ar.recycle(); - Log.i(TAG, "...preloaded " + N + " resource in " - + (SystemClock.uptimeMillis() - startTime) + "ms."); - } - } - mResources.finishPreloading(); - } catch (RuntimeException e) { - Log.w(TAG, "Failure preloading resources", e); - } - } - - private static int preloadColorStateLists(TypedArray ar) { - int N = ar.length(); - for (int i = 0; i < N; i++) { - int id = ar.getResourceId(i, 0); - - if (id != 0) { - if (mResources.getColorStateList(id, null) == null) { - throw new IllegalArgumentException( - "Unable to find preloaded color resource #0x" - + Integer.toHexString(id) - + " (" + ar.getString(i) + ")"); - } - } - } - return N; - } - - - private static int preloadDrawables(TypedArray ar) { - int N = ar.length(); - for (int i = 0; i < N; i++) { - int id = ar.getResourceId(i, 0); - - if (id != 0) { - if (mResources.getDrawable(id, null) == null) { - throw new IllegalArgumentException( - "Unable to find preloaded drawable resource #0x" - + Integer.toHexString(id) - + " (" + ar.getString(i) + ")"); - } - } - } - return N; - } - /** * Runs several special GCs to try to clean up a few generations of softly- and final-reachable * objects, along with any other garbage. This is only useful just before a fork().