Android finds the memory data location of the resource

  Android gets the result of the corresponding attribute in the theme or the result of the style you set. When this article says GetBag(uint32_t resid, std::vector<uint32_t>& child_resids), it will pass FindEntry(resid, 0u /* density_override / , false / stop_at_first_match /, false / ignore_configuration */) will get a data pointer of the ResTable_map_entry structure. This pointer is the location of the Bag resource in memory.
  We know that when searching for a resource, it is queried based on a 32-bit integer. This 32-bit integer is divided into 3 parts, the first 8 bits are the package id, the system resource package id is 01, and the application resource package id is 0x7F; the next 8 bits are the type id, and the type id starts from 1, so when searching for resources, in When used as an array sequence, it needs to be decremented by 1 first; the last 16 bits are the sequence of occurrences in the resource collection process of the corresponding type, called entry_idx.
  In the process of searching for resources, first find the corresponding package through the package id, because the package id does not increase from 0, so it is necessary to maintain the mapping between the package id and the corresponding array sequence, as can be seen from the following code, and then pass the type id , find the corresponding type in the package, and finally find the corresponding resource in this type through entry_idx.

asset manager2

  The previous article also said that AssetManager2 contains all the resource content of the application. It is necessary to understand this class

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_;
  …………
}  	

  The member apk_assets_ is std::vector<const ApkAssets*>, and ApkAssets corresponds to the Apk file. When parsing the resources of the APK file, the APK file is parsed into an ApkAssets object.
  package_groups_ is std::vector, which is reprocessed by apk_assets_.
  The package_ids_ stores the mapping between the package id and the order in the above package_groups_.
  configuration_ is the configuration information of the current AssetManager, which is described by ResTable_config. When it changes, cached resources may need to be discarded.
  The following three members can be seen from the name to be used for caching.

PackageGroup

  For PackageGroup, we still need to talk about it. look at its structure

  // 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>();
  };

  Each PackageGroup corresponds to a package id. It may contain resources of multiple packages, the type of packages_ is std::vector, and each instance of ConfiguredPackage corresponds to a resource of a package. The id values ​​of these packages are the same.
  cookies_ is used to describe which APK the corresponding ConfiguredPackage comes from. Its order corresponds to the order of packages_.
  overlays_ is related to RRO, which is the running resource mask.
  dynamic_ref_table It is the library reference table, which contains the mapping from the construction package ID to the running package ID.
  Looking at the ConfiguredPackage structure again, it has two members: loaded_package_ and filtered_configs_.
  loaded_package_ is LoadedPackage*, which points to the resource information in the package.
  And filtered_configs_ is for optimization, according to the current configuration information, all matching resources are put into it. In this way, there is no need to query resource information that does not match. filtered_configs_ is of ByteBucketArray type, and the order inside is (typeid -1), corresponding to each type of resource. For the convenience of calling it later, let’s call it optimized allocation of resources for the time being.

FindEntry()

  Let's talk about FindEntry(), read in sections:

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;
  }

   The parameter resid is the resource id to be queried; density_override is the screen density to be satisfied, if it is 0, there is no requirement; stop_at_first_match is whether to stop if the first match is found; ignore_configuration is to ignore the configuration.
   The first piece of code is to process the parameter density_override. If density_override is not 0 and is inconsistent with the screen density of the current AssetManager2 configuration configuration_, set the screen density of desired_config to density_override. Other configurations take configuration_.
  Look at the next piece of code:

  …………
  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);  

  This block starts to process resid to get package_id, type_idx, entry_idx. This was explained earlier. The corresponding values ​​are obtained through the shift operation respectively. type_idx is a minus 1 operation after the shift operation. Because typeid starts from 1. Later, we will search through the sequence of the array, so we need to subtract 1.
  Then how to find the corresponding PackageGroup, the order of the corresponding package id is stored in package_ids_, and then the corresponding package_group is obtained through package_groups_[package_idx].
  Then call FindEntryInternal() to get the FindEntryResult object result. Let’s talk about this below, and then look down at the code:

	…………
  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;
}	

  This piece of code mainly deals with the existence of RRO, and I am not going to go into details. You can see that it also calls FindEntry() to return the result.
  FindEntry() is done.

ResTable_type

  Before talking about FindEntryInternal(), we need to talk about ResTable_type, which is closely related to it when searching

