diff options
author | Ryan Mitchell <rtmitchell@google.com> | 2021-01-11 16:01:35 -0800 |
---|---|---|
committer | Ryan Mitchell <rtmitchell@google.com> | 2021-01-13 21:14:31 -0800 |
commit | 14e8ade9a91ea48cb65b3e14954e13a396e1a94d (patch) | |
tree | adbf03b67a1b60910948fa4fdc10726344eaf1ae | |
parent | fb4d09cadd27a3fb1a2e268417f0f511aa92e344 (diff) |
Optimize FilterApkAssets by caching config
ResTable_config of every ResTable_type is read from device every time
AssetManager::RebuildFilterList is invoked. For large APKs (like
framework-res.apk), this causes a large number of page faults
when accessing the config from disk. The configs are also used in the
slow path of AssetManager::FindEntryInternal, which makes it even
slower. Instead cache the config on the TypeSpec of its ApkAsset.
Bug: 177247024
Test: libandroidfw_tests
Change-Id: I66d507c4eeb2399f7558f3d9dfc53c157129ada0
-rw-r--r-- | cmds/idmap2/libidmap2/ResourceMapping.cpp | 6 | ||||
-rw-r--r-- | libs/androidfw/AssetManager2.cpp | 274 | ||||
-rw-r--r-- | libs/androidfw/LoadedArsc.cpp | 94 | ||||
-rw-r--r-- | libs/androidfw/include/androidfw/AssetManager2.h | 29 | ||||
-rw-r--r-- | libs/androidfw/include/androidfw/LoadedArsc.h | 42 | ||||
-rw-r--r-- | libs/androidfw/tests/AssetManager2_test.cpp | 55 | ||||
-rw-r--r-- | libs/androidfw/tests/LoadedArsc_test.cpp | 26 | ||||
-rw-r--r-- | tools/aapt2/process/SymbolTable.cpp | 15 |
8 files changed, 240 insertions, 301 deletions
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp index 9abb9e458902..46eeb8e6ac80 100644 --- a/cmds/idmap2/libidmap2/ResourceMapping.cpp +++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp @@ -285,14 +285,12 @@ Result<ResourceMapping> ResourceMapping::FromApkAssets(const ApkAssets& target_a bool enforce_overlayable, LogInfo& log_info) { AssetManager2 target_asset_manager; - if (!target_asset_manager.SetApkAssets({&target_apk_assets}, true /* invalidate_caches */, - false /* filter_incompatible_configs*/)) { + if (!target_asset_manager.SetApkAssets({&target_apk_assets})) { return Error("failed to create target asset manager"); } AssetManager2 overlay_asset_manager; - if (!overlay_asset_manager.SetApkAssets({&overlay_apk_assets}, true /* invalidate_caches */, - false /* filter_incompatible_configs */)) { + if (!overlay_asset_manager.SetApkAssets({&overlay_apk_assets})) { return Error("failed to create overlay asset manager"); } diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 3f0600040139..03ab62f48870 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -103,10 +103,10 @@ AssetManager2::AssetManager2() { } bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets, - bool invalidate_caches, bool filter_incompatible_configs) { + bool invalidate_caches) { apk_assets_ = apk_assets; BuildDynamicRefTable(); - RebuildFilterList(filter_incompatible_configs); + RebuildFilterList(); if (invalidate_caches) { InvalidateCaches(static_cast<uint32_t>(-1)); } @@ -623,7 +623,8 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry( if (UNLIKELY(logging_enabled)) { last_resolution_.steps.push_back( Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->config.toString(), - overlay_result->package_name}); + overlay_result->package_name, + overlay_result->cookie}); } } } @@ -646,22 +647,21 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal( const LoadedPackage* best_package = nullptr; incfs::verified_map_ptr<ResTable_type> best_type; const ResTable_config* best_config = nullptr; - ResTable_config best_config_copy; uint32_t best_offset = 0U; uint32_t type_flags = 0U; - auto resolution_type = Resolution::Step::Type::NO_ENTRY; std::vector<Resolution::Step> resolution_steps; - // If desired_config is the same as the set configuration, then we can use our filtered list - // and we don't need to match the configurations, since they already matched. - const bool use_fast_path = !ignore_configuration && &desired_config == &configuration_; + // If `desired_config` is not the same as the set configuration or the caller will accept a value + // from any configuration, then we cannot use our filtered list of types since it only it contains + // types matched to the set configuration. + const bool use_filtered = !ignore_configuration && &desired_config == &configuration_; const size_t package_count = package_group.packages_.size(); for (size_t pi = 0; pi < package_count; pi++) { const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi]; const LoadedPackage* loaded_package = loaded_package_impl.loaded_package_; - ApkAssetsCookie cookie = package_group.cookies_[pi]; + const ApkAssetsCookie cookie = package_group.cookies_[pi]; // If the type IDs are offset in this package, we need to take that into account when searching // for a type. @@ -670,130 +670,82 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal( continue; } + // Allow custom loader packages to overlay resource values with configurations equivalent to the + // current best configuration. + const bool package_is_loader = loaded_package->IsCustomLoader(); + auto entry_flags = type_spec->GetFlagsForEntryIndex(entry_idx); if (UNLIKELY(!entry_flags.has_value())) { return base::unexpected(entry_flags.error()); } type_flags |= entry_flags.value(); - // If the package is an overlay or custom loader, - // then even configurations that are the same MUST be chosen. - const bool package_is_loader = loaded_package->IsCustomLoader(); - - if (use_fast_path) { - const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx]; - for (const auto& type_config : filtered_group.type_configs) { - const ResTable_config& this_config = type_config.config; - - // We can skip calling ResTable_config::match() because we know that all candidate - // configurations that do NOT match have been filtered-out. - if (best_config == nullptr) { - resolution_type = Resolution::Step::Type::INITIAL; - } else if (this_config.isBetterThan(*best_config, &desired_config)) { - resolution_type = (package_is_loader) ? Resolution::Step::Type::BETTER_MATCH_LOADER - : Resolution::Step::Type::BETTER_MATCH; - } else if (package_is_loader && this_config.compare(*best_config) == 0) { - resolution_type = Resolution::Step::Type::OVERLAID_LOADER; - } else { - if (UNLIKELY(logging_enabled)) { - resolution_type = (package_is_loader) ? Resolution::Step::Type::SKIPPED_LOADER - : Resolution::Step::Type::SKIPPED; - resolution_steps.push_back(Resolution::Step{resolution_type, - this_config.toString(), - &loaded_package->GetPackageName()}); - } - continue; - } - - // The configuration matches and is better than the previous selection. - // Find the entry value if it exists for this configuration. - const auto& type = type_config.type; - const auto offset = LoadedPackage::GetEntryOffset(type, entry_idx); - if (UNLIKELY(IsIOError(offset))) { - return base::unexpected(offset.error()); - } - if (!offset.has_value()) { - if (UNLIKELY(logging_enabled)) { - if (package_is_loader) { - resolution_type = Resolution::Step::Type::NO_ENTRY_LOADER; - } else { - resolution_type = Resolution::Step::Type::NO_ENTRY; - } - resolution_steps.push_back(Resolution::Step{resolution_type, - this_config.toString(), - &loaded_package->GetPackageName()}); - } - continue; - } - - best_cookie = cookie; - best_package = loaded_package; - best_type = type; - best_config = &this_config; - best_offset = offset.value(); + const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx]; + const size_t type_entry_count = (use_filtered) ? filtered_group.type_entries.size() + : type_spec->type_entries.size(); + for (size_t i = 0; i < type_entry_count; i++) { + const TypeSpec::TypeEntry* type_entry = (use_filtered) ? filtered_group.type_entries[i] + : &type_spec->type_entries[i]; + + // We can skip calling ResTable_config::match() if the caller does not care for the + // configuration to match or if we're using the list of types that have already had their + // configuration matched. + const ResTable_config& this_config = type_entry->config; + if (!(use_filtered || ignore_configuration || this_config.match(desired_config))) { + continue; + } + Resolution::Step::Type resolution_type; + if (best_config == nullptr) { + resolution_type = Resolution::Step::Type::INITIAL; + } else if (this_config.isBetterThan(*best_config, &desired_config)) { + resolution_type = Resolution::Step::Type::BETTER_MATCH; + } else if (package_is_loader && this_config.compare(*best_config) == 0) { + resolution_type = Resolution::Step::Type::OVERLAID; + } else { if (UNLIKELY(logging_enabled)) { - last_resolution_.steps.push_back(Resolution::Step{resolution_type, - this_config.toString(), - &loaded_package->GetPackageName()}); + resolution_steps.push_back(Resolution::Step{Resolution::Step::Type::SKIPPED, + this_config.toString(), + &loaded_package->GetPackageName(), + cookie}); } + continue; } - } else { - // This is the slower path, which doesn't use the filtered list of configurations. - // Here we must read the ResTable_config from the mmapped APK, convert it to host endianness - // and fill in any new fields that did not exist when the APK was compiled. - // Furthermore when selecting configurations we can't just record the pointer to the - // ResTable_config, we must copy it. - const auto iter_end = type_spec->types + type_spec->type_count; - for (auto iter = type_spec->types; iter != iter_end; ++iter) { - const incfs::verified_map_ptr<ResTable_type>& type = *iter; - - ResTable_config this_config{}; - if (!ignore_configuration) { - this_config.copyFromDtoH(type->config); - if (!this_config.match(desired_config)) { - continue; - } - if (best_config == nullptr) { - resolution_type = Resolution::Step::Type::INITIAL; - } else if (this_config.isBetterThan(*best_config, &desired_config)) { - resolution_type = (package_is_loader) ? Resolution::Step::Type::BETTER_MATCH_LOADER - : Resolution::Step::Type::BETTER_MATCH; - } else if (package_is_loader && this_config.compare(*best_config) == 0) { - resolution_type = Resolution::Step::Type::OVERLAID_LOADER; - } else { - continue; - } - } + // The configuration matches and is better than the previous selection. + // Find the entry value if it exists for this configuration. + const auto& type = type_entry->type; + const auto offset = LoadedPackage::GetEntryOffset(type, entry_idx); + if (UNLIKELY(IsIOError(offset))) { + return base::unexpected(offset.error()); + } - // The configuration matches and is better than the previous selection. - // Find the entry value if it exists for this configuration. - const auto offset = LoadedPackage::GetEntryOffset(type, entry_idx); - if (UNLIKELY(IsIOError(offset))) { - return base::unexpected(offset.error()); - } - if (!offset.has_value()) { - continue; + if (!offset.has_value()) { + if (UNLIKELY(logging_enabled)) { + resolution_steps.push_back(Resolution::Step{Resolution::Step::Type::NO_ENTRY, + this_config.toString(), + &loaded_package->GetPackageName(), + cookie}); } + continue; + } - best_cookie = cookie; - best_package = loaded_package; - best_type = type; - best_config_copy = this_config; - best_config = &best_config_copy; - best_offset = offset.value(); + best_cookie = cookie; + best_package = loaded_package; + best_type = type; + best_config = &this_config; + best_offset = offset.value(); - if (stop_at_first_match) { - // Any configuration will suffice, so break. - break; - } + if (UNLIKELY(logging_enabled)) { + last_resolution_.steps.push_back(Resolution::Step{resolution_type, + this_config.toString(), + &loaded_package->GetPackageName(), + cookie}); + } - if (UNLIKELY(logging_enabled)) { - last_resolution_.steps.push_back(Resolution::Step{resolution_type, - this_config.toString(), - &loaded_package->GetPackageName()}); - } + // Any configuration will suffice, so break. + if (stop_at_first_match) { + break; } } } @@ -851,19 +803,16 @@ std::string AssetManager2::GetLastResourceResolution() const { return {}; } - auto cookie = last_resolution_.cookie; + const ApkAssetsCookie cookie = last_resolution_.cookie; if (cookie == kInvalidCookie) { LOG(ERROR) << "AssetManager hasn't resolved a resource to read resolution path."; return {}; } - uint32_t resid = last_resolution_.resid; - std::vector<Resolution::Step>& steps = last_resolution_.steps; - std::string resource_name_string; - - const LoadedPackage* package = - apk_assets_[cookie]->GetLoadedArsc()->GetPackageById(get_package_id(resid)); + const uint32_t resid = last_resolution_.resid; + const auto package = apk_assets_[cookie]->GetLoadedArsc()->GetPackageById(get_package_id(resid)); + std::string resource_name_string; if (package != nullptr) { auto resource_name = ToResourceName(last_resolution_.type_string_ref, last_resolution_.entry_string_ref, @@ -878,44 +827,24 @@ std::string AssetManager2::GetLastResourceResolution() const { << "\n\tFor config -" << configuration_.toString(); - std::string prefix; - for (Resolution::Step step : steps) { - switch (step.type) { - case Resolution::Step::Type::INITIAL: - prefix = "Found initial"; - break; - case Resolution::Step::Type::BETTER_MATCH: - prefix = "Found better"; - break; - case Resolution::Step::Type::BETTER_MATCH_LOADER: - prefix = "Found better in loader"; - break; - case Resolution::Step::Type::OVERLAID: - prefix = "Overlaid"; - break; - case Resolution::Step::Type::OVERLAID_LOADER: - prefix = "Overlaid by loader"; - break; - case Resolution::Step::Type::SKIPPED: - prefix = "Skipped"; - break; - case Resolution::Step::Type::SKIPPED_LOADER: - prefix = "Skipped loader"; - break; - case Resolution::Step::Type::NO_ENTRY: - prefix = "No entry"; - break; - case Resolution::Step::Type::NO_ENTRY_LOADER: - prefix = "No entry for loader"; - break; - } + for (const Resolution::Step& step : last_resolution_.steps) { + const static std::unordered_map<Resolution::Step::Type, const char*> kStepStrings = { + {Resolution::Step::Type::INITIAL, "Found initial"}, + {Resolution::Step::Type::BETTER_MATCH, "Found better"}, + {Resolution::Step::Type::OVERLAID, "Overlaid"}, + {Resolution::Step::Type::SKIPPED, "Skipped"}, + {Resolution::Step::Type::NO_ENTRY, "No entry"} + }; - if (!prefix.empty()) { - log_stream << "\n\t" << prefix << ": " << *step.package_name; + const auto prefix = kStepStrings.find(step.type); + if (prefix == kStepStrings.end()) { + continue; + } - if (!step.config_name.isEmpty()) { - log_stream << " -" << step.config_name; - } + log_stream << "\n\t" << prefix->second << ": " << *step.package_name << " (" + << apk_assets_[step.cookie]->GetPath() << ")"; + if (!step.config_name.isEmpty()) { + log_stream << " -" << step.config_name; } } @@ -935,6 +864,16 @@ base::expected<AssetManager2::ResourceName, NullOrIOError> AssetManager2::GetRes *result->package_name); } +base::expected<uint32_t, NullOrIOError> AssetManager2::GetResourceTypeSpecFlags( + uint32_t resid) const { + auto result = FindEntry(resid, 0u /* density_override */, false /* stop_at_first_match */, + true /* ignore_configuration */); + if (!result.has_value()) { + return base::unexpected(result.error()); + } + return result->type_flags; +} + base::expected<AssetManager2::SelectedValue, NullOrIOError> AssetManager2::GetResource( uint32_t resid, bool may_be_bag, uint16_t density_override) const { auto result = FindEntry(resid, density_override, false /* stop_at_first_match */, @@ -1333,7 +1272,7 @@ base::expected<uint32_t, NullOrIOError> AssetManager2::GetResourceId( return base::unexpected(std::nullopt); } -void AssetManager2::RebuildFilterList(bool filter_incompatible_configs) { +void AssetManager2::RebuildFilterList() { for (PackageGroup& group : package_groups_) { for (ConfiguredPackage& impl : group.packages_) { // Destroy it. @@ -1343,14 +1282,11 @@ void AssetManager2::RebuildFilterList(bool filter_incompatible_configs) { new (&impl.filtered_configs_) ByteBucketArray<FilteredConfigGroup>(); // Create the filters here. - impl.loaded_package_->ForEachTypeSpec([&](const TypeSpec* spec, uint8_t type_index) { - FilteredConfigGroup& group = impl.filtered_configs_.editItemAt(type_index); - const auto iter_end = spec->types + spec->type_count; - for (auto iter = spec->types; iter != iter_end; ++iter) { - ResTable_config this_config; - this_config.copyFromDtoH((*iter)->config); - if (!filter_incompatible_configs || this_config.match(configuration_)) { - group.type_configs.push_back(TypeConfig{*iter, this_config}); + impl.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) { + FilteredConfigGroup& group = impl.filtered_configs_.editItemAt(type_id - 1); + for (const auto& type_entry : type_spec.type_entries) { + if (type_entry.config.match(configuration_)) { + group.type_entries.push_back(&type_entry); } } }); diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index 2fc3b05011c2..996b42426282 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -33,7 +33,6 @@ #endif #endif -#include "androidfw/ByteBucketArray.h" #include "androidfw/Chunk.h" #include "androidfw/ResourceUtils.h" #include "androidfw/Util.h" @@ -49,36 +48,24 @@ namespace { // Builder that helps accumulate Type structs and then create a single // contiguous block of memory to store both the TypeSpec struct and // the Type structs. -class TypeSpecPtrBuilder { - public: - explicit TypeSpecPtrBuilder(incfs::verified_map_ptr<ResTable_typeSpec> header) - : header_(header) { - } +struct TypeSpecBuilder { + explicit TypeSpecBuilder(incfs::verified_map_ptr<ResTable_typeSpec> header) : header_(header) {} void AddType(incfs::verified_map_ptr<ResTable_type> type) { - types_.push_back(type); + TypeSpec::TypeEntry& entry = type_entries.emplace_back(); + entry.config.copyFromDtoH(type->config); + entry.type = type; } - TypeSpecPtr Build() { - // Check for overflow. - using ElementType = incfs::verified_map_ptr<ResTable_type>; - if ((std::numeric_limits<size_t>::max() - sizeof(TypeSpec)) / sizeof(ElementType) < - types_.size()) { - return {}; - } - TypeSpec* type_spec = - (TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(ElementType))); - type_spec->type_spec = header_; - type_spec->type_count = types_.size(); - memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(ElementType)); - return TypeSpecPtr(type_spec); + TypeSpec Build() { + return {header_, std::move(type_entries)}; } private: - DISALLOW_COPY_AND_ASSIGN(TypeSpecPtrBuilder); + DISALLOW_COPY_AND_ASSIGN(TypeSpecBuilder); incfs::verified_map_ptr<ResTable_typeSpec> header_; - std::vector<incfs::verified_map_ptr<ResTable_type>> types_; + std::vector<TypeSpec::TypeEntry> type_entries; }; } // namespace @@ -322,15 +309,10 @@ base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::Get } base::expected<std::monostate, IOError> LoadedPackage::CollectConfigurations( - bool exclude_mipmap, std::set<ResTable_config>* out_configs) const { - const size_t type_count = type_specs_.size(); - for (size_t i = 0; i < type_count; i++) { - const TypeSpecPtr& type_spec = type_specs_[i]; - if (type_spec == nullptr) { - continue; - } + bool exclude_mipmap, std::set<ResTable_config>* out_configs) const {\ + for (const auto& type_spec : type_specs_) { if (exclude_mipmap) { - const int type_idx = type_spec->type_spec->id - 1; + const int type_idx = type_spec.first - 1; const auto type_name16 = type_string_pool_.stringAt(type_idx); if (UNLIKELY(IsIOError(type_name16))) { return base::unexpected(GetIOError(type_name16.error())); @@ -354,11 +336,8 @@ base::expected<std::monostate, IOError> LoadedPackage::CollectConfigurations( } } - const auto iter_end = type_spec->types + type_spec->type_count; - for (auto iter = type_spec->types; iter != iter_end; ++iter) { - ResTable_config config; - config.copyFromDtoH((*iter)->config); - out_configs->insert(config); + for (const auto& type_entry : type_spec.second.type_entries) { + out_configs->insert(type_entry.config); } } return {}; @@ -366,19 +345,12 @@ base::expected<std::monostate, IOError> LoadedPackage::CollectConfigurations( void LoadedPackage::CollectLocales(bool canonicalize, std::set<std::string>* out_locales) const { char temp_locale[RESTABLE_MAX_LOCALE_LEN]; - const size_t type_count = type_specs_.size(); - for (size_t i = 0; i < type_count; i++) { - const TypeSpecPtr& type_spec = type_specs_[i]; - if (type_spec != nullptr) { - const auto iter_end = type_spec->types + type_spec->type_count; - for (auto iter = type_spec->types; iter != iter_end; ++iter) { - ResTable_config configuration; - configuration.copyFromDtoH((*iter)->config); - if (configuration.locale != 0) { - configuration.getBcp47Locale(temp_locale, canonicalize); - std::string locale(temp_locale); - out_locales->insert(std::move(locale)); - } + for (const auto& type_spec : type_specs_) { + for (const auto& type_entry : type_spec.second.type_entries) { + if (type_entry.config.locale != 0) { + type_entry.config.getBcp47Locale(temp_locale, canonicalize); + std::string locale(temp_locale); + out_locales->insert(std::move(locale)); } } } @@ -398,14 +370,13 @@ base::expected<uint32_t, NullOrIOError> LoadedPackage::FindEntryByName( return base::unexpected(key_idx.error()); } - const TypeSpec* type_spec = type_specs_[*type_idx].get(); + const TypeSpec* type_spec = GetTypeSpecByTypeIndex(*type_idx); if (type_spec == nullptr) { return base::unexpected(std::nullopt); } - const auto iter_end = type_spec->types + type_spec->type_count; - for (auto iter = type_spec->types; iter != iter_end; ++iter) { - const incfs::verified_map_ptr<ResTable_type>& type = *iter; + for (const auto& type_entry : type_spec->type_entries) { + const incfs::verified_map_ptr<ResTable_type>& type = type_entry.type; size_t entry_count = dtohl(type->entryCount); for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) { @@ -492,7 +463,7 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, // A map of TypeSpec builders, each associated with an type index. // We use these to accumulate the set of Types available for a TypeSpec, and later build a single, // contiguous block of memory that holds all the Types together with the TypeSpec. - std::unordered_map<int, std::unique_ptr<TypeSpecPtrBuilder>> type_builder_map; + std::unordered_map<int, std::unique_ptr<TypeSpecBuilder>> type_builder_map; ChunkIterator iter(chunk.data_ptr(), chunk.data_size()); while (iter.HasNext()) { @@ -562,9 +533,9 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, return {}; } - std::unique_ptr<TypeSpecPtrBuilder>& builder_ptr = type_builder_map[type_spec->id - 1]; + std::unique_ptr<TypeSpecBuilder>& builder_ptr = type_builder_map[type_spec->id]; if (builder_ptr == nullptr) { - builder_ptr = util::make_unique<TypeSpecPtrBuilder>(type_spec.verified()); + builder_ptr = util::make_unique<TypeSpecBuilder>(type_spec.verified()); loaded_package->resource_ids_.set(type_spec->id, entry_count); } else { LOG(WARNING) << StringPrintf("RES_TABLE_TYPE_SPEC_TYPE already defined for ID %02x", @@ -584,7 +555,7 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, } // Type chunks must be preceded by their TypeSpec chunks. - std::unique_ptr<TypeSpecPtrBuilder>& builder_ptr = type_builder_map[type->id - 1]; + std::unique_ptr<TypeSpecBuilder>& builder_ptr = type_builder_map[type->id]; if (builder_ptr != nullptr) { builder_ptr->AddType(type.verified()); } else { @@ -722,14 +693,9 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, // Flatten and construct the TypeSpecs. for (auto& entry : type_builder_map) { - uint8_t type_idx = static_cast<uint8_t>(entry.first); - TypeSpecPtr type_spec_ptr = entry.second->Build(); - if (type_spec_ptr == nullptr) { - LOG(ERROR) << "Too many type configurations, overflow detected."; - return {}; - } - - loaded_package->type_specs_.editItemAt(type_idx) = std::move(type_spec_ptr); + TypeSpec type_spec = entry.second->Build(); + uint8_t type_id = static_cast<uint8_t>(entry.first); + loaded_package->type_specs_[type_id] = std::move(type_spec); } return std::move(loaded_package); diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h index a92694c94b9f..6fbd6aa0df7b 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -101,12 +101,7 @@ class AssetManager2 { // Only pass invalidate_caches=false when it is known that the structure // change in ApkAssets is due to a safe addition of resources with completely // new resource IDs. - // - // Only pass in filter_incompatible_configs=false when you want to load all - // configurations (including incompatible ones) such as when constructing an - // idmap. - bool SetApkAssets(const std::vector<const ApkAssets*>& apk_assets, bool invalidate_caches = true, - bool filter_incompatible_configs = true); + bool SetApkAssets(const std::vector<const ApkAssets*>& apk_assets, bool invalidate_caches = true); inline const std::vector<const ApkAssets*> GetApkAssets() const { return apk_assets_; @@ -298,6 +293,12 @@ class AssetManager2 { // data failed. base::expected<const ResolvedBag*, NullOrIOError> ResolveBag(SelectedValue& value) const; + // Returns the android::ResTable_typeSpec flags of the resource ID. + // + // Returns a null error if the resource could not be resolved, or an I/O error if reading + // resource data failed. + base::expected<uint32_t, NullOrIOError> GetResourceTypeSpecFlags(uint32_t resid) const; + const std::vector<uint32_t> GetBagResIdStack(uint32_t resid) const; // Resets the resource resolution structures in preparation for the next resource retrieval. @@ -330,15 +331,10 @@ class AssetManager2 { private: DISALLOW_COPY_AND_ASSIGN(AssetManager2); - struct TypeConfig { - incfs::verified_map_ptr<ResTable_type> type; - ResTable_config config; - }; - // A collection of configurations and their associated ResTable_type that match the current // AssetManager configuration. struct FilteredConfigGroup { - std::vector<TypeConfig> type_configs; + std::vector<const TypeSpec::TypeEntry*> type_entries; }; // Represents an single package. @@ -413,7 +409,7 @@ class AssetManager2 { // Triggers the re-construction of lists of types that match the set configuration. // This should always be called when mutating the AssetManager's configuration or ApkAssets set. - void RebuildFilterList(bool filter_incompatible_configs = true); + void RebuildFilterList(); // Retrieves the APK paths of overlays that overlay non-system packages. std::set<std::string> GetNonSystemOverlayPaths() const; @@ -460,13 +456,9 @@ class AssetManager2 { enum class Type { INITIAL, BETTER_MATCH, - BETTER_MATCH_LOADER, OVERLAID, - OVERLAID_LOADER, SKIPPED, - SKIPPED_LOADER, NO_ENTRY, - NO_ENTRY_LOADER, }; // Marks what kind of override this step was. @@ -477,6 +469,9 @@ class AssetManager2 { // Marks the package name of the better resource found in this step. const std::string* package_name; + + // + ApkAssetsCookie cookie = kInvalidCookie; }; // Last resolved resource ID. diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h index 17d97a2a2e73..891fb90d8eeb 100644 --- a/libs/androidfw/include/androidfw/LoadedArsc.h +++ b/libs/androidfw/include/androidfw/LoadedArsc.h @@ -47,18 +47,19 @@ class DynamicPackageEntry { // TypeSpec is going to be immediately proceeded by // an array of Type structs, all in the same block of memory. struct TypeSpec { - // Pointer to the mmapped data where flags are kept. - // Flags denote whether the resource entry is public - // and under which configurations it varies. - incfs::verified_map_ptr<ResTable_typeSpec> type_spec; + struct TypeEntry { + incfs::verified_map_ptr<ResTable_type> type; + + // Type configurations are accessed frequently when setting up an AssetManager and querying + // resources. Access this cached configuration to minimize page faults. + ResTable_config config; + }; - // The number of types that follow this struct. - // There is a type for each configuration that entries are defined for. - size_t type_count; + // Pointer to the mmapped data where flags are kept. Flags denote whether the resource entry is + // public and under which configurations it varies. + incfs::verified_map_ptr<ResTable_typeSpec> type_spec; - // Trick to easily access a variable number of Type structs - // proceeding this struct, and to ensure their alignment. - incfs::verified_map_ptr<ResTable_type> types[0]; + std::vector<TypeEntry> type_entries; base::expected<uint32_t, NullOrIOError> GetFlagsForEntryIndex(uint16_t entry_index) const { if (entry_index >= dtohl(type_spec->entryCount)) { @@ -92,11 +93,6 @@ enum : package_property_t { PROPERTY_OVERLAY = 1U << 3U, }; -// TypeSpecPtr points to a block of memory that holds a TypeSpec struct, followed by an array of -// ResTable_type pointers. -// TypeSpecPtr is a managed pointer that knows how to delete itself. -using TypeSpecPtr = util::unique_cptr<TypeSpec>; - struct OverlayableInfo { std::string name; std::string actor; @@ -239,17 +235,17 @@ class LoadedPackage { inline const TypeSpec* GetTypeSpecByTypeIndex(uint8_t type_index) const { // If the type IDs are offset in this package, we need to take that into account when searching // for a type. - return type_specs_[type_index - type_id_offset_].get(); + const auto& type_spec = type_specs_.find(type_index + 1 - type_id_offset_); + if (type_spec == type_specs_.end()) { + return nullptr; + } + return &type_spec->second; } template <typename Func> void ForEachTypeSpec(Func f) const { - for (size_t i = 0; i < type_specs_.size(); i++) { - const TypeSpecPtr& ptr = type_specs_[i]; - if (ptr != nullptr) { - uint8_t type_id = ptr->type_spec->id; - f(ptr.get(), type_id - 1); - } + for (const auto& type_spec : type_specs_) { + f(type_spec.second, type_spec.first); } } @@ -289,7 +285,7 @@ class LoadedPackage { int type_id_offset_ = 0; package_property_t property_flags_ = 0U; - ByteBucketArray<TypeSpecPtr> type_specs_; + std::unordered_map<uint8_t, TypeSpec> type_specs_; ByteBucketArray<uint32_t> resource_ids_; std::vector<DynamicPackageEntry> dynamic_package_map_; std::vector<const std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_; diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp index 471b0ee1e7e9..e1c0fab77884 100644 --- a/libs/androidfw/tests/AssetManager2_test.cpp +++ b/libs/androidfw/tests/AssetManager2_test.cpp @@ -55,6 +55,12 @@ class AssetManager2Test : public ::testing::Test { basic_de_fr_assets_ = ApkAssets::Load("basic/basic_de_fr.apk"); ASSERT_NE(nullptr, basic_de_fr_assets_); + basic_xhdpi_assets_ = ApkAssets::Load("basic/basic_xhdpi-v4.apk"); + ASSERT_NE(nullptr, basic_de_fr_assets_); + + basic_xxhdpi_assets_ = ApkAssets::Load("basic/basic_xxhdpi-v4.apk"); + ASSERT_NE(nullptr, basic_de_fr_assets_); + style_assets_ = ApkAssets::Load("styles/styles.apk"); ASSERT_NE(nullptr, style_assets_); @@ -87,6 +93,8 @@ class AssetManager2Test : public ::testing::Test { protected: std::unique_ptr<const ApkAssets> basic_assets_; std::unique_ptr<const ApkAssets> basic_de_fr_assets_; + std::unique_ptr<const ApkAssets> basic_xhdpi_assets_; + std::unique_ptr<const ApkAssets> basic_xxhdpi_assets_; std::unique_ptr<const ApkAssets> style_assets_; std::unique_ptr<const ApkAssets> lib_one_assets_; std::unique_ptr<const ApkAssets> lib_two_assets_; @@ -225,6 +233,24 @@ TEST_F(AssetManager2Test, GetSharedLibraryResourceName) { ASSERT_EQ("com.android.lib_one:string/foo", ToFormattedResourceString(*name)); } +TEST_F(AssetManager2Test, GetResourceNameNonMatchingConfig) { + AssetManager2 assetmanager; + assetmanager.SetApkAssets({basic_de_fr_assets_.get()}); + + auto value = assetmanager.GetResourceName(basic::R::string::test1); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ("com.android.basic:string/test1", ToFormattedResourceString(*value)); +} + +TEST_F(AssetManager2Test, GetResourceTypeSpecFlags) { + AssetManager2 assetmanager; + assetmanager.SetApkAssets({basic_de_fr_assets_.get()}); + + auto value = assetmanager.GetResourceTypeSpecFlags(basic::R::string::test1); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(ResTable_typeSpec::SPEC_PUBLIC | ResTable_config::CONFIG_LOCALE, *value); +} + TEST_F(AssetManager2Test, FindsBagResourceFromSingleApkAssets) { AssetManager2 assetmanager; assetmanager.SetApkAssets({basic_assets_.get()}); @@ -442,6 +468,29 @@ TEST_F(AssetManager2Test, ResolveDeepIdReference) { EXPECT_EQ(*low_ref, value->resid); } +TEST_F(AssetManager2Test, DensityOverride) { + AssetManager2 assetmanager; + assetmanager.SetApkAssets({basic_assets_.get(), basic_xhdpi_assets_.get(), + basic_xxhdpi_assets_.get()}); + assetmanager.SetConfiguration({ + .density = ResTable_config::DENSITY_XHIGH, + .sdkVersion = 21, + }); + + auto value = assetmanager.GetResource(basic::R::string::density, false /*may_be_bag*/); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(Res_value::TYPE_STRING, value->type); + EXPECT_EQ("xhdpi", GetStringFromPool(assetmanager.GetStringPoolForCookie(value->cookie), + value->data)); + + value = assetmanager.GetResource(basic::R::string::density, false /*may_be_bag*/, + ResTable_config::DENSITY_XXHIGH); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(Res_value::TYPE_STRING, value->type); + EXPECT_EQ("xxhdpi", GetStringFromPool(assetmanager.GetStringPoolForCookie(value->cookie), + value->data)); +} + TEST_F(AssetManager2Test, KeepLastReferenceIdUnmodifiedIfNoReferenceIsResolved) { AssetManager2 assetmanager; assetmanager.SetApkAssets({basic_assets_.get()}); @@ -716,7 +765,7 @@ TEST_F(AssetManager2Test, GetLastPathWithSingleApkAssets) { auto result = assetmanager.GetLastResourceResolution(); EXPECT_EQ("Resolution for 0x7f030000 com.android.basic:string/test1\n" - "\tFor config -de\n\tFound initial: com.android.basic", result); + "\tFor config -de\n\tFound initial: com.android.basic (basic/basic.apk)", result); } TEST_F(AssetManager2Test, GetLastPathWithMultipleApkAssets) { @@ -736,8 +785,8 @@ TEST_F(AssetManager2Test, GetLastPathWithMultipleApkAssets) { auto result = assetmanager.GetLastResourceResolution(); EXPECT_EQ("Resolution for 0x7f030000 com.android.basic:string/test1\n" "\tFor config -de\n" - "\tFound initial: com.android.basic\n" - "\tFound better: com.android.basic -de", result); + "\tFound initial: com.android.basic (basic/basic.apk)\n" + "\tFound better: com.android.basic (basic/basic_de_fr.apk) -de", result); } TEST_F(AssetManager2Test, GetLastPathAfterDisablingReturnsEmpty) { diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp index 63574110a817..9aa363495131 100644 --- a/libs/androidfw/tests/LoadedArsc_test.cpp +++ b/libs/androidfw/tests/LoadedArsc_test.cpp @@ -65,10 +65,10 @@ TEST(LoadedArscTest, LoadSinglePackageArsc) { const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index); ASSERT_THAT(type_spec, NotNull()); - ASSERT_THAT(type_spec->type_count, Ge(1u)); + ASSERT_THAT(type_spec->type_entries.size(), Ge(1u)); - auto type = type_spec->types[0]; - ASSERT_TRUE(LoadedPackage::GetEntry(type, entry_index).has_value()); + auto type = type_spec->type_entries[0]; + ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value()); } TEST(LoadedArscTest, LoadSparseEntryApp) { @@ -89,10 +89,10 @@ TEST(LoadedArscTest, LoadSparseEntryApp) { const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index); ASSERT_THAT(type_spec, NotNull()); - ASSERT_THAT(type_spec->type_count, Ge(1u)); + ASSERT_THAT(type_spec->type_entries.size(), Ge(1u)); - auto type = type_spec->types[0]; - ASSERT_TRUE(LoadedPackage::GetEntry(type, entry_index).has_value()); + auto type = type_spec->type_entries[0]; + ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value()); } TEST(LoadedArscTest, LoadSharedLibrary) { @@ -176,13 +176,13 @@ TEST(LoadedArscTest, LoadFeatureSplit) { const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index); ASSERT_THAT(type_spec, NotNull()); - ASSERT_THAT(type_spec->type_count, Ge(1u)); + ASSERT_THAT(type_spec->type_entries.size(), Ge(1u)); auto type_name16 = package->GetTypeStringPool()->stringAt(type_spec->type_spec->id - 1); ASSERT_TRUE(type_name16.has_value()); EXPECT_THAT(util::Utf16ToUtf8(*type_name16), StrEq("string")); - ASSERT_TRUE(LoadedPackage::GetEntry(type_spec->types[0], entry_index).has_value()); + ASSERT_TRUE(LoadedPackage::GetEntry(type_spec->type_entries[0].type, entry_index).has_value()); } // AAPT(2) generates resource tables with chunks in a certain order. The rule is that @@ -217,11 +217,11 @@ TEST(LoadedArscTest, LoadOutOfOrderTypeSpecs) { const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(0); ASSERT_THAT(type_spec, NotNull()); - ASSERT_THAT(type_spec->type_count, Ge(1u)); + ASSERT_THAT(type_spec->type_entries.size(), Ge(1u)); type_spec = package->GetTypeSpecByTypeIndex(1); ASSERT_THAT(type_spec, NotNull()); - ASSERT_THAT(type_spec->type_count, Ge(1u)); + ASSERT_THAT(type_spec->type_entries.size(), Ge(1u)); } TEST(LoadedArscTest, LoadOverlayable) { @@ -363,10 +363,10 @@ TEST(LoadedArscTest, LoadCustomLoader) { const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index); ASSERT_THAT(type_spec, NotNull()); - ASSERT_THAT(type_spec->type_count, Ge(1u)); + ASSERT_THAT(type_spec->type_entries.size(), Ge(1u)); - auto type = type_spec->types[0]; - ASSERT_TRUE(LoadedPackage::GetEntry(type, entry_index).has_value()); + auto type = type_spec->type_entries[0]; + ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value()); } // structs with size fields (like Res_value, ResTable_entry) should be diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp index ad716c78d65d..daedc2a14767 100644 --- a/tools/aapt2/process/SymbolTable.cpp +++ b/tools/aapt2/process/SymbolTable.cpp @@ -227,8 +227,7 @@ bool AssetManagerSymbolSource::AddAssetPath(const StringPiece& path) { apk_assets.push_back(apk_asset.get()); } - asset_manager_.SetApkAssets(apk_assets, true /* invalidate_caches */, - false /* filter_incompatible_configs */); + asset_manager_.SetApkAssets(apk_assets); return true; } return false; @@ -351,9 +350,9 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName( return true; } - auto value = asset_manager_.GetResource(res_id.id, true /* may_be_bag */); - if (value.has_value()) { - type_spec_flags = value->flags; + auto flags = asset_manager_.GetResourceTypeSpecFlags(res_id.id); + if (flags.has_value()) { + type_spec_flags = *flags; found = true; return false; } @@ -406,8 +405,8 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById( return {}; } - auto value = asset_manager_.GetResource(id.id, true /* may_be_bag */); - if (!value.has_value()) { + auto flags = asset_manager_.GetResourceTypeSpecFlags(id.id); + if (!flags.has_value()) { return {}; } @@ -422,7 +421,7 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById( } if (s) { - s->is_public = (value->flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; + s->is_public = (*flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; return s; } return {}; |