Fabricated Runtime Resources Overlay (FRRO) 是 Android 12 引入的一项新功能,它让开发者可以用代码或 shell 命令的方式动态操作 Runtime Resources Overlay(RRO) 而不需要像以前一样必须创建一个单独的 overlay app。然而四年后,网络上关于 FRRO 的文章依然少得可怜,所以在这里记录一次我调试 FRRO 问题的过程。
推荐阅读:
起因:FRRO 失效?在 PackageManagerService 启动的时候会调用如下伪代码获取某个 string:
1 2 3 public static String getConfig (Context systemContext) { return systemContext.getResources().getString(R.string.config_xxxxxx); }
在 frameworks/base/core/res/res/values/config.xml 内有如下配置:
1 <string name ="config_xxxxxx" > </string >
系统内部有一个包名为 android.auto_generated_rro_product__ 的 RRO app 对该 string 进行了 overlay:
1 <string name ="config_xxxxxx" > From RRO</string >
现在我想进行动态调试,快速修改这个 string 的值并观察系统反应。以往我们需要修改 RRO 中的值并重新编译系统,现在让我们试试 FRRO,root 下使用命令 cmd overlay fabricate --target android --name test android:string/config_xxxxxx test 创建一个 FRRO 然后 cmd overlay enable 一下,然后重启系统,看看反应! 嗯,一点反应都没有……在预期里,应该是哪里没做对,俗话说遇事不决就重启,换个重启方式试试?事实证明,无论是杀掉 zygote 触发软重启,还是使用 reboot 或 svc power reboot 发起真正的重启都没有任何改变。 那跑命令看一下 overlay 有没有生效?还好命令行也是能查询资源值的:cmd overlay lookup android android:string/config_xxxxxx。看输出结果确实已经被替换了。 脑袋要烧了,写代码验证一下是不是真的被替换了吧:
1 2 Resources res = Resouces.getSystem(); print(res.getString(res.getIdentifier("config_xxxxxx" , "string" , "android" )));
神奇的事情发生了,代码输出的结果是来自 RRO 的值,FRRO 没有替换成功!为什么呢? 会不会是出现某种权限问题,导致 app 进程无法打开 FRRO 所需的文件,所以 FRRO 没有生效呢?验证这个结论很简单,cat /proc/$(pidof 进程名)/maps | grep frro 看一下有没有我们自己的 FRRO 路径就好了。经过确认,是有的,这个结论不攻自破。 那会不会是系统原本自带的 RRO 比我们的 FRRO 优先级更高所以被优先使用了呢?cmd overlay dump 看一下就能知道新建的 FRRO 是启用状态,且优先级已经是最高的 2147483647。反之如果真的是这样,那么 cmd overlay lookup 也不会返回我们的值。又是一条死路。 这个时候我发现一个更神奇的现象:把上面代码的 Resouces.getSystem() 换成 context.getResources(),结果就正常了。 是什么导致了这个差异?它是 FRRO 对 PackageManagerService 内代码不生效的原因吗?想搞清楚这个问题,只能钻一遍 RRO 的加载流程了……
不可变 RRO 在 Zygote 中的预加载想搞清楚 Resources.getSystem() 和 context.getResources() 的差别,首先让我们搞清楚它们都是从哪来的。点开 Resources.getSystem() 看一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static Resources getSystem () { synchronized (sSync) { Resources ret = mSystem; if (ret == null ) { ret = new Resources(); mSystem = ret; } return ret; } }
它的注释里明确表示 is not affected by Runtime Resource Overlay,似乎我们的问题就这么简单地解决了,只是简单的 API 用错了而已……? 如果 Resources.getSystem() 真的完全不受 RRO 影响,那测试代码应该输出来自 frameworks/base/core/res/res/values/config.xml 的空值而不是来自 RRO 的值。所以问题并没有这么简单,我们继续。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @UnsupportedAppUsage private Resources () { mClassLoader = ClassLoader.getSystemClassLoader(); sResourcesHistory.add(this ); final DisplayMetrics metrics = new DisplayMetrics(); metrics.setToDefaults(); final Configuration config = new Configuration(); config.setToDefaults(); mResourcesImpl = new ResourcesImpl(AssetManager.getSystem(), metrics, config, new DisplayAdjustments()); }
注意 AssetManager.getSystem():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 @UnsupportedAppUsage public static AssetManager getSystem () { synchronized (sSync) { createSystemAssetsInZygoteLocked(false , FRAMEWORK_APK_PATH); return sSystem; } } @GuardedBy ("sSync" )@VisibleForTesting public static void createSystemAssetsInZygoteLocked (boolean reinitialize, String frameworkPath) { if (sSystem != null && !reinitialize) { return ; } try { final ArrayList<ApkAssets> apkAssets = new ArrayList<>(); apkAssets.add(ApkAssets.loadFromPath(frameworkPath, ApkAssets.PROPERTY_SYSTEM)); final String[] systemIdmapPaths = RavenwoodEnvironment.getInstance().isRunningOnRavenwood() ? new String[0 ] : OverlayConfig.getZygoteInstance().createImmutableFrameworkIdmapsInZygote(); for (String idmapPath : systemIdmapPaths) { apkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, ApkAssets.PROPERTY_SYSTEM)); } sSystemApkAssetsSet = new ArraySet<>(apkAssets); sSystemApkAssets = apkAssets.toArray(new ApkAssets[0 ]); if (sSystem == null ) { sSystem = new AssetManager(true ); } sSystem.setApkAssets(sSystemApkAssets, false ); } catch (IOException e) { throw new IllegalStateException("Failed to create system AssetManager" , e); } }
我们在这里首次看见了 overlay 字眼,OverlayConfig 这个类看名字就是解析 overlay 相关配置文件的,看一下它怎么做的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 @VisibleForTesting public OverlayConfig (@Nullable File rootDirectory, @Nullable Supplier<OverlayScanner> scannerFactory, @Nullable PackageProvider packageProvider) { Preconditions.checkArgument((scannerFactory == null ) != (packageProvider == null ), "scannerFactory and packageProvider cannot be both null or both non-null" ); final ArrayList<OverlayPartition> partitions; if (rootDirectory == null ) { partitions = new ArrayList<>( PackagePartitions.getOrderedPartitions(OverlayPartition::new )); } else { partitions = new ArrayList<>(PackagePartitions.getOrderedPartitions( p -> new OverlayPartition( new File(rootDirectory, p.getNonConicalFolder().getPath()), p))); } mIsDefaultPartitionOrder = !sortPartitions(PARTITION_ORDER_FILE_PATH, partitions); mPartitionOrder = generatePartitionOrderString(partitions); ArrayMap<Integer, List<String>> activeApexesPerPartition = getActiveApexes(partitions); final Map<String, ParsedOverlayInfo> packageManagerOverlayInfos = packageProvider == null ? null : getOverlayPackageInfos(packageProvider); final ArrayList<ParsedConfiguration> overlays = new ArrayList<>(); for (int i = 0 , n = partitions.size(); i < n; i++) { final OverlayPartition partition = partitions.get(i); final OverlayScanner scanner = (scannerFactory == null ) ? null : scannerFactory.get(); final ArrayList<ParsedConfiguration> partitionOverlays = OverlayConfigParser.getConfigurations(partition, scanner, packageManagerOverlayInfos, activeApexesPerPartition.getOrDefault(partition.type, Collections.emptyList())); if (partitionOverlays != null ) { overlays.addAll(partitionOverlays); continue ; } final ArrayList<ParsedOverlayInfo> partitionOverlayInfos; if (scannerFactory != null ) { partitionOverlayInfos = new ArrayList<>(scanner.getAllParsedInfos()); } else { partitionOverlayInfos = new ArrayList<>(packageManagerOverlayInfos.values()); for (int j = partitionOverlayInfos.size() - 1 ; j >= 0 ; j--) { if (!partition.containsFile(partitionOverlayInfos.get(j) .getOriginalPartitionPath())) { partitionOverlayInfos.remove(j); } } } final ArrayList<ParsedConfiguration> partitionConfigs = new ArrayList<>(); for (int j = 0 , m = partitionOverlayInfos.size(); j < m; j++) { final ParsedOverlayInfo p = partitionOverlayInfos.get(j); if (p.isStatic) { partitionConfigs.add(new ParsedConfiguration(p.packageName, true , false , partition.policy, p, null )); } } partitionConfigs.sort(sStaticOverlayComparator); overlays.addAll(partitionConfigs); } for (int i = 0 , n = overlays.size(); i < n; i++) { final ParsedConfiguration config = overlays.get(i); mConfigurations.put(config.packageName, new Configuration(config, i)); } } @VisibleForTesting public ArrayList<IdmapInvocation> getImmutableFrameworkOverlayIdmapInvocations () { final ArrayList<IdmapInvocation> idmapInvocations = new ArrayList<>(); final ArrayList<Configuration> sortedConfigs = getSortedOverlays(); for (int i = 0 , n = sortedConfigs.size(); i < n; i++) { final Configuration overlay = sortedConfigs.get(i); if (overlay.parsedConfig.mutable || !overlay.parsedConfig.enabled || !"android" .equals(overlay.parsedConfig.parsedInfo.targetPackageName)) { continue ; } final boolean enforceOverlayable = overlay.parsedConfig.parsedInfo.targetSdkVersion >= Build.VERSION_CODES.Q; IdmapInvocation invocation = null ; if (!idmapInvocations.isEmpty()) { final IdmapInvocation last = idmapInvocations.get(idmapInvocations.size() - 1 ); if (last.enforceOverlayable == enforceOverlayable && last.policy.equals(overlay.parsedConfig.policy)) { invocation = last; } } if (invocation == null ) { invocation = new IdmapInvocation(enforceOverlayable, overlay.parsedConfig.policy); idmapInvocations.add(invocation); } invocation.overlayPaths.add(overlay.parsedConfig.parsedInfo.path.getAbsolutePath()); } return idmapInvocations; }
首先在构造函数里扫描了 /apex 及其他系统分区内所有的 apk 文件,记录下所有的 overlay app 然后将所有在启用状态、不可变且目标是 android 的 overlay app 返回给 zygote 预加载。具体会扫描的分区如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private static final ArrayList<SystemPartition> SYSTEM_PARTITIONS = new ArrayList<>(Arrays.asList( new SystemPartition(Environment.getRootDirectory(), PARTITION_SYSTEM, Partition.PARTITION_NAME_SYSTEM, true , false ), new SystemPartition(Environment.getVendorDirectory(), PARTITION_VENDOR, Partition.PARTITION_NAME_VENDOR, true , true ), new SystemPartition(Environment.getOdmDirectory(), PARTITION_ODM, Partition.PARTITION_NAME_ODM, true , true ), new SystemPartition(Environment.getOemDirectory(), PARTITION_OEM, Partition.PARTITION_NAME_OEM, false , true ), new SystemPartition(Environment.getProductDirectory(), PARTITION_PRODUCT, Partition.PARTITION_NAME_PRODUCT, true , true ), new SystemPartition(Environment.getSystemExtDirectory(), PARTITION_SYSTEM_EXT, Partition.PARTITION_NAME_SYSTEM_EXT, true , true )));
扫描逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public void scanDir (File partitionOverlayDir) { if (!partitionOverlayDir.exists() || !partitionOverlayDir.isDirectory()) { return ; } if (!partitionOverlayDir.canRead()) { Log.w(TAG, "Directory " + partitionOverlayDir + " cannot be read" ); return ; } final File[] files = partitionOverlayDir.listFiles(); if (files == null ) { return ; } for (int i = 0 ; i < files.length; i++) { final File f = files[i]; if (f.isDirectory()) { scanDir(f); } if (!f.isFile() || !f.getPath().endsWith(".apk" )) { continue ; } final ParsedOverlayInfo info = parseOverlayManifest(f, mExcludedOverlayPackages); if (info == null ) { continue ; } mParsedOverlayInfos.put(info.packageName, info); } }
以上逻辑都在 zygote 里完成,存在一个专门的 Resources.preloadResources() 函数用来预加载:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @UnsupportedAppUsage public static void preloadResources () { try { final Resources sysRes = Resources.getSystem(); sysRes.startPreloading(); if (PRELOAD_RESOURCES) { Log.i(TAG, "Preloading resources..." ); long startTime = SystemClock.uptimeMillis(); TypedArray ar = sysRes.obtainTypedArray( com.android.internal.R.array.preloaded_drawables); int numberOfEntries = preloadDrawables(sysRes, ar); ar.recycle(); Log.i(TAG, "...preloaded " + numberOfEntries + " resources in " + (SystemClock.uptimeMillis() - startTime) + "ms." ); startTime = SystemClock.uptimeMillis(); ar = sysRes.obtainTypedArray( com.android.internal.R.array.preloaded_color_state_lists); numberOfEntries = preloadColorStateLists(sysRes, ar); ar.recycle(); Log.i(TAG, "...preloaded " + numberOfEntries + " resources in " + (SystemClock.uptimeMillis() - startTime) + "ms." ); } sysRes.finishPreloading(); } catch (RuntimeException e) { Log.w(TAG, "Failure preloading resources" , e); } }
而 FRRO 位于 /data/resource/cache 下,以 .frro 为后缀,显然扫描结果不可能包含任何一个 FRRO,所以不会被 zygote 预加载也不会影响 Resources.getSystem()。而虽然 android.auto_generated_rro_product__ 所属的 /product/overlay 下没有 config.xml 文件,但它被配置为静态 RRO:
1 2 3 4 <overlay android:priority ="1" android:targetPackage ="android" android:isStatic ="true" />
静态 RRO 默认就是启用且不可变的,所以被 zygote 预加载了。通过 cat /proc/$(pidof zygote)/maps | grep idmap 也可以确认输出结果含有 android.auto_generated_rro_product__ 的 idmap 但没有 FRRO 的。
可变 RRO 的加载上面这么一大串生效的前提是 1. RRO 不是 FRRO;2. RRO 不可变且处于启用状态。我们还是没有找到 FRRO 的处理逻辑,不知道这段代码藏在哪处。我们用代码或者 shell 命令创建 FRRO 的时候实际上是在和 OverlayManagerService 交互,看一下它的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @NonNull Set<UserPackage> registerFabricatedOverlay ( @NonNull final FabricatedOverlayInternal overlay) throws OperationFailedException { if (FrameworkParsingPackageUtils.validateName(overlay.overlayName, false , true ) != null ) { throw new OperationFailedException( "overlay name can only consist of alphanumeric characters, '_', and '.'" ); } final FabricatedOverlayInfo info = mIdmapManager.createFabricatedOverlay(overlay); if (info == null ) { throw new OperationFailedException("failed to create fabricated overlay" ); } final Set<UserPackage> updatedTargets = new ArraySet<>(); for (int userId : mSettings.getUsers()) { updatedTargets.addAll(registerFabricatedOverlay(info, userId)); } return updatedTargets; }
调用了 Idmap2Service 来实际创建 FRRO:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 constexpr std ::string_view kIdmapCacheDir = "/data/resource-cache" ;Status Idmap2Service::createFabricatedOverlay( const os::FabricatedOverlayInternal& overlay, std ::optional<os::FabricatedOverlayInfo>* _aidl_return) { idmap2::FabricatedOverlay::Builder builder (overlay.packageName, overlay.overlayName, overlay.targetPackageName) ; if (!overlay.targetOverlayable.empty()) { builder.SetOverlayable(overlay.targetOverlayable); } for (const auto & res : overlay.entries) { if (res.dataType == Res_value::TYPE_STRING) { builder.SetResourceValue(res.resourceName, res.dataType, res.stringData.value(), res.configuration.value_or(std ::string ())); } else if (res.binaryData.has_value()) { builder.SetResourceValue(res.resourceName, res.binaryData->get(), res.binaryDataOffset, res.binaryDataSize, res.configuration.value_or(std ::string ()), res.isNinePatch); } else { builder.SetResourceValue(res.resourceName, res.dataType, res.data, res.configuration.value_or(std ::string ())); } } std ::string path; std ::string file_name; do { constexpr size_t kSuffixLength = 4 ; const std ::string random_suffix = RandomStringForPath(kSuffixLength); file_name = StringPrintf("%s-%s-%s.frro" , overlay.packageName.c_str(), overlay.overlayName.c_str(), random_suffix.c_str()); path = StringPrintf("%s/%s" , kIdmapCacheDir.data(), file_name.c_str()); const size_t kMaxFileNameLength = 255 ; if (file_name.size() > kMaxFileNameLength) { return error( base::StringPrintf("fabricated overlay file name '%s' longer than %zu characters" , file_name.c_str(), kMaxFileNameLength)); } } while (std ::filesystem::exists(path)); builder.setFrroPath(path); const uid_t uid = IPCThreadState::self()->getCallingUid(); if (!UidHasWriteAccessToPath(uid, path)) { return error(base::StringPrintf("will not write to %s: calling uid %d lacks write access" , path.c_str(), uid)); } const auto frro = builder.Build(); if (!frro) { return error(StringPrintf("failed to serialize '%s:%s': %s" , overlay.packageName.c_str(), overlay.overlayName.c_str(), frro.GetErrorMessage().c_str())); } umask(kIdmapFilePermissionMask); std ::ofstream fout (path) ; if (fout.fail()) { return error("failed to open frro path " + path); } auto result = frro->ToBinaryStream(fout); if (!result) { unlink(path.c_str()); return error("failed to write to frro path " + path + ": " + result.GetErrorMessage()); } if (fout.fail()) { unlink(path.c_str()); return error("failed to write to frro path " + path); } os::FabricatedOverlayInfo out_info; out_info.packageName = overlay.packageName; out_info.overlayName = overlay.overlayName; out_info.targetPackageName = overlay.targetPackageName; out_info.targetOverlayable = overlay.targetOverlayable; out_info.path = path; *_aidl_return = out_info; return ok(); }
根据调用者的包名和 overlay 的名字,在 /data/resource-cache 下随机生成 FRRO 文件。猜测应该会有个地方列出这个文件夹里所有的 FRRO 然后逐个加载,搜索发现 OverlayManagerService 启动时会调用 OverlayManagerServiceImpl.updateOverlaysForUser():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 @NonNull ArraySet<UserPackage> updateOverlaysForUser (final int newUserId) { if (DEBUG) { Slog.d(TAG, "updateOverlaysForUser newUserId=" + newUserId); } final ArraySet<UserPackage> updatedTargets = new ArraySet<>(); final ArrayMap<String, PackageState> userPackages = mPackageManager.initializeForUser( newUserId); CollectionUtils.addAll(updatedTargets, removeOverlaysForUser( (info) -> !userPackages.containsKey(info.packageName), newUserId)); final ArraySet<String> overlaidByOthers = new ArraySet<>(); for (PackageState packageState : userPackages.values()) { var pkg = packageState.getAndroidPackage(); final String overlayTarget = pkg == null ? null : pkg.getOverlayTarget(); if (!TextUtils.isEmpty(overlayTarget)) { overlaidByOthers.add(overlayTarget); } } for (int i = 0 , n = userPackages.size(); i < n; i++) { final PackageState packageState = userPackages.valueAt(i); var pkg = packageState.getAndroidPackage(); if (pkg == null ) { continue ; } var packageName = packageState.getPackageName(); try { CollectionUtils.addAll(updatedTargets, updatePackageOverlays(pkg, newUserId, 0 )); if (overlaidByOthers.contains(packageName)) { updatedTargets.add(UserPackage.of(newUserId, packageName)); } } catch (OperationFailedException e) { Slog.e(TAG, "failed to initialize overlays of '" + packageName + "' for user " + newUserId + "" , e); } } for (final FabricatedOverlayInfo info : getFabricatedOverlayInfos()) { try { CollectionUtils.addAll(updatedTargets, registerFabricatedOverlay( info, newUserId)); } catch (OperationFailedException e) { Slog.e(TAG, "failed to initialize fabricated overlay of '" + info.path + "' for user " + newUserId + "" , e); } } final ArraySet<String> enabledCategories = new ArraySet<>(); final ArrayMap<String, List<OverlayInfo>> userOverlays = mSettings.getOverlaysForUser(newUserId); final int userOverlayTargetCount = userOverlays.size(); for (int i = 0 ; i < userOverlayTargetCount; i++) { final List<OverlayInfo> overlayList = userOverlays.valueAt(i); final int overlayCount = overlayList != null ? overlayList.size() : 0 ; for (int j = 0 ; j < overlayCount; j++) { final OverlayInfo oi = overlayList.get(j); if (oi.isEnabled()) { enabledCategories.add(oi.category); } } } for (final String defaultOverlay : mDefaultOverlays) { try { final OverlayIdentifier overlay = new OverlayIdentifier(defaultOverlay); final OverlayInfo oi = mSettings.getOverlayInfo(overlay, newUserId); if (!enabledCategories.contains(oi.category)) { Slog.w(TAG, "Enabling default overlay '" + defaultOverlay + "' for target '" + oi.targetPackageName + "' in category '" + oi.category + "' for user " + newUserId); mSettings.setEnabled(overlay, newUserId, true ); if (updateState(oi, newUserId, 0 )) { CollectionUtils.add(updatedTargets, UserPackage.of(oi.userId, oi.targetPackageName)); } } } catch (OverlayManagerSettings.BadKeyException e) { Slog.e(TAG, "Failed to set default overlay '" + defaultOverlay + "' for user " + newUserId, e); } } cleanStaleResourceCache(); return updatedTargets; }
它收集了所有 overlay app 然后调用 Idmap2Service 遍历 /data/resource-cache 收集所有 FRRO:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 Status Idmap2Service::acquireFabricatedOverlayIterator(int32_t * _aidl_return) { std ::lock_guard l (frro_iter_mutex_) ; if (frro_iter_.has_value()) { LOG(WARNING) << "active ffro iterator was not previously released" ; } frro_iter_ = std ::filesystem::directory_iterator(kIdmapCacheDir); if (frro_iter_id_ == std ::numeric_limits<int32_t >::max()) { frro_iter_id_ = 0 ; } else { ++frro_iter_id_; } *_aidl_return = frro_iter_id_; return ok(); } Status Idmap2Service::nextFabricatedOverlayInfos(int32_t iteratorId, std ::vector <os::FabricatedOverlayInfo>* _aidl_return) { std ::lock_guard l (frro_iter_mutex_) ; constexpr size_t kMaxEntryCount = 100 ; if (!frro_iter_.has_value()) { return error("no active frro iterator" ); } else if (frro_iter_id_ != iteratorId) { return error("incorrect iterator id in a call to next" ); } size_t count = 0 ; auto & entry_iter = *frro_iter_; auto entry_iter_end = end(*frro_iter_); for (; entry_iter != entry_iter_end && count < kMaxEntryCount; ++entry_iter) { auto & entry = *entry_iter; if (!entry.is_regular_file() || !android::IsFabricatedOverlay(entry.path().native())) { continue ; } const auto overlay = FabricatedOverlayContainer::FromPath(entry.path().native()); if (!overlay) { LOG(WARNING) << "Failed to open '" << entry.path() << "': " << overlay.GetErrorMessage(); continue ; } auto info = (*overlay)->GetManifestInfo(); os::FabricatedOverlayInfo out_info; out_info.packageName = std ::move(info.package_name); out_info.overlayName = std ::move(info.name); out_info.targetPackageName = std ::move(info.target_package); out_info.targetOverlayable = std ::move(info.target_name); out_info.path = entry.path(); _aidl_return->emplace_back(std ::move(out_info)); count++; } return ok(); }
计算完所有 RRO 的状态后,通知 PackageManagerService 更新信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @NonNull private List<String> updatePackageManagerLocked (@NonNull Collection<String> targetPackageNames, final int userId) { try { traceBegin(TRACE_TAG_RRO, "OMS#updatePackageManagerLocked " + targetPackageNames); if (DEBUG) { Slog.d(TAG, "Update package manager about changed overlays" ); } final PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class); final boolean updateFrameworkRes = targetPackageNames.contains("android" ); if (updateFrameworkRes) { targetPackageNames = pm.getTargetPackageNames(userId); } final ArrayMap<String, OverlayPaths> pendingChanges = new ArrayMap<>(targetPackageNames.size()); synchronized (mLock) { final OverlayPaths frameworkOverlays = mImpl.getEnabledOverlayPaths("android" , userId, false ); for (final String targetPackageName : targetPackageNames) { final var list = new OverlayPaths.Builder(frameworkOverlays); if (!"android" .equals(targetPackageName)) { list.addAll(mImpl.getEnabledOverlayPaths(targetPackageName, userId, true )); } pendingChanges.put(targetPackageName, list.build()); } } final HashSet<String> updatedPackages = new HashSet<>(); final HashSet<String> invalidPackages = new HashSet<>(); pm.setEnabledOverlayPackages(userId, pendingChanges, updatedPackages, invalidPackages); return new ArrayList<>(updatedPackages); } finally { traceEnd(TRACE_TAG_RRO); } }
PackageManagerService 更新受影响包的状态,最重要的是 ApplicationInfo 内的两个字段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @UnsupportedAppUsage public String[] resourceDirs;public String[] overlayPaths;
对应进程启动时会读取它们,创建 Resources 对象的时候会用到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @UnsupportedAppUsage public Resources getResources () { if (mResources == null ) { final String[] splitPaths; try { splitPaths = getSplitPaths(null ); } catch (NameNotFoundException e) { throw new AssertionError("null split not found" ); } if (Process.myUid() == mApplicationInfo.uid) { ResourcesManager.getInstance().initializeApplicationPaths(mResDir, splitPaths); } mResources = ResourcesManager.getInstance().getResources(null , mResDir, splitPaths, mLegacyOverlayDirs, mOverlayPaths, mApplicationInfo.sharedLibraryFiles, null , null , getCompatibilityInfo(), getClassLoader(), null ); } return mResources; }
那如果受影响的进程已经启动了(比如是 system_server 自己)又会是什么情况呢?回到 PackageManagerService#setEnabledOverlayPackages(),里面有一段特殊处理,修改了 android 包对应的 ApplicationInfo:
1 2 3 4 5 6 7 8 9 if (userId == UserHandle.USER_SYSTEM) { for (int i = 0 ; i < numberOfPendingChanges; i++) { final String targetPackageName = pendingChanges.keyAt(i); final OverlayPaths newOverlayPaths = pendingChanges.valueAt(i); maybeUpdateSystemOverlays(targetPackageName, newOverlayPaths); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 private void maybeUpdateSystemOverlays (String targetPackageName, OverlayPaths newOverlayPaths) { if (!mResolverReplaced) { if (targetPackageName.equals("android" )) { if (newOverlayPaths == null ) { mPlatformPackageOverlayPaths = null ; mPlatformPackageOverlayResourceDirs = null ; } else { mPlatformPackageOverlayPaths = newOverlayPaths.getOverlayPaths().toArray( new String[0 ]); mPlatformPackageOverlayResourceDirs = newOverlayPaths.getResourceDirs().toArray( new String[0 ]); } applyUpdatedSystemOverlayPaths(); } } else { if (targetPackageName.equals(mResolveActivity.applicationInfo.packageName)) { if (newOverlayPaths == null ) { mReplacedResolverPackageOverlayPaths = null ; mReplacedResolverPackageOverlayResourceDirs = null ; } else { mReplacedResolverPackageOverlayPaths = newOverlayPaths.getOverlayPaths().toArray(new String[0 ]); mReplacedResolverPackageOverlayResourceDirs = newOverlayPaths.getResourceDirs().toArray(new String[0 ]); } applyUpdatedSystemOverlayPaths(); } } } private void applyUpdatedSystemOverlayPaths () { if (mAndroidApplication == null ) { Slog.i(TAG, "Skipped the AndroidApplication overlay paths update - no app yet" ); } else { mAndroidApplication.overlayPaths = mPlatformPackageOverlayPaths; mAndroidApplication.resourceDirs = mPlatformPackageOverlayResourceDirs; } if (mResolverReplaced) { mResolveActivity.applicationInfo.overlayPaths = mReplacedResolverPackageOverlayPaths; mResolveActivity.applicationInfo.resourceDirs = mReplacedResolverPackageOverlayResourceDirs; } }
通知完 PackageManagerService 后,OverlayManagerService 会通知 ActivityManagerService,由 ActivityManagerService 通知相关进程刷新 ApplicationInfo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @GuardedBy (anyOf = {"this" , "mProcLock" })private void updateApplicationInfoLOSP (@NonNull List<String> packagesToUpdate, boolean updateFrameworkRes, int userId) { if (updateFrameworkRes) { ParsingPackageUtils.readConfigUseRoundIcon(null ); } mProcessList.updateApplicationInfoLOSP(packagesToUpdate, userId, updateFrameworkRes); if (updateFrameworkRes) { final Executor executor = ActivityThread.currentActivityThread().getExecutor(); final DisplayManagerInternal display = LocalServices.getService(DisplayManagerInternal.class); if (display != null ) { executor.execute(display::onOverlayChanged); } if (mWindowManager != null ) { executor.execute(mWindowManager::onOverlayChanged); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 @GuardedBy (anyOf = {"mService" , "mProcLock" })void updateApplicationInfoLOSP (List<String> packagesToUpdate, int userId, boolean updateFrameworkRes) { final ArrayMap<String, ApplicationInfo> applicationInfoByPackage = new ArrayMap<>(); for (int i = packagesToUpdate.size() - 1 ; i >= 0 ; i--) { final String packageName = packagesToUpdate.get(i); final ApplicationInfo ai = mService.getPackageManagerInternal().getApplicationInfo( packageName, STOCK_PM_FLAGS, Process.SYSTEM_UID, userId); if (ai != null ) { applicationInfoByPackage.put(packageName, ai); } } mService.mActivityTaskManager.updateActivityApplicationInfo(userId, applicationInfoByPackage); final ArrayList<WindowProcessController> targetProcesses = new ArrayList<>(); for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) { final ProcessRecord app = mLruProcesses.get(i); if (app.getThread() == null ) { continue ; } if (userId != UserHandle.USER_ALL && app.userId != userId) { continue ; } app.getPkgList().forEachPackage(packageName -> { if (updateFrameworkRes || packagesToUpdate.contains(packageName)) { try { final ApplicationInfo ai = applicationInfoByPackage.get(packageName); if (ai != null ) { if (ai.packageName.equals(app.info.packageName)) { app.info = ai; app.getWindowProcessController().updateApplicationInfo(ai); PlatformCompatCache.getInstance() .onApplicationInfoChanged(ai); } app.getThread().scheduleApplicationInfoChanged(ai); targetProcesses.add(app.getWindowProcessController()); } } catch (RemoteException e) { Slog.w(TAG, String.format("Failed to update %s ApplicationInfo for %s" , packageName, app)); } } }); } mService.mActivityTaskManager.updateAssetConfiguration(targetProcesses, updateFrameworkRes); }
进程收到消息后,刷新 ApplicationInfo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 @VisibleForTesting (visibility = PACKAGE)public void handleApplicationInfoChanged (@NonNull final ApplicationInfo ai) { LoadedApk apk; LoadedApk resApk; synchronized (mResourcesManager) { WeakReference<LoadedApk> ref = mPackages.get(ai.packageName); apk = ref != null ? ref.get() : null ; ref = mResourcePackages.get(ai.packageName); resApk = ref != null ? ref.get() : null ; for (ActivityClientRecord ar : mActivities.values()) { if (ar.activityInfo.applicationInfo.packageName.equals(ai.packageName)) { ar.activityInfo.applicationInfo = ai; if (apk != null || resApk != null ) { ar.packageInfo = apk != null ? apk : resApk; } else { apk = ar.packageInfo; } } } } if (apk != null ) { final ArrayList<String> oldPaths = new ArrayList<>(); LoadedApk.makePaths(this , apk.getApplicationInfo(), oldPaths); apk.updateApplicationInfo(ai, oldPaths); } if (resApk != null ) { final ArrayList<String> oldPaths = new ArrayList<>(); LoadedApk.makePaths(this , resApk.getApplicationInfo(), oldPaths); resApk.updateApplicationInfo(ai, oldPaths); } if (android.content.res.Flags.systemContextHandleAppInfoChanged() && mSystemThread) { final var systemContext = getSystemContext(); if (systemContext.getPackageName().equals(ai.packageName)) { final ArrayList<String> oldPaths = new ArrayList<>(); LoadedApk.makePaths(this , systemContext.getApplicationInfo(), oldPaths); systemContext.mPackageInfo.updateApplicationInfo(ai, oldPaths); } } ResourcesImpl beforeImpl = getApplication().getResources().getImpl(); synchronized (mResourcesManager) { mResourcesManager.applyAllPendingAppInfoUpdates(); } ResourcesImpl afterImpl = getApplication().getResources().getImpl(); if ((beforeImpl != afterImpl) && !Arrays.equals(beforeImpl.getAssets().getApkAssets(), afterImpl.getAssets().getApkAssets())) { List<String> beforeAssets = Arrays.asList(beforeImpl.getAssets().getApkPaths()); List<String> afterAssets = Arrays.asList(afterImpl.getAssets().getApkPaths()); List<String> onlyBefore = new ArrayList<>(beforeAssets); onlyBefore.removeAll(afterAssets); List<String> onlyAfter = new ArrayList<>(afterAssets); onlyAfter.removeAll(beforeAssets); Slog.i(TAG, "ApplicationInfo updating for " + ai.packageName + ", new timestamp: " + ai.createTimestamp + "\nassets removed: " + onlyBefore + "\nassets added: " + onlyAfter); if (DEBUG_APP_INFO) { Slog.v(TAG, "ApplicationInfo updating for " + ai.packageName + ", assets before change: " + beforeAssets + "\n assets after change: " + afterAssets); } } }
看见了熟悉的 updateApplicationInfo(),里面就会重新创建出 Resources 对象了,完成资源的刷新:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 public void updateApplicationInfo (@NonNull ApplicationInfo aInfo, @Nullable List<String> oldPaths) { if (!setApplicationInfo(aInfo)) { return ; } final List<String> newPaths = new ArrayList<>(); makePaths(mActivityThread, aInfo, newPaths); final List<String> addedPaths = new ArrayList<>(newPaths.size()); if (oldPaths != null ) { for (String path : newPaths) { final String apkName = path.substring(path.lastIndexOf(File.separator)); boolean match = false ; for (String oldPath : oldPaths) { final String oldApkName = oldPath.substring(oldPath.lastIndexOf(File.separator)); if (apkName.equals(oldApkName)) { match = true ; break ; } } if (!match) { addedPaths.add(path); } } } else { addedPaths.addAll(newPaths); } synchronized (mLock) { createOrUpdateClassLoaderLocked(addedPaths); if (mResources != null ) { final String[] splitPaths; try { splitPaths = getSplitPaths(null ); } catch (NameNotFoundException e) { throw new AssertionError("null split not found" ); } mResources = ResourcesManager.getInstance().getResources(null , mResDir, splitPaths, mLegacyOverlayDirs, mOverlayPaths, mApplicationInfo.sharedLibraryFiles, null , null , getCompatibilityInfo(), getClassLoader(), mApplication == null ? null : mApplication.getResources().getLoaders()); } } mAppComponentFactory = createAppFactory(aInfo, mDefaultClassLoader); }
至此整个加载流程完成。
问题解答回到我们开头抛出的两个问题:
为什么 FRRO 对 Resources.getSystem() 不生效?答:Resources.getSystem() 反映的是 zygote 中预加载的系统资源,只有不可变且启用的传统 app 格式的 RRO 会对其生效。 为什么 PackageManagerService 内没有读取到 FRRO 替换的数据?答:首先肯定跟 Resources.getSystem() 没有关系,看示意代码就已经能知道是用 context 拿的 Resources 了……那根据我们之前的分析,FRRO 应该会生效,所以肯定还存在什么我们还没有发现的东西。其实答案很简单,点开 SystemServer.java 看一下系统服务的启动顺序:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 private void startBootstrapServices (@NonNull TimingsTraceAndSlog t) { t.traceBegin("startBootstrapServices" ); t.traceBegin("StartPackageManagerService" ); try { Watchdog.getInstance().pauseWatchingCurrentThread("packagemanagermain" ); mPackageManagerService = PackageManagerService.main( mSystemContext, installer, domainVerificationService, mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF); } finally { Watchdog.getInstance().resumeWatchingCurrentThread("packagemanagermain" ); } t.traceBegin("SetSystemProcess" ); mActivityManagerService.setSystemProcess(); t.traceEnd(); t.traceBegin("StartOverlayManagerService" ); mSystemServiceManager.startService(new OverlayManagerService(mSystemContext)); t.traceEnd(); t.traceEnd(); }
我们前面提到过,所有可变 RRO 及所有 FRRO 都是在 OverlayManagerService 启动的时候被处理然后加入到 Resources 中的,而 PackageManagerService 刚好在 OverlayManagerService 之前启动(毕竟 OverlayManagerService 的初始化还要依赖 PackageManagerService 呢),在 PackageManagerService 去 getString() 的时候 FRRO 根本还没被加载呢,自然不可能读到我们预期的值,只能读到已经在 zygote 里被预加载的另一个 RRO 设定的值…… 总结FRRO 是一个很好的新东西,但是仍然不能完全替代传统的 app 格式的 RRO,除了能替换的资源类型受限之外,如果需要替换在系统启动非常早期就需要获取的值,遇到的加载时序问题可以说是根本无法解决的。另一方面,Google 的文档真的可用性堪忧,FRRO 这个特性可以说是完全没有任何文档,只能硬啃代码。这次可以说是踩了大部分人都碰不到的坑,以后还是老老实实用有文档的东西吧……