安卓AssetManager【一】- 资源的查找过程

本文基于安卓11。

安卓应用的资源文件都在编译时通过aapt(frameworks/base/tools/aapt2)工具打包在APK中,安装后保存在userdata分区,当应用需要使用某个资源文件时,通常使用getResources().getString(R.string.name);等方式,R.string.name是一个32位的int类型,由aapt工具生成,保存的是每个资源文件对应的ID,下面就看看安卓是如何通过这个ID获取到对应的数据的。

应用从Java层的AssetManager通过JNI调用到native模块的AssetManager,这里从androidfw模块的AssetManager2开始。

AssetManager2::FindEntry

cpp 复制代码
//AssetManager2.cpp
ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override,
                                         bool /*stop_at_first_match*/,
                                         bool ignore_configuration,
                                         FindEntryResult* out_entry) const {
  if (resource_resolution_logging_enabled_) {
    // Clear the last logged resource resolution.
    ResetResourceResolution();
    last_resolution_.resid = resid;
  }

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

  // Retrieve the package group from the package id of the resource id.
  if (!is_valid_resid(resid)) {
    LOG(ERROR) << base::StringPrintf("Invalid ID 0x%08x.", resid);
    return kInvalidCookie;
  }

  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 (package_idx == 0xff) {
    ANDROID_LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.",
                                             package_id, resid);
    return kInvalidCookie;
  }

  const PackageGroup& package_group = package_groups_[package_idx];
  ApkAssetsCookie cookie = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config,
                                             false /* stop_at_first_match */,
                                             ignore_configuration, out_entry);
  if (UNLIKELY(cookie == kInvalidCookie)) {
    return kInvalidCookie;
  }

  if (!apk_assets_[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.IsTableEntry()) {
        // The target resource is overlaid by an inline value not represented by a resource.
        out_entry->entry = overlay_entry.GetTableEntry();
        out_entry->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
        cookie = id_map.cookie;
        continue;
      }

      FindEntryResult overlay_result;
      ApkAssetsCookie overlay_cookie = FindEntry(overlay_entry.GetResourceId(), density_override,
                                                 false /* stop_at_first_match */,
                                                 ignore_configuration, &overlay_result);
      if (UNLIKELY(overlay_cookie == kInvalidCookie)) {
        continue;
      }

      if (!overlay_result.config.isBetterThan(out_entry->config, desired_config)
          && overlay_result.config.compare(out_entry->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;
      }

      cookie = overlay_cookie;
      out_entry->entry = std::move(overlay_result.entry);
      out_entry->config = overlay_result.config;
      out_entry->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
      if (resource_resolution_logging_enabled_) {
        last_resolution_.steps.push_back(
            Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result.config.toString(),
                             overlay_result.package_name});
      }
    }
  }

AssetManager2::FindEntry方法接收5个参数,在资源文件中通过resid查找到对应的资源并返回:

  • uint32_t resid:R.java中定义的resid,32位无符号整型,前2位标识package,后2位标识type,最后2位标识entry,例如:0x01040000,0x01表示package,0x04表示type,0x0000标识entry。
  • uint16_t density_override:覆盖屏幕像素密度参数,通常为0u,也就是不覆盖。
  • bool stop_at_first_match:通常为false。
  • bool ignore_configuration:通常为false。
  • FindEntryResult* out_entry:查找的结果。

AssetManager2::FindEntry方法主要执行了3个步骤:

  1. 初始化desired_config变量,desired_configResTable_config类型,ResTable_config从十几个维度定义了一个特定的资源配置,比如:MCC 和 MNC、语言、布局方向、最小宽度、可用宽度和高度、屏幕尺寸、屏幕宽高比等参数,具体可参考About app Resources,还定义了match()isBetterThan()compare()等方法判断资源和配置是否匹配,desired_config一开始被赋值为configuration_,也就是当前AssetManager的资源配置值,如果需要覆盖屏幕像素密度参数,desired_configdensity参数被重新赋值;拆解resid,分别得到package_idtype_idxentry_idx,一个应用可以获取多个package的资源,比如自己APK中的资源,还有系统的公共资源,其保存在framework-res.apk中,对于resid: 0x01040000,其中package_id: 0x1, type_idx: 0x3, entry_idx: 0,通过package_id找到PackageGroup对象,继续在package范围内查找对应的资源。

  2. 调用AssetManager2::FindEntryInternal,在package内部开始查找。

  3. 如果这个Asset不是被动态加载的,继续查找overlay资源。

