Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c4f2605b authored by felkachang's avatar felkachang Committed by Brandon Liu
Browse files

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
parent c3199051
Loading
Loading
Loading
Loading
+98 −0
Original line number Diff line number Diff line
@@ -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());
+18 −0
Original line number Diff line number Diff line
@@ -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.
     *
+1 −94
Original line number Diff line number Diff line
@@ -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().