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

Commit c086fe9a authored by Winson's avatar Winson
Browse files

Optimize (Parsing)PackageImpl implementation

To speed up recurring ApplicationInfo creation, this caches
the the appInfoFlags/appInfoPrivateFlags at parse time, since
they're immutable at that point. This saves having to calculate
it hundreds of times for all the packages in getInstalledPackages
or similar.

This also saves the base app data directories for the system user,
or user 0, interning the strings since they're shared across
packages with the same volume UUID. A better solution would
cache these paths during boot scan and re-use the strings directly
rather than re-building and relying on interning, but there was
no good mechanism for that.

This decreases string append computation time and compromises
between persistent memory and performance.

This also compacts boolean fields into a bitset, decreasing
the amount persisted in both the object memory and the Parcelable
output.

In combination these changes result in a net decrease in memory
usage, although the difference is neglible, on the order of
~4KB for 181 packages or ~22B a package. The increase in speed is
roughly 2/3rds saved off the total time of generateWithComponents.

An important note is that hideAsParsed/hideAsFinal are now
required to be called because they now calculate the derived
fields to be cached.

Bug: 153656459

Test: atest CrossProfileAppsServiceImplRoboTest
Test: atest PackageManagerComponentLabelIconOverrideTest
Test: atest PackageParserTest
Test: atest UserSystemPackageInstallerTest
Test: atest DexoptUtilsTest
Test: atest com.android.server.pm.parsing
Test: atest PackageInfoUserFieldsTest
Test: atest com.android.server.pm.ScanTests
Test: atest android.content.pm.cts.PackageManagerTest

Change-Id: I977edb9dec720893ccb1ce5b9df33733c408d3c1
parent e78c0050
Loading
Loading
Loading
Loading
+80 −11
Original line number Diff line number Diff line
@@ -63,6 +63,9 @@ import java.util.Set;
/** @hide **/
public class PackageInfoWithoutStateUtils {

    public static final String SYSTEM_DATA_PATH =
            Environment.getDataDirectoryPath() + File.separator + "system";

    @Nullable
    public static PackageInfo generate(ParsingPackageRead pkg, int[] gids,
            @PackageManager.PackageInfoFlags int flags, long firstInstallTime, long lastUpdateTime,
@@ -168,7 +171,8 @@ public class PackageInfoWithoutStateUtils {
                info.instrumentation = new InstrumentationInfo[N];
                for (int i = 0; i < N; i++) {
                    info.instrumentation[i] = generateInstrumentationInfo(
                            pkg.getInstrumentations().get(i), pkg, flags, userId);
                            pkg.getInstrumentations().get(i), pkg, flags, userId,
                            true /* assignUserFields */);
                }
            }
        }