AssetManager2::FindEntryInternal

cpp 复制代码
//AssetManager2.cpp
ApkAssetsCookie 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,
                                                 FindEntryResult* out_entry) const {
  ApkAssetsCookie best_cookie = kInvalidCookie;
  const LoadedPackage* best_package = nullptr;
  const ResTable_type* best_type = nullptr;
  const ResTable_config* best_config = nullptr;
  ResTable_config best_config_copy;
  uint32_t best_offset = 0u;
  uint32_t type_flags = 0u;

  Resolution::Step::Type 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_;

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

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

    // 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();
    type_flags |= type_spec->GetFlagsForEntryIndex(entry_idx);
    
    if (use_fast_path) {
      const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx];
      const std::vector<ResTable_config>& candidate_configs = filtered_group.configurations;
      const size_t type_count = candidate_configs.size();
      for (uint32_t i = 0; i < type_count; i++) {
        const ResTable_config& this_config = candidate_configs[i];

        // 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 (resource_resolution_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 ResTable_type* type = filtered_group.types[i];
        const uint32_t offset = LoadedPackage::GetEntryOffset(type, entry_idx);
        if (offset == ResTable_type::NO_ENTRY) {
          if (resource_resolution_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;

        if (resource_resolution_logging_enabled_) {
          last_resolution_.steps.push_back(Resolution::Step{resolution_type,
                                                            this_config.toString(),
                                                            &loaded_package->GetPackageName()});
        }
      }
    } 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) {
        ResTable_config this_config{};

        if (!ignore_configuration) {
          this_config.copyFromDtoH((*iter)->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 uint32_t offset = LoadedPackage::GetEntryOffset(*iter, entry_idx);
        if (offset == ResTable_type::NO_ENTRY) {
          continue;
        }

        best_cookie = cookie;
        best_package = loaded_package;
        best_type = *iter;
        best_config_copy = this_config;
        best_config = &best_config_copy;
        best_offset = offset;

        if (ignore_configuration) {
          // Any configuration will suffice, so break.
          break;
        }

        if (resource_resolution_logging_enabled_) {
          last_resolution_.steps.push_back(Resolution::Step{resolution_type,
                                                            this_config.toString(),
                                                            &loaded_package->GetPackageName()});
        }
      }
    }
  }

  if (UNLIKELY(best_cookie == kInvalidCookie)) {
    return kInvalidCookie;
  }

  const ResTable_entry* best_entry = LoadedPackage::GetEntryFromOffset(best_type, best_offset);
  if (UNLIKELY(best_entry == nullptr)) {
    return kInvalidCookie;
  }

  out_entry->entry = ResTable_entry_handle::unmanaged(best_entry);
  out_entry->config = *best_config;
  out_entry->type_flags = type_flags;
  out_entry->package_name = &best_package->GetPackageName();
  out_entry->type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1);
  out_entry->entry_string_ref =
          StringPoolRef(best_package->GetKeyStringPool(), best_entry->key.index);
  out_entry->dynamic_ref_table = package_group.dynamic_ref_table.get();

  return best_cookie;
}

PackageGroup结构体变量std::vector<ConfiguredPackage> packages_;是一个数组,保存了多个ConfiguredPackage引用,ConfiguredPackage定义了2个变量:const LoadedPackage* loaded_package_;表示一个apk资源;ByteBucketArray<FilteredConfigGroup> filtered_configs_;保存着匹配着当前AssetManager的配置ResTable_config,以空间换时间,节省后续查找的时间。

Package

先遍历packages_数组,得到每个ConfiguredPackageloaded_package_LoadedPackage定义了一个apk的资源配置:

  • ResStringPool type_string_pool_ : 存储资源类型的名称。例如,资源的类型可能是 stringdrawablelayout 等。
  • ResStringPool key_string_pool_ : 存储具体资源条目的名称(也就是键)。例如,strings.xml 中的每个 <string> 元素的名称(如 "app_name")。
  • std::string package_name_ : 包名。
  • int package_id_ : package_id对应resid的前2位,如0x01040000,package_id就是0x01。
  • int type_id_offset_ : 内存偏移量,一般为0。
  • ByteBucketArray<TypeSpecPtr> type_specs_ : TypeSpecPtr是结构体TypeSpec的指针(using TypeSpecPtr = util::unique_cptr<TypeSpec>;),TypeSpec定义了每一个Type(资源类型),一个Type的所有Entry都存储在同一个内存块中。
  • ByteBucketArray<uint32_t> resource_ids_ : resource_ids_描述了每个Type的Entry数量,比如string类型有100个,drawable类型有80个,代码形式表现为resource_ids_[typeIndex_] = entry_count

