Update optimizing RegisteredServicesCache ServiceInfo parsing
1.Use component name as the cache key instead of an ad-hoc
constructed string.
2.If no services are asked within 30 seconds, the cache will be
cleared. If the services are asked within 30 seconds, it will
reset the timer to 30 seconds again.
3.Move the logic to read or update the cache in generateServicesMap.
Flag: android.content.pm.optimize_parsing_in_registered_services_cache
Bug: 319137634
Test: atest RegisteredServicesCacheTest
Change-Id: Id2dc224796d8259d5274237e40c2db3a89448889
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 9d11710..8266384 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -85,6 +85,8 @@
private static final boolean DEBUG = false;
protected static final String REGISTERED_SERVICES_DIR = "registered_services";
+ static final long SERVICE_INFO_CACHES_TIMEOUT_MILLIS = 30000; // 30 seconds
+
public final Context mContext;
private final String mInterfaceName;
private final String mMetaDataName;
@@ -96,8 +98,18 @@
@GuardedBy("mServicesLock")
private final SparseArray<UserServices<V>> mUserServices = new SparseArray<UserServices<V>>(2);
- @GuardedBy("mServicesLock")
- private final ArrayMap<String, ServiceInfo<V>> mServiceInfoCaches = new ArrayMap<>();
+ @GuardedBy("mServiceInfoCaches")
+ private final ArrayMap<ComponentName, ServiceInfo<V>> mServiceInfoCaches = new ArrayMap<>();
+
+ private final Handler mBackgroundHandler;
+
+ private final Runnable mClearServiceInfoCachesRunnable = new Runnable() {
+ public void run() {
+ synchronized (mServiceInfoCaches) {
+ mServiceInfoCaches.clear();
+ }
+ }
+ };
private static class UserServices<V> {
@GuardedBy("mServicesLock")
@@ -172,9 +184,9 @@
if (isCore) {
intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
}
- Handler handler = BackgroundThread.getHandler();
+ mBackgroundHandler = BackgroundThread.getHandler();
mContext.registerReceiverAsUser(
- mPackageReceiver, UserHandle.ALL, intentFilter, null, handler);
+ mPackageReceiver, UserHandle.ALL, intentFilter, null, mBackgroundHandler);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
@@ -183,7 +195,7 @@
if (isCore) {
sdFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
}
- mContext.registerReceiver(mExternalReceiver, sdFilter, null, handler);
+ mContext.registerReceiver(mExternalReceiver, sdFilter, null, mBackgroundHandler);
// Register for user-related events
IntentFilter userFilter = new IntentFilter();
@@ -191,7 +203,7 @@
if (isCore) {
userFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
}
- mContext.registerReceiver(mUserRemovedReceiver, userFilter, null, handler);
+ mContext.registerReceiver(mUserRemovedReceiver, userFilter, null, mBackgroundHandler);
}
private void handlePackageEvent(Intent intent, int userId) {
@@ -328,6 +340,10 @@
public final ComponentName componentName;
@UnsupportedAppUsage
public final int uid;
+ /**
+ * The last update time of the package that contains the service.
+ * It's from {@link PackageInfo#lastUpdateTime}.
+ */
public final long lastUpdateTime;
/** @hide */
@@ -342,7 +358,8 @@
@Override
public String toString() {
- return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid;
+ return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid
+ + ", lastUpdateTime " + lastUpdateTime;
}
}
@@ -496,19 +513,56 @@
final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<>();
final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
+ final PackageManager pm = mContext.getPackageManager();
for (ResolveInfo resolveInfo : resolveInfos) {
+ // Check if the service has been in the service cache.
+ long lastUpdateTime = -1;
+ final android.content.pm.ServiceInfo si = resolveInfo.serviceInfo;
+ final ComponentName componentName = si.getComponentName();
+ if (Flags.optimizeParsingInRegisteredServicesCache()) {
+ try {
+ PackageInfo packageInfo = pm.getPackageInfoAsUser(si.packageName,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+ lastUpdateTime = packageInfo.lastUpdateTime;
+ } catch (NameNotFoundException | SecurityException e) {
+ Slog.d(TAG, "Fail to get the PackageInfo in generateServicesMap: " + e);
+ continue;
+ }
+ ServiceInfo<V> serviceInfo = getServiceInfoFromServiceCache(componentName,
+ lastUpdateTime);
+ if (serviceInfo != null) {
+ serviceInfos.add(serviceInfo);
+ continue;
+ }
+ }
try {
- ServiceInfo<V> info = parseServiceInfo(resolveInfo, userId);
+ ServiceInfo<V> info = parseServiceInfo(resolveInfo, lastUpdateTime);
if (info == null) {
Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
continue;
}
serviceInfos.add(info);
+ if (Flags.optimizeParsingInRegisteredServicesCache()) {
+ synchronized (mServiceInfoCaches) {
+ mServiceInfoCaches.put(componentName, info);
+ }
+ }
} catch (XmlPullParserException | IOException e) {
Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
}
}
+ if (Flags.optimizeParsingInRegisteredServicesCache()) {
+ synchronized (mServiceInfoCaches) {
+ if (!mServiceInfoCaches.isEmpty()) {
+ mBackgroundHandler.removeCallbacks(mClearServiceInfoCachesRunnable);
+ mBackgroundHandler.postDelayed(mClearServiceInfoCachesRunnable,
+ SERVICE_INFO_CACHES_TIMEOUT_MILLIS);
+ }
+ }
+ }
+
synchronized (mServicesLock) {
final UserServices<V> user = findOrCreateUserLocked(userId);
final boolean firstScan = user.services == null;
@@ -645,32 +699,18 @@
return false;
}
+ /**
+ * If the service has already existed in the caches, this method will not be called to parse
+ * the service.
+ */
@VisibleForTesting
- protected ServiceInfo<V> parseServiceInfo(ResolveInfo service, int userId)
+ protected ServiceInfo<V> parseServiceInfo(ResolveInfo service, long lastUpdateTime)
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);
@@ -696,13 +736,7 @@
if (v == null) {
return null;
}
- ServiceInfo<V> serviceInfo = new ServiceInfo<V>(v, si, componentName, lastUpdateTime);
- if (Flags.optimizeParsingInRegisteredServicesCache()) {
- synchronized (mServicesLock) {
- mServiceInfoCaches.put(getServiceCacheKey(si), serviceInfo);
- }
- }
- return serviceInfo;
+ return new ServiceInfo<V>(v, si, componentName, lastUpdateTime);
} catch (NameNotFoundException e) {
throw new XmlPullParserException(
"Unable to load resources for pacakge " + si.packageName);
@@ -873,26 +907,13 @@
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) {
+ private ServiceInfo<V> getServiceInfoFromServiceCache(@NonNull ComponentName componentName,
+ long lastUpdateTime) {
+ synchronized (mServiceInfoCaches) {
+ ServiceInfo<V> serviceCache = mServiceInfoCaches.get(componentName);
+ if (serviceCache != null && serviceCache.lastUpdateTime == lastUpdateTime) {
return serviceCache;
}
- // The service is not latest, remove it from the cache.
- mServiceInfoCaches.remove(serviceCacheKey);
return null;
}
}
diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
index 939bf2e..ee4761b 100644
--- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
@@ -76,6 +76,10 @@
mUsers = new ArrayList<>();
mUsers.add(new UserInfo(0, "Owner", UserInfo.FLAG_ADMIN));
mUsers.add(new UserInfo(1, "User1", 0));
+ addServiceInfoIntoResolveInfo(r1, "r1.package.name" /* packageName */,
+ "r1.service.name" /* serviceName */);
+ addServiceInfoIntoResolveInfo(r2, "r2.package.name" /* packageName */,
+ "r2.service.name" /* serviceName */);
}
public void testGetAllServicesHappyPath() {
@@ -218,6 +222,14 @@
assertTrue("File should be created at " + file, file.length() > 0);
}
+ private void addServiceInfoIntoResolveInfo(ResolveInfo resolveInfo, String packageName,
+ String serviceName) {
+ final ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = packageName;
+ serviceInfo.name = serviceName;
+ resolveInfo.serviceInfo = serviceInfo;
+ }
+
/**
* Mock implementation of {@link android.content.pm.RegisteredServicesCache} for testing
*/
@@ -301,8 +313,8 @@
}
@Override
- protected ServiceInfo<TestServiceType> parseServiceInfo(
- ResolveInfo resolveInfo, int userId) throws XmlPullParserException, IOException {
+ protected ServiceInfo<TestServiceType> parseServiceInfo(ResolveInfo resolveInfo,
+ long lastUpdateTime) throws XmlPullParserException, IOException {
int size = mServices.size();
for (int i = 0; i < size; i++) {
Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.valueAt(i);