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

Commit 9ab48e85 authored by Yurii Zubrytskyi's avatar Yurii Zubrytskyi
Browse files

[res] Correct, optimize adding shared lib assets

When we add shared library assets to all ResourceImpl objects
there are several important details:
1. ApkAssets array contains both file-based and loader-based
   assets, and they have to be partitioned in this order,
   otherwise loaders won't be able to overlay all file assets

2. When adding all shared library assets they also may contain
   frros, and those need to be loaded as overlays explicitly

3. No need to repeatedly add each asset path as it involves the
   same steps of checking for duplicates and rebuilding the
   native structures

4. Given it's the constructor and it will force-set the
   configuration later, adding assets can be done in 'preset'
   mode without calculating anything config-related.

Bug: 345562237
Test: build, atest ResourcesManagerTest, simpleperf FRRO changes
Change-Id: Ia000549bbec06d5b8f649c7bff636c4f2a1dac22
parent 9687d080
Loading
Loading
Loading
Loading
+89 −24
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.content.res.Configuration.NativeConfig;
import android.content.res.loader.ResourcesLoader;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
@@ -448,7 +449,7 @@ public final class AssetManager implements AutoCloseable {
    @Deprecated
    @UnsupportedAppUsage
    public int addAssetPath(String path) {
        return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/);
        return addAssetPathInternal(List.of(path), false, false, false);
    }

    /**
@@ -458,7 +459,7 @@ public final class AssetManager implements AutoCloseable {
    @Deprecated
    @UnsupportedAppUsage
    public int addAssetPathAsSharedLibrary(String path) {
        return addAssetPathInternal(path, false /*overlay*/, true /*appAsLib*/);
        return addAssetPathInternal(List.of(path), false, true, false);
    }

    /**
@@ -468,35 +469,103 @@ public final class AssetManager implements AutoCloseable {
    @Deprecated
    @UnsupportedAppUsage
    public int addOverlayPath(String path) {
        return addAssetPathInternal(path, true /*overlay*/, false /*appAsLib*/);
        return addAssetPathInternal(List.of(path), true, false, false);
    }

    /**
     * @hide
     */
    public void addSharedLibraryPaths(@NonNull String[] paths) {
        final int length = paths.length;
        for (int i = 0; i < length; i++) {
            addAssetPathInternal(paths[i], false, true);
    public void addSharedLibraryPaths(@NonNull List<String> paths) {
        addAssetPathInternal(paths, false, true, true);
    }

    private int addAssetPathInternal(List<String> paths, boolean overlay, boolean appAsLib,
            boolean presetAssets) {
        Objects.requireNonNull(paths, "paths");
        if (paths.isEmpty()) {
            return 0;
        }

    private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) {
        Objects.requireNonNull(path, "path");
        synchronized (this) {
            ensureOpenLocked();
            final int count = mApkAssets.length;

            // See if we already have it loaded.
            for (int i = 0; i < count; i++) {
                if (mApkAssets[i].getAssetPath().equals(path)) {
                    return i + 1;
            // See if we already have some of the paths loaded.
            final int originalAssetsCount = mApkAssets.length;

            // Getting an assets' path is a relatively expensive operation, cache them.
            final ArrayMap<String, Integer> assetPaths = new ArrayMap<>(originalAssetsCount);
            for (int i = 0; i < originalAssetsCount; i++) {
                assetPaths.put(mApkAssets[i].getAssetPath(), i);
            }

            final ArrayList<String> newPaths = new ArrayList<>(paths.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);
                } else {
                    lastFoundIndex = index;
                }
            }
            if (newPaths.isEmpty()) {
                return lastFoundIndex + 1;
            }

            final ApkAssets assets;
            final var newAssets = loadAssets(newPaths, overlay, appAsLib);
            if (newAssets.isEmpty()) {
                return 0;
            }
            mApkAssets = makeNewAssetsArrayLocked(newAssets);
            nativeSetApkAssets(mObject, mApkAssets, true, presetAssets);
            invalidateCachesLocked(-1);
            return originalAssetsCount + 1;
        }
    }

    /**
     * Insert the new assets preserving the correct order: all non-loader assets go before all
     * of the loader assets.
     */
    @GuardedBy("this")
    private @NonNull ApkAssets[] makeNewAssetsArrayLocked(
            @NonNull ArrayList<ApkAssets> newNonLoaderAssets) {
        final int originalAssetsCount = mApkAssets.length;
        int firstLoaderIndex = originalAssetsCount;
        for (int i = 0; i < originalAssetsCount; i++) {
            if (mApkAssets[i].isForLoader()) {
                firstLoaderIndex = i;
                break;
            }
        }
        final int newAssetsSize = newNonLoaderAssets.size();
        final var newAssetsArray = new ApkAssets[originalAssetsCount + newAssetsSize];
        if (firstLoaderIndex > 0) {
            // This should always be true, but who knows...
            System.arraycopy(mApkAssets, 0, newAssetsArray, 0, firstLoaderIndex);
        }
        for (int i = 0; i < newAssetsSize; i++) {
            newAssetsArray[firstLoaderIndex + i] = newNonLoaderAssets.get(i);
        }
        if (originalAssetsCount > firstLoaderIndex) {
            System.arraycopy(
                    mApkAssets, firstLoaderIndex,
                    newAssetsArray, firstLoaderIndex + newAssetsSize,
                    originalAssetsCount - firstLoaderIndex);
        }
        return newAssetsArray;
    }

    private static @NonNull ArrayList<ApkAssets> loadAssets(@NonNull ArrayList<String> paths,
            boolean overlay, boolean appAsLib) {
        final int pathsSize = paths.size();
        final var loadedAssets = new ArrayList<ApkAssets>(pathsSize);
        for (int i = 0; i < pathsSize; i++) {
            final var path = paths.get(i);
            try {
                if (overlay) {
                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/"
@@ -507,16 +576,12 @@ public final class AssetManager implements AutoCloseable {
                    assets = ApkAssets.loadFromPath(path,
                            appAsLib ? ApkAssets.PROPERTY_DYNAMIC : 0);
                }
                loadedAssets.add(assets);
            } catch (IOException e) {
                return 0;
                Log.w(TAG, "Failed to load asset, path = " + path, e);
            }

            mApkAssets = Arrays.copyOf(mApkAssets, count + 1);
            mApkAssets[count] = assets;
            nativeSetApkAssets(mObject, mApkAssets, true, false);
            invalidateCachesLocked(-1);
            return count + 1;
        }
        return loadedAssets;
    }

    /** @hide */
+13 −3
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ 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;
@@ -71,6 +72,7 @@ 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;

@@ -205,13 +207,21 @@ public class ResourcesImpl {
            @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
        mAssets = assets;
        if (Flags.registerResourcePaths()) {
            ArrayMap<String, SharedLibraryAssets> sharedLibMap =
            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++) {
                assets.addSharedLibraryPaths(sharedLibMap.valueAt(i).getAllAssetPaths());
                final var paths = sharedLibMap.valueAt(i).getAllAssetPaths();
                for (int j = 0; j < paths.length; j++) {
                    if (uniquePaths.add(paths[j])) {
                        orderedPaths.add(paths[j]);
                    }
            mSharedLibCount = sharedLibMap.size();
                }
            }
            assets.addSharedLibraryPaths(orderedPaths);
            mSharedLibCount = size;
        }
        mMetrics.setToDefaults();
        mDisplayAdjustments = displayAdjustments;