ByteBucketArray是模板类,数据结构是数组,ByteBucketArray<TypeSpecPtr>就是TypeSpecPtr类型的数组。

回到AssetManager2::FindEntryInternal方法,在遍历packages_数组后,通过LoadedPackagetype_idx参数得到它指向的TypeSpec对象,loaded_package->GetTypeSpecByTypeIndex(type_idx);

Type

对于每种Type(资源类型)都有多种配置,比如drawable资源有drawabledrawable-hdpidrawable-ldrtl-hdpidrawable-ldrtl-mdpi等,要找到匹配的资源类型,需要比较desired_config和每个Type关联的配置是否匹配,可以通过上文提到的ResTable_config提供的match()isBetterThan()compare()等方法判断。

TypeSpec定义了一个Type(资源类型)结构体。注意命名后缀Spec,Spec是Specification**(规范/规格)** 的缩写,它一般用于表示某种 定义、描述或元数据 ,而不是实际的数据本身,可以理解为在package和type中的一个抽象类型。ResTable_type(在 types[0] 数组中)才是实际的资源类型数据,它存储了不同配置下的资源内容,type_count表示一个package存在多个的Type(资源类型),比如有layout, drawable,那么type_count的值就是2。

ResTable_type才是实际的资源类型数据,定义了identryCountconfigentriesStart等关键数据。

  • header : 头文件。
  • id : type_id对应resid的三四位,如0x01040000,id就是0x04。
  • entryCount : entry数量。
  • entriesStart : 从header数据开始,ResTable_entry 数据的偏移量。
  • config : 特定的资源配置。
  • FLAG_SPARSE : 是否分散保存。

回到AssetManager2::FindEntryInternal方法,遍历TypeSpec的每个ResTable_type对象,比较其关联的config,得到和desired_config最匹配的ResTable_type

找到了匹配的Type后,下一步就是通过Type找到Entry,通过传递过来的entry_idx参数:

const uint32_t offset = LoadedPackage::GetEntryOffset(*iter, entry_idx);

LoadedPackage::GetEntryOffset方法的作用是获取指定资源条目(entry)在资源类型(type_chunk)中的偏移量,首先判断ResTable_type是否有FLAG_SPARSE标志表示分散保存,如果是分散保存,则需要通过二分搜索找到对应的entry,否则简单多了,找到头文件之后的内存指针,后面的内存保存着uint32_t类型的偏移量数据。

cpp 复制代码
//LoadedArsc.cpp
uint32_t LoadedPackage::GetEntryOffset(const ResTable_type* type_chunk, uint16_t entry_index) {
    const size_t offsets_offset = dtohs(type_chunk->header.headerSize);
    const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
    reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset);
  	return dtohl(entry_offsets[entry_index]);
}
  1. reinterpret_cast<const uint8_t*>(type_chunk) 先将结构体指针重新解释为字节指针,type_chunk 是指向 ResTable_type 结构的指针,而结构体指针一般会按结构体的大小和对齐方式对齐,这对于我们后续的内存访问和计算偏移并不方便,通过将 type_chunk 转换为 uint8_t*,我们将其视为一个字节数组,这样就可以按 字节 访问内存。

  2. reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offsettype_chunk*指针位置偏移offsets_offset个字节。

  3. reinterpret_cast<const uint32_t*>(reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset)将其重新解释为uint32_t*类型的数据,在内存中,偏移量通常是以 4 字节为单位存储的。entry_offsets 存储了一组 偏移量 ,每个偏移量对应一个资源条目(例如,资源ID,图像,字符串等)。entry_offsets[entry_index] 便是 资源条目对应的偏移量

  4. dtohl(entry_offsets[entry_index])将网络字节序(大端字节序)转换为主机字节序(小端字节序)。

