Android は、テーマ内の対応する属性の結果、または設定したスタイルの結果を取得します。この記事で GetBag(uint32_t resid, std::vector<uint32_t>& child_resids) と書かれている場合、FindEntry(resid, 0u /*) が渡されます。 density_override / , false / stop_at_first_match /, false / ignore_configuration */) は、ResTable_map_entry 構造体のデータ ポインターを取得します。このポインタは、メモリ内の Bag リソースの場所です。
リソースを検索するとき、32 ビット整数に基づいてクエリが実行されることがわかっています。この 32 ビット整数は 3 つの部分に分割され、最初の 8 ビットはパッケージ ID、システム リソース パッケージ ID は 01、アプリケーション リソース パッケージ ID は 0x7F、次の 8 ビットはタイプ ID、タイプ ID です。は 1 から始まるため、リソースを検索する場合、配列シーケンスとして使用する場合は、最初に 1 ずつデクリメントする必要があります。最後の 16 ビットは、entry_idx と呼ばれる、対応するタイプのリソース収集プロセスでの出現シーケンスです。
リソースを検索するプロセスでは、まずパッケージ ID を通じて対応するパッケージを見つけます。パッケージ ID は 0 から増加しないため、見てわかるように、パッケージ ID と対応する配列シーケンスの間のマッピングを維持する必要があります。次のコードから、型 id を渡し、パッケージ内で対応する型を見つけ、最後に、entry_idx を通じてこの型の対応するリソースを見つけます。
資産管理者2
前回の記事では、AssetManager2 にはアプリケーションのすべてのリソース コンテンツが含まれているとも述べました。このクラスを理解する必要があります
class AssetManager2 {
…………
// The ordered list of ApkAssets to search. These are not owned by the AssetManager, and must
// have a longer lifetime.
std::vector<const ApkAssets*> apk_assets_;
// DynamicRefTables for shared library package resolution.
// These are ordered according to apk_assets_. The mappings may change depending on what is
// in apk_assets_, therefore they must be stored in the AssetManager and not in the
// immutable ApkAssets class.
std::vector<PackageGroup> package_groups_;
// An array mapping package ID to index into package_groups. This keeps the lookup fast
// without taking too much memory.
std::array<uint8_t, std::numeric_limits<uint8_t>::max() + 1> package_ids_;
// The current configuration set for this AssetManager. When this changes, cached resources
// may need to be purged.
ResTable_config configuration_;
// Cached set of bags. These are cached because they can inherit keys from parent bags,
// which involves some calculation.
mutable std::unordered_map<uint32_t, util::unique_cptr<ResolvedBag>> cached_bags_;
// Cached set of bag resid stacks for each bag. These are cached because they might be requested
// a number of times for each view during View inspection.
mutable std::unordered_map<uint32_t, std::vector<uint32_t>> cached_bag_resid_stacks_;
// Cached set of resolved resource values.
mutable std::unordered_map<uint32_t, SelectedValue> cached_resolved_values_;
…………
}
メンバー apk_assets_ は std::vector<const ApkAssets*> であり、ApkAssets が Apk ファイルに対応します。APK ファイルのリソースを解析するとき、APK ファイルは ApkAssets オブジェクトに解析されます。
package_groups_ は std::vector であり、apk_assets_ によって再処理されます。
package_ids_ には、パッケージ ID と上記の package_groups_ 内の順序との間のマッピングが格納されます。
configuration_ は、ResTable_config で記述される現在の AssetManager の構成情報です。変更されると、キャッシュされたリソースを破棄する必要がある場合があります。
キャッシュに使用されるメンバー名から次の3つのメンバーが分かります。
パッケージグループ
PackageGroup については、まだ話し合う必要があります。その構造を見てください
// A collection of configurations and their associated ResTable_type that match the current
// AssetManager configuration.
struct FilteredConfigGroup {
std::vector<const TypeSpec::TypeEntry*> type_entries;
};
// Represents an single package.
struct ConfiguredPackage {
// A pointer to the immutable, loaded package info.
const LoadedPackage* loaded_package_;
// A mutable AssetManager-specific list of configurations that match the AssetManager's
// current configuration. This is used as an optimization to avoid checking every single
// candidate configuration when looking up resources.
ByteBucketArray<FilteredConfigGroup> filtered_configs_;
};
// Represents a Runtime Resource Overlay that overlays resources in the logical package.
struct ConfiguredOverlay {
// The set of package groups that overlay this package group.
IdmapResMap overlay_res_maps_;
// The cookie of the overlay assets.
ApkAssetsCookie cookie;
};
// Represents a logical package, which can be made up of many individual packages. Each package
// in a PackageGroup shares the same package name and package ID.
struct PackageGroup {
// The set of packages that make-up this group.
std::vector<ConfiguredPackage> packages_;
// The cookies associated with each package in the group. They share the same order as
// packages_.
std::vector<ApkAssetsCookie> cookies_;
// Runtime Resource Overlays that overlay resources in this package group.
std::vector<ConfiguredOverlay> overlays_;
// A library reference table that contains build-package ID to runtime-package ID mappings.
std::shared_ptr<DynamicRefTable> dynamic_ref_table = std::make_shared<DynamicRefTable>();
};
各 PackageGroup はパッケージ ID に対応します。複数のパッケージのリソースが含まれる場合があり、packages_ のタイプは std::vector で、ConfiguredPackage の各インスタンスはパッケージのリソースに対応します。これらのパッケージの id 値は同じです。
cookies_ は、対応する ConfiguredPackage がどの APK からのものかを記述するために使用されます。その順序は、packages_ の順序に対応します。
overlays_ は、実行中のリソース マスクである RRO に関連しています。
Dynamic_ref_table これは、構築パッケージ ID から実行パッケージ ID へのマッピングを含むライブラリ参照テーブルです。
ConfiguredPackage 構造体をもう一度見ると、loaded_package_ と filtered_configs_ という 2 つのメンバーがあります。
loaded_package_ は LoadedPackage* で、パッケージ内のリソース情報を指します。
そして、filtered_configs_ は最適化のためのもので、現在の構成情報に従って、一致するすべてのリソースがそれに入れられます。このようにして、一致しないリソース情報を照会する必要はありません。filtered_configs_ は ByteBucketArray タイプであり、内部の順序は (typeid -1) であり、リソースの各タイプに対応します。後で呼び出すときの便宜上、当面はリソースの最適化割り当てと呼ぶことにします。
FindEntry()
FindEntry() について説明します。以下のセクションを読んでください。
base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry(
uint32_t resid, uint16_t density_override, bool stop_at_first_match,
bool ignore_configuration) const {
…………
// Might use this if density_override != 0.
ResTable_config density_override_config;
// Select our configuration or generate a density override configuration.
const ResTable_config* desired_config = &configuration_;
if (density_override != 0 && density_override != configuration_.density) {
density_override_config = configuration_;
density_override_config.density = density_override;
desired_config = &density_override_config;
}
パラメータ resid は照会するリソース ID、density_override は満たすべき画面密度、0 の場合は要件なし、stop_at_first_match は最初の一致が見つかった場合に停止するかどうか、ignore_configuration は設定を無視します。
最初のコードは、パラメータdensity_overrideを処理するものです。density_overrideが0ではなく、現在のAssetManager2構成configuration_の画面密度と一致しない場合は、desired_configの画面密度をdensity_overrideに設定します。他の構成にはconfiguration_がかかります。
次のコードを見てください。
…………
const uint32_t package_id = get_package_id(resid);
const uint8_t type_idx = get_type_id(resid) - 1;
const uint16_t entry_idx = get_entry_id(resid);
uint8_t package_idx = package_ids_[package_id];
if (UNLIKELY(package_idx == 0xff)) {
ANDROID_LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.",
package_id, resid);
return base::unexpected(std::nullopt);
}
const PackageGroup& package_group = package_groups_[package_idx];
auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config,
stop_at_first_match, ignore_configuration);
このブロックは、package_id、type_idx、entry_idx を取得するために resid の処理を開始します。これについては以前に説明しました。対応する値はそれぞれシフト演算を通じて取得されます。type_idx はシフト演算後のマイナス 1 演算です。typeid は 1 から始まるので。後で配列のシーケンスを検索するため、1 を減算する必要があります。
次に、対応する PackageGroup を見つける方法は、対応するパッケージ ID の順序が package_ids_ に格納され、対応する package_group が package_groups_[package_idx] を通じて取得されます。
次に、FindEntryInternal()を呼び出して、FindEntryResult オブジェクトの結果を取得します。以下でこれについて説明してから、コードを見てみましょう。
…………
if (!stop_at_first_match && !ignore_configuration && !apk_assets_[result->cookie]->IsLoader()) {
for (const auto& id_map : package_group.overlays_) {
auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid);
if (!overlay_entry) {
// No id map entry exists for this target resource.
continue;
}
if (overlay_entry.IsInlineValue()) {
// The target resource is overlaid by an inline value not represented by a resource.
result->entry = overlay_entry.GetInlineValue();
result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
result->cookie = id_map.cookie;
…………
continue;
}
auto overlay_result = FindEntry(overlay_entry.GetResourceId(), density_override,
false /* stop_at_first_match */,
false /* ignore_configuration */);
if (UNLIKELY(IsIOError(overlay_result))) {
return base::unexpected(overlay_result.error());
}
if (!overlay_result.has_value()) {
continue;
}
if (!overlay_result->config.isBetterThan(result->config, desired_config)
&& overlay_result->config.compare(result->config) != 0) {
// The configuration of the entry for the overlay must be equal to or better than the target
// configuration to be chosen as the better value.
continue;
}
result->cookie = overlay_result->cookie;
result->entry = overlay_result->entry;
result->config = overlay_result->config;
result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
…………
}
}
…………
return result;
}
このコードは主に RRO の存在について扱っているため、詳細は説明しませんが、FindEntry() を呼び出して結果を返していることがわかります。
FindEntry()が完了しました。
ResTable_type
FindEntryInternal() について話す前に、検索時に密接に関連する ResTable_type について話す必要があります。
/**
* Header that appears at the front of every data chunk in a resource.
*/
struct ResChunk_header
{
// Type identifier for this chunk. The meaning of this value depends
// on the containing chunk.
uint16_t type;
// Size of the chunk header (in bytes). Adding this value to
// the address of the chunk allows you to find its associated data
// (if any).
uint16_t headerSize;
// Total size of this chunk (in bytes). This is the chunkSize plus
// the size of any data associated with the chunk. Adding this value
// to the chunk allows you to completely skip its contents (including
// any child chunks). If this value is the same as chunkSize, there is
// no data associated with the chunk.
uint32_t size;
};
…………
struct ResTable_type
{
struct ResChunk_header header;
enum {
NO_ENTRY = 0xFFFFFFFF
};
// The type identifier this chunk is holding. Type IDs start
// at 1 (corresponding to the value of the type bits in a
// resource identifier). 0 is invalid.
uint8_t id;
enum {
// If set, the entry is sparse, and encodes both the entry ID and offset into each entry,
// and a binary search is used to find the key. Only available on platforms >= O.
// Mark any types that use this with a v26 qualifier to prevent runtime issues on older
// platforms.
FLAG_SPARSE = 0x01,
};
uint8_t flags;
// Must be 0.
uint16_t reserved;
// Number of uint32_t entry indices that follow.
uint32_t entryCount;
// Offset from header where ResTable_entry data starts.
uint32_t entriesStart;
// Configuration this collection of entries is designed for. This must always be last.
ResTable_config config;
};
ResTable_type データ構造は、メモリ内のデータ内容に対応します。
ヘッダーは ResChunk_header 構造体です。ResChunk_header は、各データ テーブルの先頭のコンテンツです。ResChunk_header の type はテーブル タイプを表し、headerSize は ResTable_type データ構造のメモリ サイズ、size は ResTable_type データ構造 + 関連データ コンテンツのサイズです。
ResTable_type の ID はタイプ ID です。カウントは1から始まります。
ResTable_typeのentryCountは、このタイプのリソースのエントリ数を表し、
ResTable_typeのentryStartは、データの先頭から構造体の先頭までのオフセットです。
ResTable_type の config は、このタイプの関連構成情報です。
以下の図を見てください。これは、メモリ内の構造に関連するデータのレイアウトです。
図からも通常リソースとバッグリソースに分かれていることがわかります。通常のリソースは、文字列型などの比較的単純なリソースです。バッグ リソースは、スタイル タイプ リソースなどのより複雑なリソースであり、xml ファイルに項目属性コンテンツの多くの項目が含まれます。
ResTable_map_entry および ResTable_map データ構造は、テーマ内の対応する属性の結果、または Android で設定したスタイルの結果を取得します。
ResTable_type は、リソースの特定の構成に関連しています。ドローアブル タイプと同様に、mdpi と hdpi は 2 つの ResTable_type で表されます。
FindEntryInternal() と言って開始します
FindEntryInternal()
このメソッドは、リソースのコアを見つけるためのメソッドであり、リソース検索を処理するときに構成を比較する方法、最適化された filtered_configs_ をいつ使用するか、メモリ内の対応するリソースを見つける方法について説明します。
次に、コードを確認するか、セクションごとに確認します。
base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
const PackageGroup& package_group, uint8_t type_idx, uint16_t entry_idx,
const ResTable_config& desired_config, bool stop_at_first_match,
bool ignore_configuration) const {
const bool logging_enabled = resource_resolution_logging_enabled_;
ApkAssetsCookie best_cookie = kInvalidCookie;
const LoadedPackage* best_package = nullptr;
incfs::verified_map_ptr<ResTable_type> best_type;
const ResTable_config* best_config = nullptr;
uint32_t best_offset = 0U;
uint32_t type_flags = 0U;
std::vector<Resolution::Step> resolution_steps;
// 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_;
パラメータはすべて FindEntry() から渡されるため、これについては説明しません。
いくつかの変数が定義されており、best_cookie はリソースがどの ApkAssets に由来するかを表し、best_package はパッケージに対応する LoadedPackage のリソースを表し、best_type はどの ResTable_type に由来するかを表し、best_config はどの構成に由来するかを表し、best_offset はresource は、前の ResTable_type 構造に関連しており、type_flags は、リソースによって影響を受ける構成要素を表します。
また、最適化された構成リソースを使用する変数 use_filtered も定義します (前述したように、ConfiguredPackage の filtered_configs_ を使用して検索します)。この変数が設定を無視せず、現在の AssetManager2 設定情報とまったく同じ設定が見つかると期待される場合、最適化された設定リソース情報を使用して、対応するリソースを検索します。
次の段落
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_;
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.
const TypeSpec* type_spec = loaded_package->GetTypeSpecByTypeIndex(type_idx);
if (UNLIKELY(type_spec == nullptr)) {
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();
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();
サイクルに入り始めます。これは、同じパッケージ ID を持つすべてのパッケージです。
まず、LoadedPackage型のloaded_package を取得し、次にその GetTypeSpecByTypeIndex(type_idx) メソッドを呼び出して、TypeSpec型 type_spec を取得します。loaded_package には、解析された APK ファイルのリソース テーブル (resources.arsc) 内のすべてのリソース情報が含まれています。取得されたtype_specは、typeidxに対応するタイプを含む構成情報およびリソースデータ情報を指す。その構造を見てください
// TypeSpec is going to be immediately proceeded by
// an array of Type structs, all in the same block of memory.
struct TypeSpec {
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;
};
// 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;
std::vector<TypeEntry> type_entries;
…………
};
その type_spec は incfs::verified_map_ptr<ResTable_typeSpec> タイプで、エントリが影響を受ける構成が含まれます。entryidx を通じて、対応するリソースが影響を受けることを示すフラグを取得できます。type_entries は TypeEntry のコレクションであり、各 TypeEntry オブジェクトには incfs::verified_map_ptr<ResTable_type> タイプが含まれます。ResTable_type については前に説明しました。
次にコードを確認し、type_spec->GetFlagsForEntryIndex(entry_idx) を呼び出して、リソース構成に影響するフラグを取得します。複数のパッケージがある場合は、影響を受けるすべてのフラグが収集されます。
次に、前の変数 use_filtered に従って、最適化された構成によって収集されたリソースを使用してリソースを見つけることができるかどうかが判断されます。
最適な構成リソース コレクションを使用する場合は、filtered_group.type_entries を取得します。それ以外の場合は、type_spec->type_entries を取得して、対応するタイプのリソースの数である type_entry_count の数値を取得します。filtered_group.type_entries には現在の構成を満たすすべてのタイプ リソースが含まれ、type_spec->type_entries にはすべてのタイプ リソースが含まれます。したがって、filtered_group.type_entries を使用すると、不要な比較オーバーヘッドを大幅に節約できますが、これはパラメータに応じて判断する必要があります。
次に、コードを見てみましょう。
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;
}
…………
// 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);
…………
best_cookie = cookie;
best_package = loaded_package;
best_type = type;
best_config = &this_config;
best_offset = offset.value();
…………
// Any configuration will suffice, so break.
if (stop_at_first_match) {
break;
}
}
}
タイプリソースの数が取得されると、ループが開始されます。まず、前述したように、type_entry (TypeSpec::TypeEntry*) を取得します。次に、その構成 this_config を取得します。
次に判定があり、3 つの条件がすべて false であれば、そのまま次のサイクル比較に入ります。use_filtered は最適化された構成リソースを使用すること、ignore_configuration は構成を無視すること、3 番目は this_config.match(desired_config) です。これは、このリソースの現在の構成が予想される構成と一致しています。これら 3 つの条件のいずれも満たされない場合、現在のタイプのリソースは破棄され、次のタイプのリソースが直接比較されます。どちらかが満たされた場合は、下方向に実行します。
次に、前述の ResTable_type ポインターであるタイプを取得し、LoadedPackage::GetEntryOffset(type,entry_idx) を通じてオフセットを取得します。これは、ResTable_type について説明したときに前述したエントリのオフセットです。
下に進むと、適切な結果が見つかったことを意味し、best_cookie、best_package、best_type、best_config、および best_offset を割り当てます。
stop_at_first_match パラメータが true に設定されているかどうかを確認します。設定されている場合は、ループの最初の層から抜け出します。設定されていない場合は、2 層サイクルが終了するまでサイクルを続けます。
2 層ループの内容が完了しました。引き続きコードを見てください。
…………
auto best_entry_result = LoadedPackage::GetEntryFromOffset(best_type, best_offset);
…………
const incfs::map_ptr<ResTable_entry> best_entry = *best_entry_result;
…………
const auto entry = GetEntryValue(best_entry.verified());
…………
return FindEntryResult{
.cookie = best_cookie,
.entry = *entry,
.config = *best_config,
.type_flags = type_flags,
.package_name = &best_package->GetPackageName(),
.type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1),
.entry_string_ref = StringPoolRef(best_package->GetKeyStringPool(),
best_entry->key.index),
.dynamic_ref_table = package_group.dynamic_ref_table.get(),
};
}
この最後のコード部分が処理結果です。
LoadedPackage::GetEntryFromOffset(best_type, best_offset) は、ResTable_entry ポインター best_entry_result を取得します。これは、上で説明した ResTable_type データ構造です。entriesStart に対する相対的なオフセットを通じて、ResTable_entry の位置が見つかります。
次に、GetEntryValue(best_entry.verified()) を通じて EntryValue タイプのエントリを取得します。関連するコードを見てください。
using EntryValue = std::variant<Res_value, incfs::verified_map_ptr<ResTable_map_entry>>;
base::expected<EntryValue, IOError> GetEntryValue(
incfs::verified_map_ptr<ResTable_entry> table_entry) {
const uint16_t entry_size = dtohs(table_entry->size);
// Check if the entry represents a bag value.
if (entry_size >= sizeof(ResTable_map_entry) &&
(dtohs(table_entry->flags) & ResTable_entry::FLAG_COMPLEX)) {
const auto map_entry = table_entry.convert<ResTable_map_entry>();
if (!map_entry) {
return base::unexpected(IOError::PAGES_MISSING);
}
return map_entry.verified();
}
// The entry represents a non-bag value.
const auto entry_value = table_entry.offset(entry_size).convert<Res_value>();
if (!entry_value) {
return base::unexpected(IOError::PAGES_MISSING);
}
Res_value value;
value.copyFrom_dtoh(entry_value.value());
return value;
}
EntryValue の可能な値のタイプは Res_value または incfs::verified_map_ptr<ResTable_map_entry> であることがわかります。リソースが Bag リソースの場合は incfs::verified_map_ptr<ResTable_map_entry> になり、通常のリソースの場合は incfs::verified_map_ptr<ResTable_map_entry> になります。 Res_value になります。また、ResTable_entryクラスのメンバ変数flagにResTable_entry::FLAG_COMPLEXがある場合、Bagリソースとなることがわかります。
上記の最後のコードをもう一度見て、最後に結果として FindEntryResult 構造体をアセンブルして返します。そのメンバー type_string_ref とentry_string_ref を見てください。type_string_ref は、LoadedPackage の Type Type に対応する ResStringPool であり、entry_string_ref は、LoadedPackage の Key に対応する ResStringPool です。そして、メンバーのdynamic_ref_tableは、対応するpackage_group.dynamic_ref_tableを受け取ります。
以上でFindEntry()関数は終了です。