Android应⽤程序apk内xml⽂件编码解析
Android的应⽤程序apk⽂件内包含了许多xml⽂件。⼤家知道,每⼀个Android应⽤程序中都有⼀个l⽂件。再⽐如,Android的Layout也是可以通过xml描述的。如果直接从apk⽂件中解压出这些xml⽂件,然后⽤⽂本⽂件打开,会发现根本不可读,是⼀堆乱码。
这是因为在apk⽂件中的xml⽂件是经过编码的,要想得到其原始的可读版本,必须要对其进⾏解码。
下⾯,我们来⼤致介绍⼀下Android⽤到的这种xml⽂件编码格式。
Google对xml⽂件主要采⽤的是流式编码⽅式,从编码中任意截取⼀块出来是没⽤的,因为你⽆法知道它对应的上下⽂关系,这个节点的⽗节点是谁,有哪些⼦节点,都⽆法获知。Google在对xml编码前,会预先处理⼀下,提取出⼀些信息,⽐如说对所有xml⽂件中出现的字符串都会分割提取出来。对所有不同类型的信息,都会分块(Chunk)存放。每⼀个块都有⼀个头,其格式定义如下:
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;
};
可以看出来,⾸先的两个字节表⽰该块的类型,接着两个字节表⽰该块头的长度,最后4个字节表⽰该块的整体⼤⼩。⽽块类型有如下定义:
enum {
RES_NULL_TYPE = 0x0000,
RES_STRING_POOL_TYPE = 0x0001,
RES_TABLE_TYPE = 0x0002,
RES_XML_TYPE = 0x0003,
// Chunk types in RES_XML_TYPE
RES_XML_FIRST_CHUNK_TYPE = 0x0100,
RES_XML_START_NAMESPACE_TYPE= 0x0100,
RES_XML_END_NAMESPACE_TYPE = 0x0101,
RES_XML_START_ELEMENT_TYPE = 0x0102,
RES_XML_END_ELEMENT_TYPE = 0x0103,
RES_XML_CDATA_TYPE = 0x0104,
RES_XML_LAST_CHUNK_TYPE = 0x017f,
// This contains a uint32_t array mapping strings in the string
// pool back to resource identifiers. It is optional.
RES_XML_RESOURCE_MAP_TYPE = 0x0180,
// Chunk types in RES_TABLE_TYPE
RES_TABLE_PACKAGE_TYPE = 0x0200,
RES_TABLE_TYPE_TYPE = 0x0201,
RES_TABLE_TYPE_SPEC_TYPE = 0x0202
};
下⾯挑⼏个类型介绍⼀下:
1)RES_XML_FIRST_CHUNK_TYPE和RES_XML_LAST_CHUNK_TYPE分别表明XML元素块类型的最⼩值和最⼤值,只是⽤来检测错误⽤的,没有实际⽤途。
2)RES_STRING_POOL_TYPE表⽰该块保存的是所有xml⽂件中使⽤到的字符串。前⾯提到过了,在对xml编码之前会提取在该xml⽂件中出现的所有字符串,然后全部保存到这个块中。由于在xml⽂件中很多字符串是重复出现的,所以这样做⼀定程度上可以起到压缩的作⽤。字符串块的头⼤⼩固定为28个字节。其块头具体定义如下:
struct ResStringPool_header
{
struct ResChunk_header header;
// Number of strings in this pool (number of uint32_t indices that follow
// in the data).
uint32_t stringCount;
// Number of style span arrays in the pool (number of uint32_t indices
// follow the string indices).
uint32_t styleCount;
// Flags.
enum {
// If set, the string index is sorted by the string values (based
// on strcmp16()).
SORTED_FLAG = 1<<0,
// String pool is encoded in UTF-8
UTF8_FLAG = 1<<8
};
uint32_t flags;
// Index from header of the string data.
uint32_t stringsStart;
// Index from header of the style data.
uint32_t stylesStart;
};
块的最开始是⼀个块头,包含类型、块头长度和块整体长度的信息。然后4个字节指出该字符串块中包含多少个字符串。接下来四个字节表明有多少个样式。再下来的4个字节是⼀个Flag,如果最低位为1表明块中的字符串都是排序后存放的,如果第8位为1表明字符串都是⽤UTF-8编码的。再下来的4个字节,表明具体字符串池存放的位置相对于该字符串块头位置的偏移。最后4个字节,表明样式存放的位置相对于该字符串块头位置的偏移。
紧接着这个块头的是⼀个偏移值数组,每个偏移4字节,表明要查的字符串相对于字符串池的偏移。该数组包含的偏移个数由前⾯字符块头中的stringCount决定。
再下⾯⼜是⼀组偏移值,只不过指向的是样式。如果头中的styleCount值为0,则没有这串信息。
再下⾯就是字符池的位置了,每个字符串依次存放,对于使⽤UTF-16编码的xml⽂件来说,每个字符占⽤两个字节。字符串的开头有两个字节表明字符串的长度,接着就是字符串本⾝,最后⽤0x0000结尾,表明该字符串结束。
再下⾯就是样式池位置,如果样式值为0,则没有这块。
3)RES_XML_TYPE表⽰该块存放了⼀个完整的xml⽂件。事实上所有编码过后的xml⽂件,前两个字节⼀定是0x0003。这种块的头长度固定为8个字节。
4)RES_XML_START_NAMESPACE_TYPE表明开始⼀个名字空间,在这个块之后出现的所有xml元素块都属于这个名字空间。定义如下:
<pre name="code" class="cpp">/**
* Basic XML tree node. A single item in the XML document. Extended info
document有安卓版吗* about the node can be found after header.headerSize.
*/
struct ResXMLTree_node
{
struct ResChunk_header header;
// Line number in original source file at which this element appeared.
uint32_t lineNumber;
// Optional XML comment that was associated with this element; -1 if none.
struct ResStringPool_ref comment;
};
......
/
**
* Extended XML tree node for namespace start/end nodes.
* Appears header.headerSize bytes after a ResXMLTree_node.
*/
struct ResXMLTree_namespaceExt
{
// The prefix of the namespace.
struct ResStringPool_ref prefix;
// The URI of the namespace.
struct ResStringPool_ref uri;
};
⾸先还是⼀个普通的块头,接着的4个字节表⽰的是这个元素在原始xml⽂件中出现在哪⼀⾏,在下⾯的四个字节⽤来指⽰对应该元素的注释的位置,如果没有注释,则为0xFFFFFFFF。
接下来的4个字节指明该名字空间的前缀,最后的4个字节指明该名字空间的URI。
5)RES_XML_END_NAMESPACE_TYPE表明结束⼀个名字空间,在这个块之后出现的所有xml元素块都将不属于这个名字空间。其具体格式和RES_XML_START_NAMESPACE_TYPE⼀样。⼀般⼀个xml⽂档都会以这个块来结尾。
6)RES_XML_RESOURCE_MAP_TYPE表明该块存放的是⼀组资源ID,其定义如下:
enum {
LABEL_ATTR = 0x01010001,
ICON_ATTR = 0x01010002,
NAME_ATTR = 0x01010003,
PERMISSION_ATTR = 0x01010006,
RESOURCE_ATTR = 0x01010025,
DEBUGGABLE_ATTR = 0x0101000f,
VERSION_CODE_ATTR = 0x0101021b,
VERSION_NAME_ATTR = 0x0101021c,
SCREEN_ORIENTATION_ATTR = 0x0101001e,
MIN_SDK_VERSION_ATTR = 0x0101020c,
MAX_SDK_VERSION_ATTR = 0x01010271,
REQ_TOUCH_SCREEN_ATTR = 0x01010227,
REQ_KEYBOARD_TYPE_ATTR = 0x01010228,
REQ_HARD_KEYBOARD_ATTR = 0x01010229,
REQ_NAVIGATION_ATTR = 0x0101022a,
REQ_FIVE_WAY_NAV_ATTR = 0x01010232,
TARGET_SDK_VERSION_ATTR = 0x01010270,
TEST_ONLY_ATTR = 0x01010272,
ANY_DENSITY_ATTR = 0x0101026c,
GL_ES_VERSION_ATTR = 0x01010281,
SMALL_SCREEN_ATTR = 0x01010284,
NORMAL_SCREEN_ATTR = 0x01010285,
LARGE_SCREEN_ATTR = 0x01010286,
XLARGE_SCREEN_ATTR = 0x010102bf,
REQUIRED_ATTR = 0x0101028e,
SCREEN_SIZE_ATTR = 0x010102ca,
SCREEN_DENSITY_ATTR = 0x010102cb,
REQUIRES_SMALLEST_WIDTH_DP_ATTR = 0x01010364,
COMPATIBLE_WIDTH_LIMIT_DP_ATTR = 0x01010365,
LARGEST_WIDTH_LIMIT_DP_ATTR = 0x01010366,
PUBLIC_KEY_ATTR = 0x010103a6,
CATEGORY_ATTR = 0x010103e8,
};
由于这个块是可选的,可有可⽆,就不多做介绍了。
7)RES_XML_START_ELEMENT_TYPE表明⼀个xml元素的开始,这是最复杂的⼀个块
/**
* Extended XML tree node for start tags -- includes attribute
* information.
* Appears header.headerSize bytes after a ResXMLTree_node.
*/
struct ResXMLTree_attrExt
{
// String of the full namespace of this element.
struct ResStringPool_ref ns;
// String name of this node if it is an ELEMENT; the raw
// character data if this is a CDATA node.
struct ResStringPool_ref name;
// Byte offset from the start of this structure where the attributes start.
uint16_t attributeStart;
// Size of the ResXMLTree_attribute structures that follow.
uint16_t attributeSize;
// Number of attributes associated with an ELEMENT. These are
// available as an array of ResXMLTree_attribute structures
// immediately following this node.
uint16_t attributeCount;
// Index (1-based) of the "id" attribute. 0 if none.
uint16_t idIndex;
// Index (1-based) of the "class" attribute. 0 if none.
uint16_t classIndex;
/
/ Index (1-based) of the "style" attribute. 0 if none.
uint16_t styleIndex;
};
struct ResXMLTree_attribute
{
// Namespace of this attribute.
struct ResStringPool_ref ns;
// Name of this attribute.
struct ResStringPool_ref name;
// The original raw string value of this attribute.
struct ResStringPool_ref rawValue;
/
/ Processesd typed value of this attribute.
struct Res_value typedValue;
};
很好理解,先是命名空间的名称;然后是元素的名称;然后是属性结构开始位置的偏移,注意是两个字节;接着是表⽰紧随这个结构后的属性结构的⼤⼩,以字节为单位,也是两个字节;再后⾯表⽰该xml元素包含⼏个属性,也是两个字节;再后⾯的3个两个字节,风别⽤来表⽰该元素中id属性、class属性和style属性的索引,不过是以1为开始的,0表⽰没有。
接着这个结构体后⾯是0到多个属性结构体,到底有⼏个由前⾯结构中的attributeCount来决定。在结构体中,先是属性所属名字空间的名称,然后是属性的名称,然后是属性值的原始字符串。例如,如果有⼀个属性是android:versionName="1.0",则名字空间
是“android”,属性名称是“versionName”,属性值原始字符串是“1.0”。接下来是⼀个新的结构体,表⽰处理过的数据。最开始的两个字节表⽰该结构体的⼤⼩,接着的1个字节⼀定全是0,接着的⼀个字节表⽰处理过后数据的类型,最后的4个字节存储的是实际处理过后的数据,⾄于如何解释,要看前⾯类型指定的是什么。
struct Res_value
{
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论