在Android APK 文件结构中我们简单介绍了 Android APK 的构成,本文怎通过 ApkAssets
展开介绍 APK 在Android 运行时的形态,为后续详细介绍资源管理做铺垫。 ApkAssets
在内存中用于表示已加载且不可变的 APK 文件。这个类的实现主要是 C++,而在 Java 层只暴露了非常少的 API。主要用于在 Android 应用中直接访问 APK 文件内部的资源文件,比如图片、布局、字符串等。ApkAssets
主要通过 AssetManager
来访问。 可以通过Android 资源管理-AssetManager获取关于AssetManager
更多信息。
注意
Android 中一个 ApkAssets 对应一个 APK 资源包,并且它内部会有一个 LoadedArsc,一个LoadedArsc对应一个资源包中的 resources.arsc 文件,因此解读 ApkAssets 也可以理解解读 resources.arsc 文件。
1、图解 Native 层 ApkAssets
下述分析源代码均参考自frameworks/base/libs/androidfw/include/androidfw/ResourceTypes.h
在内存中,APK 其实就是一堆各种类型的数据集合。
ResTable_header(1)
位于 ARSC 文件的开头部分,用于描述整个 ARSC 文件的结构和内容,主要内容就是记录当前文件有几个LoadedPackage
,一般都是一个。
反编译 ARSC 文件截图
ResStringPool(1)
字符串资源池用于存储和管理字符串资源。ResStringPool_header
用与描述资源池的头部信息,它包含了关于资源池的基本信息,记录了资源池中 string 和 style 的数量 以及内存中的 index 偏移值。
ResStringPool_header中偏移的计算示例
stringsStart:8312+12(Ch)=8324(2084h)// 字符串数据段相对于这个resStringPool的header的起始地址的偏移量
stylesStart:此例中,APK文件不包含style // style数据段相对于这个resStringPool的header的起始地址的偏移量
ResStringPool_header
之后则跟着mEntries、mEntryStyles、mStrings、mStyles
mEntries:string偏移数组,它的元素的个数为mHeader.stringCount
mEntryStyles:style偏移数组,它的元素的个数为mHeader.styleCount,本例则没有因为style为0
mStrings:存放所该字符串池中所有的字符串
mStyles:存放所该字符串池中所有的style
反编译 ARSC 文件截图
LoadedPackage(1)
包含了应用的所有资源,如图片、布局文件、字符串等。LoadedPackage
是这些资源包在内存中的表示形式,用于在运行时动态加载和管理这些资源。
反编译 ARSC 文件截图
ResTable_package(1)
用于描述资源包的信息,在 ARSC 文件中,ResTable_package
包含了资源包的数据块内容。有点类似各个chunk 的 header 作用。ResTable_package
中定义的 name 便是我们 APK 的名字。
这也能看出来从 Android 8.0(Oreo)开始,APK 文件名的最大长度限制为最大 256(100h)个字符
type_string_pool_、key_string_pool_(1)
type_string_pool_
和 key_string_pool_
主要收集的信息都来自 R.java 文件。
反编译 ARSC 文件截图
TypeSpec(n)
用于描述某个特定资源,比如drawable,color。内部由ResTable_typeSpec、ResTable_type
、ResTable_entry
、Res_value
ResTable_typeSpec(1)
用于描述某个指定资源类型相关的元数据,更好的帮助描述该类型的资源如何被组织和访问。和type_string_pool_
都是对应的。比如上例中展示的type_string_pool_
字符串有 14 个,那么ResTable_typeSpec
数量也会有 14 个。entryCount 则用于描述同类型资源的个数,比如上例中 anim 资源的个数实际为 28(反编译 R 文件获取),相应的我们反编译 ARSC 文件,获取的到数量也是一致的(下图中entryCount显示为28),
entryCount:该类型的资源的总数。
反编译 ARSC 文件截图
ResTable_type(n)
解析这块最为复杂,涉及到 APK 中各种资源的处理,还有相同资源针对不同config的处理。这里的 n 表示就是同一个资源涵盖的config数量。比如 demo 中 ic_launcher 和 ic_launcher_round 分别配置了 6 中不同的 config 资源文件,那么在 ARSC 文件中 ResTable_type
数量也是 6 个。
ResTable_entry(1n)、Res_value(1n)
用于描述制定资源类型的真实数据,ResTable_type
类似 chunk header的作用,ResTable_entry
、Res_value
则分别用于表示制定资源类型下的确定资源的key和value,比如 ResTable_type
为 anim,ResTable_entry
为abc_fade_in,Res_value
为btn_radio_to_off_mtrl_dot_group_animation
反编译 ARSC 文件截图
DynamicPackageEntry
一般 APK 不涉及,暂不详细分析
OverlayableInfo
一般 APK 不涉及,暂不详细分析
2、核心源码释义参考
ApkAssets
ApkAssets
类是一个用于管理和访问 APK 文件资源的类,提供了多种方式加载资源(包括从文件路径、文件描述符、AssetsProvider
等),并提供了各种方法来获取和管理这些资源数据。
- 通过
resources_asset_
存储的资源文件 - 通过
loaded_arsc_
和loaded_idmap_
存储的解析后的资源数据占用内存,通常这些数据以特定的数据结构形式存在,供进一步访问和操作。 - 通过
assets_provider_
负责管理和提供对资源的访问。
loaded_arsc_
和loaded_idmap_
则是 ApkAssets 的重点
// ApkAssets 类用于处理 APK 资源,加载资源表、覆盖资源等。
class ApkAssets : public RefBase {
public:
// 从设备路径创建 ApkAssets。
static ApkAssetsPtr Load(const std::string& path, package_property_t flags = 0U);
// 从打开的文件描述符创建 ApkAssets。
static ApkAssetsPtr LoadFromFd(base::unique_fd fd, const std::string& debug_name,
package_property_t flags = 0U, off64_t offset = 0,
off64_t len = AssetsProvider::kUnknownLength);
// 从一个 AssetProvider 创建 ApkAssets。
static ApkAssetsPtr Load(std::unique_ptr<AssetsProvider> assets, package_property_t flags = 0U);
// 从资源文件(resources.arsc)创建 ApkAssets。
static ApkAssetsPtr LoadTable(std::unique_ptr<Asset> resources_asset,
std::unique_ptr<AssetsProvider> assets,
package_property_t flags = 0U);
// 从 IDMAP 文件创建 ApkAssets。
static ApkAssetsPtr LoadOverlay(const std::string& idmap_path, package_property_t flags = 0U);
// 获取资源提供者。
const AssetsProvider* GetAssetsProvider() const { return assets_provider_.get(); }
// 获取已加载的资源表(resources.arsc)。
const LoadedArsc* GetLoadedArsc() const { return loaded_arsc_.get(); }
// 获取已加载的 IDMAP 数据。
const LoadedIdmap* GetLoadedIdmap() const { return loaded_idmap_.get(); }
private:
// 资源加载实现方法。
static ApkAssetsPtr LoadImpl(std::unique_ptr<AssetsProvider> assets,
package_property_t property_flags,
std::unique_ptr<Asset> idmap_asset,
std::unique_ptr<LoadedIdmap> loaded_idmap);
static ApkAssetsPtr LoadImpl(std::unique_ptr<Asset> resources_asset,
std::unique_ptr<AssetsProvider> assets,
package_property_t property_flags,
std::unique_ptr<Asset> idmap_asset,
std::unique_ptr<LoadedIdmap> loaded_idmap);
public:
ApkAssets(PrivateConstructorUtil, std::unique_ptr<Asset> resources_asset,
std::unique_ptr<LoadedArsc> loaded_arsc, std::unique_ptr<AssetsProvider> assets,
package_property_t property_flags, std::unique_ptr<Asset> idmap_asset,
std::unique_ptr<LoadedIdmap> loaded_idmap);
private:
std::unique_ptr<Asset> resources_asset_; // 表示 APK 中 resources.arsc
std::unique_ptr<LoadedArsc> loaded_arsc_; // 表示加载的资源表(resources.arsc)
// 和resources_asset_的区别在于,resources_asset_是表示APK中的resources.arsc文件,
// 而loaded_arsc_则是对这个文件加载解析后的一个数据结构
std::unique_ptr<AssetsProvider> assets_provider_; // 资源提供者
package_property_t property_flags_ = 0U; // 资源属性标志
std::unique_ptr<Asset> idmap_asset_; // 表示 IDMAP 文件(用于资源覆盖)
std::unique_ptr<LoadedIdmap> loaded_idmap_; // 已加载的 IDMAP 数据
// 和resources_asset_和loaded_arsc_区别类似
};
}
LoadedArsc
LoadedArsc
类用于加载和解析 resources.arsc
文件,提供了对 APK 中所有资源(如字符串、图片、布局等)的访问。LoadedArsc
非常简单,主要就是一个 Global String Pool 和 LoadedPackage,它的核心功能包括:
- 加载和解析
resources.arsc
文件。 - 提供访问资源包和字符串池的功能。
- 支持资源覆盖(通过
IDMAP
文件)。 - 提供管理和查找字符串资源、包资源的能力。
LoadedArsc 中 LoadedPackage 定义是一个 vector,从定义来看 Android 是支持 resources.arsc 中存在多个 package 的,但是从实际的运行来看,基本都是一对一的关系。
class LoadedArsc {
public:
// 从内存中加载资源表,`data` 是指向内存的指针,`length` 是数据的长度。
// `data` 的生命周期必须长于返回的 LoadedArsc 对象。
// `loaded_idmap` 用于资源覆盖,`property_flags` 是额外的加载属性标志。
static std::unique_ptr<LoadedArsc> Load(incfs::map_ptr<void> data,
size_t length,
const LoadedIdmap* loaded_idmap = nullptr,
package_property_t property_flags = 0U);
// 通过 `loaded_idmap` 加载资源表。适用于没有传入具体的资源数据(例如资源表为空的情况)。
static std::unique_ptr<LoadedArsc> Load(const LoadedIdmap* loaded_idmap = nullptr);
// 创建一个空的 LoadedArsc 实例。用于没有 `resources.arsc` 文件的 APK。
static std::unique_ptr<LoadedArsc> CreateEmpty();
// 获取字符串池,所有字符串资源(如文本)都存储在这里。
// 字符串池中的数据类型为 `Res_value::TYPE_STRING`。
inline const ResStringPool* GetStringPool() const {
return global_string_pool_.get();
}
// 根据指定的 package_id 获取对应的资源包(LoadedPackage)。
// 如果不存在该包,返回 nullptr。
const LoadedPackage* GetPackageById(uint8_t package_id) const;
// 获取所有的资源包(LoadedPackage)的向量。
// 返回当前 `LoadedArsc` 对象中所有加载的资源包。
inline const std::vector<std::unique_ptr<const LoadedPackage>>& GetPackages() const {
return packages_;
}
private:
// 禁止拷贝构造和赋值操作,确保资源管理的唯一性。
DISALLOW_COPY_AND_ASSIGN(LoadedArsc);
// 默认构造函数
LoadedArsc() = default;
// 加载资源表,处理资源的块数据。会根据传入的 `Chunk` 进行解析。
// 也会根据 `loaded_idmap` 进行资源覆盖,`property_flags` 设置其他加载特性。
bool LoadTable(
const Chunk& chunk, const LoadedIdmap* loaded_idmap, package_property_t property_flags);
// 加载字符串池(即存储所有字符串资源的数据结构)。
bool LoadStringPool(const LoadedIdmap* loaded_idmap);
// 全局字符串池,存储所有字符串资源。
// 使用 `ResStringPool` 来管理和查找字符串。
std::unique_ptr<ResStringPool> global_string_pool_ = util::make_unique<ResStringPool>();
// 所有资源包(LoadedPackage)列表。每个资源包包含了一组相关的资源。
std::vector<std::unique_ptr<const LoadedPackage>> packages_;
};
LoadedPackage
LoadedPackage
专注于从资源表中提取和管理资源信息。不仅支持标准的资源查找和访问,还提供了对动态包、Overlayable 资源和系统包等复杂功能的支持。通过 ResStringPool
管理字符串池、通过迭代器访问资源条目、通过各种方法查找资源,LoadedPackage
提供了一整套灵活且高效的接口,用于访问和操作 Android 应用的资源。
type_specs_ 是 LoadedPackage 中存储资源重要对象
class LoadedPackage {
public:
// 迭代器类,用于遍历 `LoadedPackage` 中的资源条目。
class iterator {
...
};
// 静态方法:加载一个 `Chunk` 数据,并返回一个 `LoadedPackage` 实例。
static std::unique_ptr<const LoadedPackage> Load(const Chunk& chunk,
package_property_t property_flags);
// 根据类型名和条目名查找资源条目。返回一个部分资源 ID(包 ID 为 0x00)。调用者需自行修复包 ID。
base::expected<uint32_t, NullOrIOError> FindEntryByName(const std::u16string& type_name,
const std::u16string& entry_name) const;
// 静态方法:从指定的资源类型块中获取资源条目。
static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
GetEntry(incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index);
// 静态方法:从资源类型块中获取资源条目的偏移量。
static base::expected<uint32_t, NullOrIOError> GetEntryOffset(
incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index);
// 静态方法:根据资源类型块和偏移量获取资源条目。
static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
GetEntryFromOffset(incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset);
// 返回类型字符串池,存储资源类型名的字符串。
const ResStringPool* GetTypeStringPool() const {
return &type_string_pool_;
}
// 返回键值字符串池,存储资源条目名称的字符串。
const ResStringPool* GetKeyStringPool() const {
return &key_string_pool_;
}
// 返回资源包的名称。
const std::string& GetPackageName() const {
return package_name_;
}
// 返回资源包的 ID。
int GetPackageId() const {
return package_id_;
}
...
// 返回动态包映射,即从包名到包 ID 的映射。这个映射可能与编译时的包 ID 不同。
const std::vector<DynamicPackageEntry>& GetDynamicPackageMap() const {
return dynamic_package_map_;
}
// 收集资源包中的所有配置(ResTable_config)。可以排除 mipmap 类型。
base::expected<std::monostate, IOError> CollectConfigurations(
bool exclude_mipmap, std::set<ResTable_config>* out_configs) const;
// 根据类型索引获取类型规格(TypeSpec)。类型索引是 0xPPTTEEEE 格式中的 TT - 1。
inline const TypeSpec* GetTypeSpecByTypeIndex(uint8_t type_index) const {
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;
}
// 获取指定资源 ID 的 overlayable 信息。如果该资源不可覆盖,返回 nullptr。
const OverlayableInfo* GetOverlayableInfo(uint32_t resid) const {
for (const std::pair<OverlayableInfo, std::unordered_set<uint32_t>>& overlayable_info_ids
: overlayable_infos_) {
if (overlayable_info_ids.second.find(resid) != overlayable_info_ids.second.end()) {
return &overlayable_info_ids.first;
}
}
return nullptr;
}
// 判断该资源包是否定义了 overlayable 资源。
bool DefinesOverlayable() const {
return defines_overlayable_;
}
// 获取 overlayable 映射,即从 overlayable 名称到 actor 的映射。
const std::unordered_map<std::string, std::string>& GetOverlayableMap() const {
return overlayable_map_;
}
// 获取资源 ID 别名映射,即旧资源 ID 到新资源 ID 的映射。
const std::vector<std::pair<uint32_t, uint32_t>>& GetAliasResourceIdMap() const {
return alias_id_map_;
}
private:
// 禁止拷贝和赋值操作,确保对象的唯一性。
DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
// 类型字符串池和键值字符串池,分别用于存储类型名称和资源条目名称。
ResStringPool type_string_pool_;
ResStringPool key_string_pool_;
// 资源包名称。
std::string package_name_;
// 标志:是否定义了 overlayable 资源。
bool defines_overlayable_ = false;
// 资源包的 ID 和类型 ID 偏移。
int package_id_ = -1;
int type_id_offset_ = 0;
// 资源包的属性标志。
package_property_t property_flags_ = 0U;
// 类型规格、资源 ID 列表和动态包映射。
std::unordered_map<uint8_t, TypeSpec> type_specs_;
ByteBucketArray<uint32_t> resource_ids_;
std::vector<DynamicPackageEntry> dynamic_package_map_;
// Overlayable 信息和别名资源 ID 映射。
std::vector<std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_;
std::vector<std::pair<uint32_t, uint32_t>> alias_id_map_;
// overlayable 名称到 actor 的映射。
std::unordered_map<std::string, std::string> overlayable_map_;
};
TypeSpec
struct TypeSpec {
// TypeSpec 结构体的内部结构 TypeEntry。
// 每个 TypeEntry 对应一个具体的资源类型条目,包含类型、配置等信息。
struct TypeEntry {
// type 是一个指向 mmap 映射区域的指针,用来访问具体的资源类型数据。
// 它是一个经过验证的映射指针,指向一个 ResTable_type 类型的数据。
incfs::verified_map_ptr<ResTable_type> type;
// 该资源类型的配置(ResTable_config)信息。
// 配置通常包含有关该资源类型的分辨率、语言、屏幕密度等信息,
// 访问此缓存配置可以减少内存页面错误(page faults)。
ResTable_config config;
};
// type_spec 是指向 mmapped 数据的指针,存储着标记(flags)数据。
// 这些标记用于表示资源条目的访问权限(如 public)以及该资源条目的变化配置。
incfs::verified_map_ptr<ResTable_typeSpec> type_spec;
// type_entries 是一个包含多个 TypeEntry 的 vector,
// 每个 TypeEntry 表示一个具体的资源类型条目,包括资源的配置和类型信息。
std::vector<TypeEntry> type_entries;
// GetFlagsForEntryIndex 方法用于获取指定索引的资源条目的标记信息。
// entry_index 是条目索引,方法将返回该资源条目的标记信息(如是否为 public、配置等)。
base::expected<uint32_t, NullOrIOError> GetFlagsForEntryIndex(uint16_t entry_index) const {
// 如果 entry_index 超出了 entryCount 的范围,返回 0U
if (entry_index >= dtohl(type_spec->entryCount)) {
return 0U;
}
// 计算并访问 entry_flags_ptr 来获取资源条目的标记数据
const auto entry_flags_ptr = ((type_spec + 1).convert<uint32_t>() + entry_index);
// 如果指针为空,表示访问失败,返回 I/O 错误
if (!entry_flags_ptr) {
return base::unexpected(IOError::PAGES_MISSING);
}
// 返回对应资源条目的标记信息
return entry_flags_ptr.value();
}
};
3、ApkAssets 初始化
我们以loadFromPath
来构建一个ApkAssets
为例介绍一下ApkAssets
的初始化。
public static @NonNull ApkAssets loadFromPath(@NonNull String path, @PropertyFlags int flags)
throws IOException {
return new ApkAssets(FORMAT_APK, path, flags, null /* assets */);
}
private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags,
@Nullable AssetsProvider assets) throws IOException {
Objects.requireNonNull(path, "path");
mFlags = flags;
mNativePtr = nativeLoad(format, path, flags, assets);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
mAssets = assets;
}
核心代码
// NativeLoad 是一个加载不同格式(APK、IDMAP、ARSC 或目录)的资源并返回 jlong 值的函数。
// 该方法使用 JNIEnv 与 Java 对象交互,并根据提供的路径和属性加载资源。
static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t format,
jstring java_path, const jint property_flags, jobject assets_provider) {
// ScopedUtfChars 将 Java 字符串(jstring)转换为 C++ 字符串(const char*)。
// 这样可以确保 Java 字符串在函数结束后被自动释放。
ScopedUtfChars path(env, java_path);
// 如果字符串转换失败,则返回 0 作为错误信号。
if (path.c_str() == nullptr) {
return 0;
}
// 将资源路径记录到追踪日志中,方便调试。
ATRACE_NAME(base::StringPrintf("LoadApkAssets(%s)", path.c_str()).c_str());
// 使用提供的 assets_provider 创建资源加载器,这个加载器处理资源的加载过程。
auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
// 声明一个指针,用于存储加载的 APK 资源。
AssetManager2::ApkAssetsPtr apk_assets;
// 根据 'format' 参数,函数将处理不同的资源加载方式:
switch (format) {
case FORMAT_APK: {
// 对于 APK 格式:创建一个多资源提供者,它将 loader_assets 与一个 zip 提供者结合,
// zip 提供者负责从 APK 文件中提取资源。
// 然后使用提供的属性标志加载 APK 资源。
auto assets = MultiAssetsProvider::Create(std::move(loader_assets),
ZipAssetsProvider::Create(path.c_str(),
property_flags));
apk_assets = ApkAssets::Load(std::move(assets), property_flags);
break;
}
case FORMAT_IDMAP:
// 对于 IDMAP 格式:将 APK 资源作为覆盖资源加载。
apk_assets = ApkAssets::LoadOverlay(path.c_str(), property_flags);
break;
case FORMAT_ARSC:
// 对于 ARSC 格式:将 APK 资源作为表格加载,从资产文件加载数据。
apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFile(path.c_str()),
std::move(loader_assets),
property_flags);
break;
case FORMAT_DIRECTORY: {
// 对于目录格式:创建一个多资源提供者,将 loader_assets 与目录资源提供者结合。
// 然后使用提供的属性标志加载 APK 资源。
auto assets = MultiAssetsProvider::Create(std::move(loader_assets),
DirectoryAssetsProvider::Create(path.c_str()));
apk_assets = ApkAssets::Load(std::move(assets), property_flags);
break;
}
default:
// 如果传入的格式不支持,抛出一个异常,并返回错误信息。
const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
return 0;
}
// 如果 APK 资源加载失败,抛出一个 IOException 异常,并返回错误信息。
if (apk_assets == nullptr) {
const std::string error_msg = base::StringPrintf("Failed to load asset path %s", path.c_str());
jniThrowException(env, "java/io/IOException", error_msg.c_str());
return 0;
}
// 如果成功加载资源,返回结果(CreateGuardedApkAssets 将加载的 APK 资源包装成一个受保护的对象)。
return CreateGuardedApkAssets(std::move(apk_assets));
}
根据不同的格式类型来选择相应的资源加载方式的分支结构。不同格式(APK、IDMAP、ARSC、目录)需要使用不同的资源提供者来加载资源:
FORMAT_APK
:从 APK 文件中提取资源并加载。FORMAT_IDMAP
:加载 APK 资源作为覆盖资源。FORMAT_ARSC
:加载 APK 资源作为表格(可能是资源配置表)。FORMAT_DIRECTORY
:从指定的目录中加载资源。
针对 APK 核心逻辑,就是构建一个ZipAssetsProvider
通过libziparchive
库,操作 APK 文件。执行读取、解压、打包等操作。
C++ 层 ApkAssets 构造
// ApkAssets::Load 是 ApkAssets 类的入口函数,用于加载 APK 资源。
ApkAssetsPtr ApkAssets::Load(std::unique_ptr<AssetsProvider> assets, package_property_t flags) {
// 调用 LoadImpl 方法进行实际的资源加载
return LoadImpl(std::move(assets), flags, nullptr /* idmap_asset */, nullptr /* loaded_idmap */);
}
// ApkAssets::LoadImpl 是一个实现细节函数,用于加载 APK 资源。
// 该函数处理打开资源表(如 ARSC 文件)并进行资源的加载。
// 它还处理 IDMAP 和已加载 IDMAP 的相关逻辑。
ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<AssetsProvider> assets,
package_property_t property_flags,
std::unique_ptr<Asset> idmap_asset,
std::unique_ptr<LoadedIdmap> loaded_idmap) {
// 如果 assets 为 nullptr,返回空指针,表示加载失败
if (assets == nullptr) {
return {};
}
// 定义资源表是否存在的标志
bool resources_asset_exists = false;
// 尝试打开资源文件(kResourcesArsc)并读取资源,如果文件不存在,resources_asset 为 nullptr
auto resources_asset = assets->Open(kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER,
&resources_asset_exists);
// 如果打开资源文件失败且该文件存在,输出错误日志并返回空指针
if (resources_asset == nullptr && resources_asset_exists) {
LOG(ERROR) << "Failed to open '" << kResourcesArsc << "' in APK '" << assets->GetDebugName()
<< "'.";
return {};
}
// 如果打开资源文件成功,继续调用 LoadImpl 处理后续逻辑,传递资源文件、资产提供者和其他参数
return LoadImpl(std::move(resources_asset), std::move(assets), property_flags,
std::move(idmap_asset), std::move(loaded_idmap));
}
// ApkAssets::LoadImpl 的实际处理逻辑,负责加载 APK 资源表(ARSC 文件)和 IDMAP。
ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<Asset> resources_asset,
std::unique_ptr<AssetsProvider> assets,
package_property_t property_flags,
std::unique_ptr<Asset> idmap_asset,
std::unique_ptr<LoadedIdmap> loaded_idmap) {
// 如果 assets 为 nullptr,表示资源加载失败,返回空指针
if (assets == nullptr) {
return {};
}
// 用于存储加载后的 ARSC 资源表
std::unique_ptr<LoadedArsc> loaded_arsc;
// 如果资源文件(resources_asset)不为空,表示需要加载 ARSC 资源表
if (resources_asset != nullptr) {
// 获取资源文件的数据缓冲区,并确保数据对齐
const auto data = resources_asset->getIncFsBuffer(true /* aligned */);
const size_t length = resources_asset->getLength();
// 如果资源文件读取失败或数据长度为零,输出错误日志并返回空指针
if (!data || length == 0) {
LOG(ERROR) << "Failed to read resources table in APK '" << assets->GetDebugName() << "'.";
return {};
}
// 调用 LoadedArsc::Load 方法加载资源表
loaded_arsc = LoadedArsc::Load(data, length, loaded_idmap.get(), property_flags);
} else if (loaded_idmap != nullptr && IsFabricatedOverlay(loaded_idmap->OverlayApkPath())) {
// 如果没有资源文件但有已加载的 IDMAP,并且该 IDMAP 是一个“伪造的覆盖”,则加载 ARSC 数据
loaded_arsc = LoadedArsc::Load(loaded_idmap.get());
} else {
// 如果没有资源文件且没有有效的 IDMAP,创建一个空的 ARSC 数据
loaded_arsc = LoadedArsc::CreateEmpty();
}
// 如果加载 ARSC 资源表失败,输出错误日志并返回空指针
if (loaded_arsc == nullptr) {
LOG(ERROR) << "Failed to load resources table in APK '" << assets->GetDebugName() << "'.";
return {};
}
// 最终构造并返回一个 ApkAssets 对象
return ApkAssetsPtr::make(PrivateConstructorUtil{}, std::move(resources_asset),
std::move(loaded_arsc), std::move(assets), property_flags,
std::move(idmap_asset), std::move(loaded_idmap));
}
深刻理解完第一个章节,这部分代码就很简单了,只是通过代码将 ApkAssets 内存结构完成解析构造出来。
篇幅考虑并没有再额外罗列 LoadedArsc::Load 和 LoadedArsc::LoadTable 方法,请咨询感兴趣阅读
4、总结
ApkAssets
类是 Android 系统中用于表示 APK 文件资源的底层实现类。它提供了 APK 文件的不可变内存表示,并与 AssetManager
配合使用,允许高效地访问和共享 APK 内的资源。其实现主要是本地 C++ 代码,目的为了提高性能和减少资源消耗。