Android 11 Application获取PackageInfo逻辑变化

3,880 阅读3分钟

在Android11之前

通过Application Context获取到PackageManager,通过调用getPackageInfo(String packageName, int flags)方法获取PackageInfo

@Override
public PackageInfo getPackageInfo(String packageName, int flags)
        throws NameNotFoundException {
    return getPackageInfoAsUser(packageName, flags, getUserId());
}

@Override
public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
    throws NameNotFoundException {
    try {
        PackageInfo pi = mPM.getPackageInfo(packageName,
        updateFlagsForPackage(flags, userId), userId);
        if (pi != null) {
            return pi;
        }
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
    throw new NameNotFoundException(packageName);
}

其中mPM为一个IPackageManager的代理,IPackageManager是一个aidl接口,最终由PackageManagerService返回这个代理对象。mPM调用getPackageInfo方法来返回我们需要的PackageInfo对象。

在Android11之后

在Android11之后系统增加了一个缓存,用于降低跨进程调用的频率。这里增加了两个新的方法getPackageInfoAsUserCached(String packageName, int flags, int userId)getPacakgeInfoAsuserUncached(String packagename, int flags, int userId)

@Override
public PackageInfo getPackageInfo(String packageName, int flags)
    throws NameNotFoundException {
    return getPackageInfoAsUser(packageName, flags, getUserId());
}

@Override
public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
    throws NameNotFoundException {
    PackageInfo pi =
        getPackageInfoAsUserCached(
        packageName,
        updateFlagsForPackage(flags, userId),
        userId);
    if (pi == null) {
        throw new NameNotFoundException(packageName);
    }
    return pi;
}

当调用getPackageInfoAsUser时,系统不再直接通过mPM代理类获取PackageInfo,而是先去调用getPackageInfoAsUserCached获取已经缓存的PackageInfo,然后在看一下getPackageInfoAsUserCached的实现

public static PackageInfo getPackageInfoAsUserCached(
    String packageName, int flags, int userId) {
    return sPackageInfoCache.query(new PackageInfoQuery(packageName, flags, userId));
}

可以看到是通过sPackageInfoCache这个对象去查找缓存的PackageInfo对象,这里在看一下sPackageInfoCache对象的实现

private static final PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>
            sPackageInfoCache =
    new PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>(
    32, PermissionManager.CACHE_KEY_PACKAGE_INFO) {
    @Override
    protected PackageInfo recompute(PackageInfoQuery query) {
        return getPackageInfoAsUserUncached(
            query.packageName, query.flags, query.userId);
    }
    @Override
    protected PackageInfo maybeCheckConsistency(
        PackageInfoQuery query, PackageInfo proposedResult) {
        // Implementing this debug check for PackageInfo would require a
        // complicated deep comparison, so just bypass it for now.
        return proposedResult;
    }
};

sPackageInfoCache实际上是一个PropertyInvalidateCache类型,PropertyInvalidatedCache通俗上来说,是android11上的新推出的一种基于LRU (Least Recently Used) cache的缓存机制,PropertyInvalidatedCache有以下几个特点:

  1. PropertyInvalidatedCache只在prop中的数据发生变化时进行更新
  2. PropertyInvalidatedCache能够进行自我同步,同时并不会在数据查询的整个过程中持有锁
  3. 当在跨进程的数据传输过程中需要获频繁被读取但是很少改变的数据时,推荐用PropertyInvalidatedCache来做存储

这里可以看一下PropertyInvalidatedCache的query部分的实现

/**
 * Get a value from the cache or recompute it.
 */
public Result query(Query query) {
    // Let access to mDisabled race: it's atomic anyway.
    long currentNonce = (!isDisabledLocal()) ? getCurrentNonce() : NONCE_DISABLED;
    for (;;) {
        if (currentNonce == NONCE_DISABLED || currentNonce == NONCE_UNSET) {
            if (DEBUG) {
                Log.d(TAG,
                      String.format("cache %s %s for %s",
                                    cacheName(),
                                    currentNonce == NONCE_DISABLED ? "disabled" : "unset",
                                    queryToString(query)));
            }
            return recompute(query);
        }
        final Result cachedResult;
        synchronized (mLock) {
            if (currentNonce == mLastSeenNonce) {
                cachedResult = mCache.get(query);

                if (cachedResult != null) mHits++;
            } else {
                if (DEBUG) {
                    Log.d(TAG,
                          String.format("clearing cache %s because nonce changed [%s] -> [%s]",
                                        cacheName(),
                                        mLastSeenNonce, currentNonce));
                }
                mCache.clear();
                mLastSeenNonce = currentNonce;
                cachedResult = null;
            }
        }
        // Cache hit --- but we're not quite done yet.  A value in the cache might need to
        // be augmented in a "refresh" operation.  The refresh operation can combine the
        // old and the new nonce values.  In order to make sure the new parts of the value
        // are consistent with the old, possibly-reused parts, we check the property value
        // again after the refresh and do the whole fetch again if the property invalidated
        // us while we were refreshing.
        if (cachedResult != null) {
            final Result refreshedResult = refresh(cachedResult, query);
            if (refreshedResult != cachedResult) {
                if (DEBUG) {
                    Log.d(TAG, "cache refresh for " + cacheName() + " " + queryToString(query));
                }
                final long afterRefreshNonce = getCurrentNonce();
                if (currentNonce != afterRefreshNonce) {
                    currentNonce = afterRefreshNonce;
                    if (DEBUG) {
                        Log.d(TAG, String.format("restarting %s %s because nonce changed in refresh",
                                                 cacheName(),
                                                 queryToString(query)));
                    }
                    continue;
                }
                synchronized (mLock) {
                    if (currentNonce != mLastSeenNonce) {
                        // Do nothing: cache is already out of date. Just return the value
                        // we already have: there's no guarantee that the contents of mCache
                        // won't become invalid as soon as we return.
                    } else if (refreshedResult == null) {
                        mCache.remove(query);
                    } else {
                        mCache.put(query, refreshedResult);
                    }
                }
                return maybeCheckConsistency(query, refreshedResult);
            }
            if (DEBUG) {
                Log.d(TAG, "cache hit for " + cacheName() + " " + queryToString(query));
            }
            return maybeCheckConsistency(query, cachedResult);
        }
        // Cache miss: make the value from scratch.
        if (DEBUG) {
            Log.d(TAG, "cache miss for " + cacheName() + " " + queryToString(query));
        }
        final Result result = recompute(query);
        synchronized (mLock) {
            // If someone else invalidated the cache while we did the recomputation, don't
            // update the cache with a potentially stale result.
            if (mLastSeenNonce == currentNonce && result != null) {
                mCache.put(query, result);
            }
            mMisses++;
        }
        return maybeCheckConsistency(query, result);
    }
}

mCache实际上是一个LinkedHashMap,当找到缓存结果的时候就返回缓存,否则就会调用recompute方法,而在sPackageInfoCache的定义的地方可以看到recompute方法的就是调用了PackageManager的getPackageInfoAsUserUncached方法

private static PackageInfo getPackageInfoAsUserUncached(
    String packageName, int flags, int userId) {
    try {
        return ActivityThread.getPackageManager().getPackageInfo(packageName, flags, userId);
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

最终在方法内,系统没有再直接使用mPM对象,而是直接通过ActivityThread.getPackageManager()获得到IPackageManager的代理,然后通过getPackageInfo方法返回了PackageInfo对象并缓存了下来。