好了,现在确定了ResTable_type对象,和Entry的偏移量,接下来就是查找Entry对象了。

Entry

查找获取Entry对象:const ResTable_entry* best_entry = LoadedPackage::GetEntryFromOffset(best_type, best_offset);,其中best_type是最匹配的Type,ResTable_type对象,best_offset是Entry的偏移量。

cpp 复制代码
//LoadedArsc.cpp
const ResTable_entry* LoadedPackage::GetEntryFromOffset(const ResTable_type* type_chunk,
                                                        uint32_t offset) {
  return reinterpret_cast<const ResTable_entry*>(reinterpret_cast<const uint8_t*>(type_chunk) +
                                                 offset + dtohl(type_chunk->entriesStart));
}

type_chunk*指针位置偏移offset个字节,然后加上type_chunk->entriesStart偏移量,就是ResTable_entry对象的内存数据了。

内存结构如下:

ResTable_type->header.headerSize表示ResTable_type结构体的大小,ResTable_type->entriesStart表示ResTable_type结构体和Entry offSets的大小。

回到LoadedPackage::GetEntryFromOffset,将对应的内存数据重新解释为ResTable_entry类型。

ResTable_entry定义了每个资源条目的数据结构。

  • size : 数据大小,字节数。
  • flags : FLAG_COMPLEX 或 FLAG_PUBLIC 或 FLAG_WEAK。
  • key : ResStringPool_ref类型,已一个uint32_t类型的index指向stringPool中的某个string,这里保存了每个entry对应的的值。

总结一下,ResTable_config从十几个维度定义了一个特定的资源配置,先是通过package_idx确定了PackageGroup,遍历PackageGroup中的所有LoadedPackage,然后通过type_idx找到LoadedPackage中保存的TypeSpec对象,比较desired_configTypeSpec关联的ResTable_config,找到最匹配的TypeSpec,最后通过entry_idx找到对应的内存地址,转换解释为ResTable_entry对象。

返回结果

回到AssetManager2::FindEntryInternal,找到了对应的Entry后通过FindEntryResult对象返回。

cpp 复制代码
//AssetManager2.cpp
  const ResTable_entry* best_entry = LoadedPackage::GetEntryFromOffset(best_type, best_offset);

  out_entry->entry = ResTable_entry_handle::unmanaged(best_entry);
  out_entry->config = *best_config;
  out_entry->type_flags = type_flags;
  out_entry->package_name = &best_package->GetPackageName();
  out_entry->type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1);
  out_entry->entry_string_ref =
          StringPoolRef(best_package->GetKeyStringPool(), best_entry->key.index);
  out_entry->dynamic_ref_table = package_group.dynamic_ref_table.get();

FindEntryResultentryResTable_entry_handle类型,共享指针指向best_entry

FindEntryResult指针保存结果返回给JNI调用者。

cpp 复制代码
//AssetManager2.cpp
ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag,
                                           uint16_t density_override, Res_value* out_value,
                                           ResTable_config* out_selected_config,
                                           uint32_t* out_flags) const {
    FindEntryResult entry;
  	ApkAssetsCookie cookie = FindEntry(resid, density_override, false /* stop_at_first_match */,
                                     false /* ignore_configuration */, &entry);
    const ResTable_entry* table_entry = *entry.entry;
    const Res_value* device_value = reinterpret_cast<const Res_value*>(
    reinterpret_cast<const uint8_t*>(table_entry) + dtohs(table_entry->size));
  	out_value->copyFrom_dtoh(*device_value);
    return cookie;
}

reinterpret_cast<const Res_value*>(reinterpret_cast<const uint8_t*>(table_entry) + dtohs(table_entry->size))ResTable_entry内存地址后size个字节的数据重新解释装换为Res_value对象,size的值是ResTable_entry结构体的内存大小。

Res_value类定义:

JNI的NativeGetResourceValue方法将CPP中的Res_value对象转换为Java中的TypedValue对象:

cpp 复制代码
//android_util_AssetManager.cpp
static jint NativeGetResourceValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
                                   jshort density, jobject typed_value,
                                   jboolean resolve_references) {
    ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
  	Res_value value;
  	ResTable_config selected_config;
  	uint32_t flags;
  	ApkAssetsCookie cookie =
      	assetmanager->GetResource(static_cast<uint32_t>(resid), false /*may_be_bag*/,
                                static_cast<uint16_t>(density), &value, &selected_config, &flags);
    uint32_t ref = static_cast<uint32_t>(resid);
    return CopyValue(env, cookie, value, ref, flags, &selected_config, typed_value);
}

