Android应用程序apk内xml文件编码解析
来源:互联网 发布:连接不上魔法学园网络 编辑:程序博客网 时间:2024/06/09 23:57
Android的应用程序apk文件内包含了许多xml文件。大家知道,每一个Android应用程序中都有一个AndroidManifest.xml文件。再比如,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 * 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{ // Number of bytes in this structure. uint16_t size; // Always set to 0. uint8_t res0; // Type of the data value. enum { // Contains no data. TYPE_NULL = 0x00, // The 'data' holds a ResTable_ref, a reference to another resource // table entry. TYPE_REFERENCE = 0x01, // The 'data' holds an attribute resource identifier. TYPE_ATTRIBUTE = 0x02, // The 'data' holds an index into the containing resource table's // global value string pool. TYPE_STRING = 0x03, // The 'data' holds a single-precision floating point number. TYPE_FLOAT = 0x04, // The 'data' holds a complex number encoding a dimension value, // such as "100in". TYPE_DIMENSION = 0x05, // The 'data' holds a complex number encoding a fraction of a // container. TYPE_FRACTION = 0x06, // Beginning of integer flavors... TYPE_FIRST_INT = 0x10, // The 'data' is a raw integer value of the form n..n. TYPE_INT_DEC = 0x10, // The 'data' is a raw integer value of the form 0xn..n. TYPE_INT_HEX = 0x11, // The 'data' is either 0 or 1, for input "false" or "true" respectively. TYPE_INT_BOOLEAN = 0x12, // Beginning of color integer flavors... TYPE_FIRST_COLOR_INT = 0x1c, // The 'data' is a raw integer value of the form #aarrggbb. TYPE_INT_COLOR_ARGB8 = 0x1c, // The 'data' is a raw integer value of the form #rrggbb. TYPE_INT_COLOR_RGB8 = 0x1d, // The 'data' is a raw integer value of the form #argb. TYPE_INT_COLOR_ARGB4 = 0x1e, // The 'data' is a raw integer value of the form #rgb. TYPE_INT_COLOR_RGB4 = 0x1f, // ...end of integer flavors. TYPE_LAST_COLOR_INT = 0x1f, // ...end of integer flavors. TYPE_LAST_INT = 0x1f }; uint8_t dataType; ...... // The data for this item, as interpreted according to dataType. uint32_t data; ......};
8)RES_XML_END_ELEMENT_TYPE表示一个xml元素的结束,其格式如下:
/** * Extended XML tree node for element start/end nodes. * Appears header.headerSize bytes after a ResXMLTree_node. */struct ResXMLTree_endElementExt{ // 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;};很简单,首先是元素的名字空间,然后是元素的名字。那么,这么多块是如何组织的呢?请看下图:
好了,为了方便理解,我们还是结合一个例子来说明吧。笔者写了一个简单的AndroidManifest.xml,其内容如下:
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.test" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="18" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.example.test.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application></manifest>在将其打包到apk文件中后,得到的二进制AndroidManifest.xml文件内容如下,为了方便解释,一段一段显示:
1)最开始的两个字节是0x0003,表示这个块包含了一个完整的xml文件。接着两个字节是0x0008,表示该块头的长度有8个字节,除去这4个字节,整个头还剩下4个字节。接下来的4个字节0x00000684,表示整个块的大小,也就是该xml文件的大小。
2)接下来的两个字节是0x0001,表示这个块是一个字符串块,其头大小为0x001C个字节,也就是28个字节。接下来4个字节是0x000360,表示该字符串块的长度为864个字节。下面4个字节是0x0000001A,表示共有26个字符串。下面4个字节值是0,表示没有样式。在后面的4个字节是标志位,全0,表明字符串使用UTF-16编码,并且没有排过序。再后面4个字节是0x00000084,表示字符串存放位置的偏移,这个偏移是相对字符串块开头的,如果相对文件开头的话,还要加上8个字节,即0x00000084+8=0x0000008C,刚好可以看到字符串。由于没有样式,所以接下来的4个字节全0。紧接着的28个4字节都是偏移,注意这个偏移是相对于字符串实际存放位置的偏移。举个例子吧,如果想查看字符串块中存放的第二个字符串,则先读出偏移值,本例中是0x0000001A,加上字符串块开始位置0x0000008C,得到地址是0x000000A6,刚好存放的字符串“versionName”。由于没有样式,所以后面接的就是实际的字符串。还是来看刚才举例的字符串,在0x000000A6开始的两个字节是0x0B,表示该字符串由11个字符组成,然后每两个字节表示一个字符,最后有0x0000结尾,表示该字符串终结。
3)接着字符串块的就是所谓的资源ID块,我们接着看:
0x0180表明这是一个资源ID块,该块头长度为8个字节,该块总体长度为0x00000030,即48个字节。紧接着的40个字节,就包含了10个资源ID。第一个是0x0101021B,查前面的定义,可以发现这是android:versionCode属性。后面的都一样我就不解释了。
4)再下面就开始真正表示xml文件的结构了。首先要开始一个名字空间:
0x0100表示开始一个名字空间,其块头长16个字节,块总长24个字节,出现在原始xml文件的第二行,没有注释。名字空间的前缀是字符串池中的第10个字符串,而URI是第11个字符串。我们接着看看第10个字符串是什么,首先找到偏移是0x000000DC,加上开始位置0x0000008C,得到地址是0x00000168,对照着看看这个位置有什么,刚好是字符串“android”的开头。接着的第11个字符串就是“http://schemas.android.com/apk/res/android”。
5)再下面就是xml中的第一个元素了:
这个元素还真不短,我们来逐条分析。0x0102表明这个块表示的是一个元素的开始,块头长16个字节,块的总长度有96个字节,块出现在文档的第二行,没有注释。
下面的就是扩展结构了,0xFFFFFFFF表明该元素没有属于别的名字空间,0x0000000E表示该元素的名字是字符串池中的第14个字符串,即“manifest”。0x0014表示属性的开头距离该扩展结构的开头偏移20个字节,接下来的0x0014表示每个表示属性的结构长20个字节,0x0003表示一共有3个属性,并且紧接着的3个0x00表示没有“id”、“class”和“style”属性。
下面的部分都是用来表示对应于该元素的属性的了。
先来分析第一个属性,最先的4个字节表示的是名字空间,0x0000000B说明是字符串池中的第11个字符,也就是前面介绍过的那个名字空间。不过在xml文档中,不是显示完整的名字空间URI,而是显示名字空间前缀,所以这里对应的就是“android:”。下面的4个字节表示属性的名字,0x00000000表示字符串池中的第0个字符串,也就是“versionCode”。下面的4字节0x00000000表示没有记录初始的属性值字符串。既然没有记原始的,那肯定属性值就在处理过的数据里了。0x0008表示接下来的结构体长8个字节,下面的1个字节一定是0,再下面的一个字节0x10,表示这个属性值是整形,下面的4个字节0x00000001就表示这个整形值是1。所以,可以看出,这个属性是android:versionCode=1。
再来看第二个属性,名字空间一样,就不解释了。下面的0x00000001表示该属性的名字是字符串池中的第1个字符串,也就是“versionName”。下面的0x00000010表示记录了初始的属性值字符串,是字符串池中的第16个字符串,查一下刚好是“1.0”。再下来可以看出处理过后的数据类型是0x03,即字符串类型,而接着的值记得是0x00000010,由于处理过后也当做字符串类型,所以和前面的初始值一样。所以,这个属性是android:versionName="1.0"。
好,接着来看最后一个属性。首先名字空间指定为0xFFFFFFFF(-1),表示没有名字空间前缀。属性名称指定为0x0000000D,表示是字符串池中的第13个字符串,也就是“package”。下面的0x0000000F表示属性值的原始字符串为字符串池中的第15个字符串,即“com.example.test”。而处理过后的属性值仍然为字符串型(0x03)。所以,这个属性为package="com.example.test"。
6)接着,还有一些元素,分析方法和前面一样,我直接跳过,如果有兴趣可以自行分析。我们下面来看一看对应前面开始元素的结束元素是什么样的:
0x0103表明这个块是用来结束一个名字空间的,0x0010表示该块的头有16个字节长,0x00000018表明该块总共有24个字节长,0x0000001B表示这个元素出现在原来xml文件中的第29行,0xFFFFFFFF表示没有注释。再下来的0xFFFFFFFF表示没有自己的名字空间,0x0000000E表示元素名是字符串池Hong的第14个字符串,即“manifest”。
7)文档最后还有一个结束名字空间块,结构基本和结束元素块一样,这里就不多做介绍了。
- Android应用程序apk内xml文件编码解析
- Android APK文件解析
- Android APK文件解析
- PHP解析Android APK包的XML文件
- android 解析gbk编码格式的网络xml文件
- Android APK XML解析与反编译方法
- Android解析XML文件
- android xml文件解析
- Android 解析XML文件
- android解析XML文件
- Android解析XML文件
- Android解析XML文件
- android xml文件解析
- Android解析XML文件
- android XML文件解析
- Android解析XML文件
- android解析xml文件
- Android解析xml文件
- SharePoint 2013跨网站集发布功能简介
- Effective C++笔记:实现
- POJ 1080 Human Gene Functions LCS变形题
- iOS技巧之获取本机通讯录中的内容,解析通讯录源代码
- 第十四周--多态性、虚函数和抽象类(5)
- Android应用程序apk内xml文件编码解析
- 总结篇:数组中N(n=1,2,3)个只出现一次的数字[find N numbers which appear only once in array]
- 【16】C++实现单例模式
- 有依赖的背包问题(背包九讲)
- VC++中操作XMLWin32实例
- Android TextView中文字通过SpannableString来设置超链接、颜色、字体等属性
- Spring IOC--Bean的装配(使用注解定义Bean)
- wait(),notify()和锁
- 数组名和指针的区别