@@ -332,7 +336,8 @@ public class PackageInfoWithoutStateUtils {
            return null;
        }

        return generateApplicationInfoUnchecked(pkg, flags, state, userId);
        return generateApplicationInfoUnchecked(pkg, flags, state, userId,
                true /* assignUserFields */);
    }

    /**
@@ -340,15 +345,23 @@ public class PackageInfoWithoutStateUtils {
     * system server.
     *
     * Prefer {@link #generateApplicationInfo(ParsingPackageRead, int, PackageUserState, int)}.
     *
     * @param assignUserFields whether to fill the returned {@link ApplicationInfo} with user
     *                         specific fields. This can be skipped when building from a system
     *                         server package, as there are cached strings which can be used rather
     *                         than querying and concatenating the comparatively expensive
     *                         {@link Environment#getDataDirectory(String)}}.
     */
    @NonNull
    public static ApplicationInfo generateApplicationInfoUnchecked(@NonNull ParsingPackageRead pkg,
            @PackageManager.ApplicationInfoFlags int flags, PackageUserState state, int userId) {
            @PackageManager.ApplicationInfoFlags int flags, PackageUserState state, int userId,
            boolean assignUserFields) {
        // Make shallow copy so we can store the metadata/libraries safely
        ApplicationInfo ai = pkg.toAppInfoWithoutState();
        // Init handles data directories
        // TODO(b/135203078): Consolidate the data directory logic, remove initForUser
        ai.initForUser(userId);

        if (assignUserFields) {
            assignUserFields(pkg, ai, userId);
        }

        if ((flags & PackageManager.GET_META_DATA) == 0) {
            ai.metaData = null;
@@ -567,9 +580,14 @@ public class PackageInfoWithoutStateUtils {
        return generateProviderInfo(pkg, p, flags, state, null, userId);
    }

    /**
     * @param assignUserFields see {@link #generateApplicationInfoUnchecked(ParsingPackageRead, int,
     *                         PackageUserState, int, boolean)}
     */
    @Nullable
    public static InstrumentationInfo generateInstrumentationInfo(ParsedInstrumentation i,
            ParsingPackageRead pkg, @PackageManager.ComponentInfoFlags int flags, int userId) {
            ParsingPackageRead pkg, @PackageManager.ComponentInfoFlags int flags, int userId,
            boolean assignUserFields) {
        if (i == null) return null;

        InstrumentationInfo ii = new InstrumentationInfo();
@@ -585,10 +603,10 @@ public class PackageInfoWithoutStateUtils {
        ii.splitSourceDirs = pkg.getSplitCodePaths();
        ii.splitPublicSourceDirs = pkg.getSplitCodePaths();
        ii.splitDependencies = pkg.getSplitDependencies();
        ii.dataDir = getDataDir(pkg, userId).getAbsolutePath();
        ii.deviceProtectedDataDir = getDeviceProtectedDataDir(pkg, userId).getAbsolutePath();
        ii.credentialProtectedDataDir = getCredentialProtectedDataDir(pkg,
                userId).getAbsolutePath();

        if (assignUserFields) {
            assignUserFields(pkg, ii, userId);
        }

        if ((flags & PackageManager.GET_META_DATA) == 0) {
            return ii;
@@ -770,4 +788,55 @@ public class PackageInfoWithoutStateUtils {
        return Environment.getDataUserCePackageDirectory(pkg.getVolumeUuid(), userId,
                pkg.getPackageName());
    }

    private static void assignUserFields(ParsingPackageRead pkg, ApplicationInfo info, int userId) {
        // This behavior is undefined for no-state ApplicationInfos when called by a public API,
        // since the uid is never assigned by the system. It will always effectively be appId 0.
        info.uid = UserHandle.getUid(userId, UserHandle.getAppId(info.uid));

        String pkgName = pkg.getPackageName();
        if ("android".equals(pkgName)) {
            info.dataDir = SYSTEM_DATA_PATH;
            return;
        }

        // For performance reasons, all these paths are built as strings
        String baseDataDirPrefix =
                Environment.getDataDirectoryPath(pkg.getVolumeUuid()) + File.separator;
        String userIdPkgSuffix = File.separator + userId + File.separator + pkgName;
        info.credentialProtectedDataDir = baseDataDirPrefix + Environment.DIR_USER_CE
                + userIdPkgSuffix;
        info.deviceProtectedDataDir = baseDataDirPrefix + Environment.DIR_USER_DE + userIdPkgSuffix;

        if (pkg.isDefaultToDeviceProtectedStorage()
                && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
            info.dataDir = info.deviceProtectedDataDir;
        } else {
            info.dataDir = info.credentialProtectedDataDir;
        }
    }

    private static void assignUserFields(ParsingPackageRead pkg, InstrumentationInfo info,
            int userId) {
        String pkgName = pkg.getPackageName();
        if ("android".equals(pkgName)) {
            info.dataDir = SYSTEM_DATA_PATH;
            return;
        }

        // For performance reasons, all these paths are built as strings
        String baseDataDirPrefix =
                Environment.getDataDirectoryPath(pkg.getVolumeUuid()) + File.separator;
        String userIdPkgSuffix = File.separator + userId + File.separator + pkgName;
        info.credentialProtectedDataDir = baseDataDirPrefix + Environment.DIR_USER_CE
                + userIdPkgSuffix;
        info.deviceProtectedDataDir = baseDataDirPrefix + Environment.DIR_USER_DE + userIdPkgSuffix;

        if (pkg.isDefaultToDeviceProtectedStorage()
                && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
            info.dataDir = info.deviceProtectedDataDir;
        } else {
            info.dataDir = info.credentialProtectedDataDir;
        }
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.content.pm.parsing;

import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Intent;
@@ -343,6 +344,6 @@ public interface ParsingPackage extends ParsingPackageRead {

    // TODO(b/135203078): This class no longer has access to ParsedPackage, find a replacement
    //  for moving to the next step
    @Deprecated
    @CallSuper
    Object hideAsParsed();
}
+239 −298

File changed.

Preview size limit exceeded, changes collapsed.

+1 −1
Original line number Diff line number Diff line
@@ -867,7 +867,7 @@ public interface ParsingPackageRead extends Parcelable {
     * @see ApplicationInfo#gwpAsanMode
     * @see R.styleable#AndroidManifest_gwpAsanMode
     */
    public int getGwpAsanMode();
    int getGwpAsanMode();

    // TODO(b/135203078): Hide and enforce going through PackageInfoUtils
    ApplicationInfo toAppInfoWithoutState();
+3 −0
Original line number Diff line number Diff line
@@ -185,6 +185,9 @@ public class ParsingPackageUtils {
                        ParsingPackageUtils.getSigningDetails(pkg, false /* skipVerify */));
            }

            // Need to call this to finish the parsing stage
            pkg.hideAsParsed();

            return input.success(pkg);
        } catch (PackageParser.PackageParserException e) {
            return input.error(PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
Loading