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
This commit is contained in:
@@ -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());
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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().
|
||||
|
||||
Reference in New Issue
Block a user