CopyValue将Res_value对象数据复制给TypedValue对象:

cpp 复制代码
//android_util_AssetManager.cpp
static jint CopyValue(JNIEnv* env, ApkAssetsCookie cookie, const Res_value& value, uint32_t ref,
                      uint32_t type_spec_flags, ResTable_config* config, jobject out_typed_value) {
  env->SetIntField(out_typed_value, gTypedValueOffsets.mType, value.dataType);
  env->SetIntField(out_typed_value, gTypedValueOffsets.mAssetCookie,
                   ApkAssetsCookieToJavaCookie(cookie));
  env->SetIntField(out_typed_value, gTypedValueOffsets.mData, value.data);
  env->SetObjectField(out_typed_value, gTypedValueOffsets.mString, nullptr);
  env->SetIntField(out_typed_value, gTypedValueOffsets.mResourceId, ref);
  env->SetIntField(out_typed_value, gTypedValueOffsets.mChangingConfigurations, type_spec_flags);
  if (config != nullptr) {
    env->SetIntField(out_typed_value, gTypedValueOffsets.mDensity, config->density);
  }
  return static_cast<jint>(ApkAssetsCookieToJavaCookie(cookie));
}
  • gTypedValueOffsets.mType : TypedValue.type
  • gTypedValueOffsets.mData : TypedValue.data

Java层的AssetManager通过TypedValue找到entry对应的值。

java 复制代码
//AssetManger.java
	boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
            boolean resolveRefs) {
        final int cookie = nativeGetResourceValue(
                    mObject, resId, (short) densityDpi, outValue, resolveRefs);
        if (outValue.type == TypedValue.TYPE_STRING) {
                outValue.string = getPooledStringForCookie(cookie, outValue.data);
        }
    }

判断type是否为ypedValue.TYPE_STRING,如果是的话getPooledStringForCookie(cookie, outValue.data);,cookie是上文AssetManager2::FindEntryInternal方法返回的best_cookieusing ApkAssetsCookie = int32_t;一个整型变量关联一个LoadedPackage,相当于LoadedPackage的索引。

java 复制代码
//AssetManger.java
	CharSequence getPooledStringForCookie(int cookie, int id) {
        // Cookies map to ApkAssets starting at 1.
        return getApkAssets()[cookie - 1].getStringFromPool(id);
    }

通过cookie索引到具体的ApkAssets对象,然后通过outValue.data在字符串池中获取对应的值。

getApkAssets()返回一个ApkAssets[]数组,一个应用可能有多个ApkAssets,比如下面的应用com.example.myapplication,3个systemApkAssets和1个mUserApkAssets,返回的ApkAssets[]大小为4。

shell 复制代码
01-01 07:24:10.137  1143  1283 D AssetManager: AssetManager systemApkAssets: 0, /system/framework/framework-res.apk
01-01 07:24:10.137  1143  1283 D AssetManager: AssetManager systemApkAssets: 1, /vendor/overlay/framework-res__auto_generated_rro_vendor.apk
01-01 07:24:10.137  1143  1283 D AssetManager: AssetManager systemApkAssets: 2, /system/product/overlay/framework-res__auto_generated_rro_product.apk
01-01 07:24:10.137  1143  1283 D AssetManager: AssetManager mUserApkAssets: 0, /data/app/~~YZ0ivppt_GbGfdxqixf45Q==/com.example.myapplication-_rJfRy7PAecqI4zoHuVdbA==/base.apk
相关推荐
每次的天空24 分钟前
Android-自定义View的实战学习总结
android·学习·kotlin·音视频
恋猫de小郭1 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
断剑重铸之日2 小时前
Android自定义相机开发(类似OCR扫描相机)
android
随心最为安2 小时前
Android Library Maven 发布完整流程指南
android
岁月玲珑2 小时前
【使用Android Studio调试手机app时候手机老掉线问题】
android·ide·android studio
还鮟6 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡7 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi008 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体
zhangphil9 小时前
Android理解onTrimMemory中ComponentCallbacks2的内存警戒水位线值
android
你过来啊你9 小时前
Android View的绘制原理详解
android