如何在C++中解析二进制XML格式的AndroidManifest.xml?
在C++中解析二进制AndroidManifest.xml的实现方案
我完全懂你的困扰——二进制格式的AndroidManifest.xml根本没法用pugixml这类常规XML解析器搞定,而且AAPT2在Visual Studio环境下的构建依赖一堆Android生态的东西,集成起来麻烦得要死。下面我给你一个轻量级的纯C++实现,不需要任何第三方库,直接就能在VS里编译运行,专门用来解析这种二进制Manifest文件。
实现思路
二进制AndroidManifest.xml的核心结构包括:
- 文件头部:存储魔数、版本、各个数据块的偏移和大小
- 字符串池:所有XML中的字符串(标签名、属性名、属性值等)都存在这里,用索引引用
- XML节点块:存储元素、文本、结束标签等节点信息
下面的代码实现了最核心的解析逻辑,能把二进制Manifest转换成可读的XML格式输出。
完整代码示例
#include <iostream> #include <fstream> #include <vector> #include <string> #include <cstdint> #include <locale> #include <codecvt> // 二进制XML文件头部结构,对应Android的BinaryXmlHeader struct BinaryXmlHeader { uint32_t magic; // 固定魔数: 0x00080003 uint32_t version; // 二进制XML版本 uint32_t stringPoolSize; // 字符串池总字节数 uint32_t stringPoolOffset; // 字符串池在文件中的偏移 uint32_t resourceIdsSize; // 资源ID池大小(可选) uint32_t resourceIdsOffset; // 资源ID池偏移(可选) uint32_t xmlNodesSize; // XML节点块总字节数 uint32_t xmlNodesOffset; // XML节点块在文件中的偏移 }; // XML节点类型枚举,对应Android的XmlNodeType enum XmlNodeType { START_ELEMENT = 0x0102, // 元素开始标签 END_ELEMENT = 0x0103, // 元素结束标签 TEXT = 0x0104 // 文本节点 }; // 解析字符串池,返回所有字符串的列表 std::vector<std::string> parseStringPool(std::ifstream& file, uint32_t offset, uint32_t size) { std::vector<std::string> strings; file.seekg(offset); uint32_t stringCount, styleCount, flags, stringsStart, stylesStart; file.read(reinterpret_cast<char*>(&stringCount), sizeof(stringCount)); file.read(reinterpret_cast<char*>(&styleCount), sizeof(styleCount)); file.read(reinterpret_cast<char*>(&flags), sizeof(flags)); file.read(reinterpret_cast<char*>(&stringsStart), sizeof(stringsStart)); file.read(reinterpret_cast<char*>(&stylesStart), sizeof(stylesStart)); // 读取每个字符串的偏移量 std::vector<uint32_t> stringOffsets(stringCount); for (uint32_t i = 0; i < stringCount; ++i) { file.read(reinterpret_cast<char*>(&stringOffsets[i]), sizeof(stringOffsets[i])); } // 逐个读取字符串,处理UTF-16到UTF-8的转换 std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter; for (uint32_t i = 0; i < stringCount; ++i) { file.seekg(offset + stringsStart + stringOffsets[i]); uint16_t len; file.read(reinterpret_cast<char*>(&len), sizeof(len)); bool isUtf16 = (len & 0x8000) != 0; len &= 0x7FFF; // 实际字符串长度 std::string str; if (isUtf16) { std::u16string u16str(len, '\0'); file.read(reinterpret_cast<char*>(&u16str[0]), len * 2); str = converter.to_bytes(u16str); } else { str.resize(len); file.read(&str[0], len); } strings.push_back(str); } return strings; } // 递归解析XML节点并输出可读格式 void parseXmlNodes(std::ifstream& file, uint32_t offset, uint32_t size, const std::vector<std::string>& stringPool, int indent = 0) { file.seekg(offset); uint32_t currentOffset = 0; while (currentOffset < size) { uint16_t nodeType, headerSize; uint32_t lineNumber, commentRef; // 读取节点头部 file.read(reinterpret_cast<char*>(&nodeType), sizeof(nodeType)); file.read(reinterpret_cast<char*>(&headerSize), sizeof(headerSize)); file.read(reinterpret_cast<char*>(&lineNumber), sizeof(lineNumber)); file.read(reinterpret_cast<char*>(&commentRef), sizeof(commentRef)); currentOffset += headerSize; if (nodeType == START_ELEMENT) { uint32_t namespaceRef, nameRef, attributeCount, classAttributeRef; file.read(reinterpret_cast<char*>(&namespaceRef), sizeof(namespaceRef)); file.read(reinterpret_cast<char*>(&nameRef), sizeof(nameRef)); file.read(reinterpret_cast<char*>(&attributeCount), sizeof(attributeCount)); file.read(reinterpret_cast<char*>(&classAttributeRef), sizeof(classAttributeRef)); // 输出缩进和开始标签 std::cout << std::string(indent, ' ') << "<" << stringPool[nameRef]; // 解析并输出属性 for (uint32_t i = 0; i < attributeCount; ++i) { uint32_t attrNamespaceRef, attrNameRef, attrValueRef, attrResourceId, attrValueType, attrValueData; file.read(reinterpret_cast<char*>(&attrNamespaceRef), sizeof(attrNamespaceRef)); file.read(reinterpret_cast<char*>(&attrNameRef), sizeof(attrNameRef)); file.read(reinterpret_cast<char*>(&attrValueRef), sizeof(attrValueRef)); file.read(reinterpret_cast<char*>(&attrResourceId), sizeof(attrResourceId)); file.read(reinterpret_cast<char*>(&attrValueType), sizeof(attrValueType)); file.read(reinterpret_cast<char*>(&attrValueData), sizeof(attrValueData)); std::cout << " " << stringPool[attrNameRef] << "=\"" << stringPool[attrValueRef] << "\""; } std::cout << ">" << std::endl; // 递归解析子节点 uint32_t childSize = size - currentOffset; parseXmlNodes(file, offset + currentOffset, childSize, stringPool, indent + 2); currentOffset += childSize; } else if (nodeType == END_ELEMENT) { uint32_t namespaceRef, nameRef; file.read(reinterpret_cast<char*>(&namespaceRef), sizeof(namespaceRef)); file.read(reinterpret_cast<char*>(&nameRef), sizeof(nameRef)); std::cout << std::string(indent, ' ') << "</" << stringPool[nameRef] << ">" << std::endl; } else if (nodeType == TEXT) { uint32_t textRef; file.read(reinterpret_cast<char*>(&textRef), sizeof(textRef)); std::cout << std::string(indent, ' ') << stringPool[textRef] << std::endl; } // 对齐到4字节边界(二进制XML的要求) while (currentOffset % 4 != 0) { file.seekg(1, std::ios::cur); currentOffset++; } } } int main() { // 替换成你的AndroidManifest.xml文件路径 const std::string filePath = "AndroidManifest.xml"; std::ifstream file(filePath, std::ios::binary); if (!file.is_open()) { std::cerr << "错误:无法打开文件 " << filePath << std::endl; return EXIT_FAILURE; } // 读取并验证文件头部 BinaryXmlHeader header; file.read(reinterpret_cast<char*>(&header), sizeof(header)); if (header.magic != 0x00080003) { std::cerr << "错误:不是有效的二进制AndroidManifest.xml文件" << std::endl; return EXIT_FAILURE; } // 解析字符串池和XML节点 auto stringPool = parseStringPool(file, header.stringPoolOffset, header.stringPoolSize); parseXmlNodes(file, header.xmlNodesOffset, header.xmlNodesSize, stringPool); file.close(); return EXIT_SUCCESS; }
使用说明
- 编译运行:在Visual Studio中新建一个空的C控制台项目,把上面的代码粘贴进去,设置C标准为C++17或更高(因为用到了
std::codecvt_utf8_utf16)。 - 路径替换:把主函数中的
filePath替换成你的二进制AndroidManifest.xml文件的实际路径。 - 输出结果:运行后会在控制台输出格式化后的可读XML内容。
扩展建议
- 如果需要解析资源ID或复杂属性值(比如颜色、尺寸等),可以扩展代码处理
resourceIds块和属性的valueType/valueData字段。 - 如果需要更完整的功能,可以参考Android源码中
libandroidfw库的BinaryXmlParser类,移植更多解析逻辑。
内容的提问来源于stack exchange,提问作者Hamidreza
相关产品推荐
相关产品推荐