/**
 * 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;
};

  The ResTable_type data structure corresponds to the data content in memory.
  The header is a ResChunk_header structure. ResChunk_header is the content at the beginning of each data table. The type of ResChunk_header represents the table type, headerSize is the memory size of the ResTable_type data structure, and size is the size of the ResTable_type data structure + related data content.
  The id of ResTable_type is type id. Counting starts from 1.
  The entryCount of ResTable_type represents the number of entries of this type of resource.
  The entriesStart of ResTable_type is the offset from the beginning of the data to the beginning of the structure.
  The config of ResTable_type is related configuration information of this type.
  Look at the figure below, which is the layout of the data related to the structure in memory.
ResTable_type memory description

The layout of ResTable_type related data in memory
  ResTable_type appears at the beginning. It is followed by entryCount integers, which are the offset values ​​of related entries, and these offset values ​​are relative to entriesStart. When searching, first get the corresponding ResTable_type through typeidx, then get the offset value through entryidx, and then get the specific data content through the offset value.

  It can also be seen in the figure that it is divided into ordinary resources and Bag resources. Ordinary resources are relatively simple resources, such as string types. Bag resources are more complex resources, such as style type resources, which include many items of item attribute content in the xml file.
  The ResTable_map_entry and ResTable_map data structures get the results of the corresponding attributes in the theme or the results in the style you set in Android .
  ResTable_type is related to the specific configuration of resources. Like the drawable type, mdpi and hdpi are represented by two ResTable_types.

   Start say FindEntryInternal()

FindEntryInternal()

   This method is a method to find the core of resources. It describes how to compare configurations when processing resource searches, when to use optimized filtered_configs_, and how to find corresponding resources in memory.
   Then look at the code, or look at it section by section:

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_;    

   The parameters are all passed from FindEntry(), so I won't talk about it.
  Several variables are defined, best_cookie represents which ApkAssets the resource comes from; best_package is the resource from LoadedPackage, which corresponds to a package; best_type represents which ResTable_type it comes from; best_config represents which configuration it comes from; best_offset is the offset value of the resource, which is related to The previous ResTable_type structure is related; type_flags represents the configuration factors affected by resources.
  It also defines a variable use_filtered that uses optimized configuration resources (as mentioned above, it uses filtered_configs_ of ConfiguredPackage to search). If this variable does not ignore the configuration and expects the configuration to be searched to be exactly the same as the current AssetManager2 configuration information, it will use the optimized configuration resource information to search for the corresponding resource.
  next paragraph

  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();

  Start to enter a cycle, this is all the packages with the same package id.
  First get the LoadedPackage type loaded_package, and then call its GetTypeSpecByTypeIndex(type_idx) method to get a TypeSpec type type_spec. The loaded_package contains all resource information in the resource table (resources.arsc) of the parsed APK file. The obtained type_spec points to the configuration information and resource data information containing the type corresponding to the typeidx. look at its structure

// 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;
	…………
};  

  Its type_spec is incfs::verified_map_ptr<ResTable_typeSpec> type, which contains which configurations the entry is affected by. Through entryidx, we can get the flags that the corresponding resources are affected. type_entries is a collection of TypeEntry, each TypeEntry object contains incfs::verified_map_ptr<ResTable_type> type, ResTable_type has been explained earlier.
  Then look at the code, call type_spec->GetFlagsForEntryIndex(entry_idx) to get the flags that affect the resource configuration. And if there are multiple packages, all the affected flags will be collected.
  Then, according to the previous variable use_filtered, it is judged whether the resource collected by the optimized configuration can be used to find the resource.
  If the resource set is optimally configured, take filtered_group.type_entries, otherwise take type_spec->type_entries to get their number type_entry_count, which is the number of resources of the corresponding type. filtered_group.type_entries contains all type resources that meet the current configuration, and type_spec->type_entries contains all type resources. Therefore, using filtered_group.type_entries can save a lot of unnecessary comparison overhead. Of course, this must be judged according to the parameters.
  Then look down at the code:

    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;
      }
    }
  }            

  When the number of type resources is obtained, the loop starts. First get type_entry, which is TypeSpec::TypeEntry*, as mentioned above. Then get its configuration this_config.
  Then there is a judgment. If all three conditions are false, it will directly enter the next cycle comparison. use_filtered is to use optimized configuration resources, ignore_configuration is to ignore configuration, the third is this_config.match(desired_config), this is the current configuration of this resource meets the expected configuration. If none of these three conditions are met, the current type of resource is discarded, and the next type of resource will be compared directly. If one is satisfied, execute downward.
  Then get the type, which is the ResTable_type pointer mentioned above, and then get the offset through LoadedPackage::GetEntryOffset(type, entry_idx), which is the offset of the entry mentioned above when talking about ResTable_type.
  Going down, it means that you have found a suitable result, and assign best_cookie, best_package, best_type, best_config, and best_offset.
  Determine whether the parameter stop_at_first_match is set to true. If it is set, it will jump out of the first layer of loop. If not set, continue to cycle until the end of the two-layer cycle.
  The content of the two-layer loop is finished, continue to look down at the code:

	…………
  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(),
  };
}

  This last piece of code is the processing result.
  LoadedPackage::GetEntryFromOffset(best_type, best_offset) gets the ResTable_entry pointer best_entry_result. This is the ResTable_type data structure we mentioned above. Through the offset relative to entriesStart, the position of ResTable_entry is found.
  Then get the EntryValue type entry through GetEntryValue(best_entry.verified()). Look at the relevant code:

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;
}

  It can be seen that the possible value type of EntryValue is Res_value or incfs::verified_map_ptr<ResTable_map_entry>. When the resource is a Bag resource, it will be incfs::verified_map_ptr<ResTable_map_entry>; if it is a normal resource, it will be Res_value. It can also be seen that when the member variable flags of the ResTable_entry class has ResTable_entry::FLAG_COMPLEX, it will be a Bag resource.
  Look at the last piece of code above again, and finally assemble a FindEntryResult structure as the result and return it. Look at its members type_string_ref and entry_string_ref. type_string_ref is the ResStringPool corresponding to the Type Type of the LoadedPackage, and entry_string_ref is the ResStringPool corresponding to the Key of the LoadedPackage. And the member dynamic_ref_table takes the corresponding package_group.dynamic_ref_table.
  In this way, the FindEntry() function is finished.

Guess you like

Origin blog.csdn.net/q1165328963/article/details/124838192