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

Commit b6f95a01 authored by lpeter's avatar lpeter
Browse files

Optimize RegisteredServicesCache ServiceInfo parsing

RegisteredServicesCache appears to be at the root of significant
cycles spent on android.bg during boot. Much of that time appears
to be spent manually parsing service info out of the manifest.
This seems duplicative and potentially wasteful.

Optimize RegisteredServicesCache ServiceInfo parsing by using caches.

Flag: android.content.pm.optimize_parsing_in_registered_services_cache

Bug: 319137634
Test: atest RegisteredServicesCacheTest
Change-Id: Ieba2077098d1580553b2e7726eef228bffc3e806
parent 3da813ee
Loading
Loading
Loading
Loading
+62 −7
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.content.pm;

import android.Manifest;
import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -30,6 +31,7 @@ import android.os.Environment;
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.AttributeSet;
import android.util.IntArray;
@@ -45,11 +47,11 @@ import com.android.internal.util.ArrayUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;

import libcore.io.IoUtils;

import com.google.android.collect.Lists;
import com.google.android.collect.Maps;

import libcore.io.IoUtils;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

@@ -94,6 +96,9 @@ public abstract class RegisteredServicesCache<V> {
    @GuardedBy("mServicesLock")
    private final SparseArray<UserServices<V>> mUserServices = new SparseArray<UserServices<V>>(2);

    @GuardedBy("mServicesLock")
    private final ArrayMap<String, ServiceInfo<V>> mServiceInfoCaches = new ArrayMap<>();

    private static class UserServices<V> {
        @GuardedBy("mServicesLock")
        final Map<V, Integer> persistentServices = Maps.newHashMap();
@@ -323,13 +328,16 @@ public abstract class RegisteredServicesCache<V> {
        public final ComponentName componentName;
        @UnsupportedAppUsage
        public final int uid;
        public final long lastUpdateTime;

        /** @hide */
        public ServiceInfo(V type, ComponentInfo componentInfo, ComponentName componentName) {
        public ServiceInfo(V type, ComponentInfo componentInfo, ComponentName componentName,
                long lastUpdateTime) {
            this.type = type;
            this.componentInfo = componentInfo;
            this.componentName = componentName;
            this.uid = (componentInfo != null) ? componentInfo.applicationInfo.uid : -1;
            this.lastUpdateTime = lastUpdateTime;
        }

        @Override
@@ -490,7 +498,7 @@ public abstract class RegisteredServicesCache<V> {
        final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
        for (ResolveInfo resolveInfo : resolveInfos) {
            try {
                ServiceInfo<V> info = parseServiceInfo(resolveInfo);
                ServiceInfo<V> info = parseServiceInfo(resolveInfo, userId);
                if (info == null) {
                    Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
                    continue;
@@ -638,13 +646,31 @@ public abstract class RegisteredServicesCache<V> {
    }

    @VisibleForTesting
    protected ServiceInfo<V> parseServiceInfo(ResolveInfo service)
    protected ServiceInfo<V> parseServiceInfo(ResolveInfo service, int userId)
            throws XmlPullParserException, IOException {
        android.content.pm.ServiceInfo si = service.serviceInfo;
        ComponentName componentName = new ComponentName(si.packageName, si.name);

        PackageManager pm = mContext.getPackageManager();

        // Check if the service has been in the service cache.
        long lastUpdateTime = -1;
        if (Flags.optimizeParsingInRegisteredServicesCache()) {
            try {
                PackageInfo packageInfo = pm.getPackageInfoAsUser(si.packageName,
                        PackageManager.MATCH_DIRECT_BOOT_AWARE
                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
                lastUpdateTime = packageInfo.lastUpdateTime;

                ServiceInfo<V> serviceInfo = getServiceInfoFromServiceCache(si, lastUpdateTime);
                if (serviceInfo != null) {
                    return serviceInfo;
                }
            } catch (NameNotFoundException | SecurityException e) {
                Slog.d(TAG, "Fail to get the PackageInfo in parseServiceInfo: " + e);
            }
        }

        XmlResourceParser parser = null;
        try {
            parser = si.loadXmlMetaData(pm, mMetaDataName);
@@ -670,8 +696,13 @@ public abstract class RegisteredServicesCache<V> {
            if (v == null) {
                return null;
            }
            final android.content.pm.ServiceInfo serviceInfo = service.serviceInfo;
            return new ServiceInfo<V>(v, serviceInfo, componentName);
            ServiceInfo<V> serviceInfo = new ServiceInfo<V>(v, si, componentName, lastUpdateTime);
            if (Flags.optimizeParsingInRegisteredServicesCache()) {
                synchronized (mServicesLock) {
                    mServiceInfoCaches.put(getServiceCacheKey(si), serviceInfo);
                }
            }
            return serviceInfo;
        } catch (NameNotFoundException e) {
            throw new XmlPullParserException(
                    "Unable to load resources for pacakge " + si.packageName);
@@ -841,4 +872,28 @@ public abstract class RegisteredServicesCache<V> {
        mContext.unregisterReceiver(mExternalReceiver);
        mContext.unregisterReceiver(mUserRemovedReceiver);
    }

    private static String getServiceCacheKey(@NonNull android.content.pm.ServiceInfo serviceInfo) {
        StringBuilder sb = new StringBuilder(serviceInfo.packageName);
        sb.append('-');
        sb.append(serviceInfo.name);
        return sb.toString();
    }

    private ServiceInfo<V> getServiceInfoFromServiceCache(
            @NonNull android.content.pm.ServiceInfo serviceInfo, long lastUpdateTime) {
        String serviceCacheKey = getServiceCacheKey(serviceInfo);
        synchronized (mServicesLock) {
            ServiceInfo<V> serviceCache = mServiceInfoCaches.get(serviceCacheKey);
            if (serviceCache == null) {
                return null;
            }
            if (serviceCache.lastUpdateTime == lastUpdateTime) {
                return serviceCache;
            }
            // The service is not latest, remove it from the cache.
            mServiceInfoCaches.remove(serviceCacheKey);
            return null;
        }
    }
}
+8 −0
Original line number Diff line number Diff line
@@ -383,3 +383,11 @@ flag {
    bug: "334024639"
    description: "Feature flag to check whether a given UID can access a content provider"
}

flag {
    name: "optimize_parsing_in_registered_services_cache"
    namespace: "package_manager_service"
    description: "Feature flag to optimize RegisteredServicesCache ServiceInfo parsing by using caches."
    bug: "319137634"
    is_fixed_read_only: true
}
+3 −2
Original line number Diff line number Diff line
@@ -207,7 +207,8 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
        final ComponentInfo info = new ComponentInfo();
        info.applicationInfo = new ApplicationInfo();
        info.applicationInfo.uid = uid;
        return new RegisteredServicesCache.ServiceInfo<>(type, info, null);
        return new RegisteredServicesCache.ServiceInfo<>(type, info, null /* componentName */,
                0 /* lastUpdateTime */);
    }

    private void assertNotEmptyFileCreated(TestServicesCache cache, int userId) {
@@ -301,7 +302,7 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {

        @Override
        protected ServiceInfo<TestServiceType> parseServiceInfo(
                ResolveInfo resolveInfo) throws XmlPullParserException, IOException {
                ResolveInfo resolveInfo, int userId) throws XmlPullParserException, IOException {
            int size = mServices.size();
            for (int i = 0; i < size; i++) {
                Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.valueAt(i);