前言
在Android中,xml文件经过编译后都是不可读的二进制文件。今天我们来解析一下这个二进制文件的内容,看看如何与我们的源码进行对应。
XML文件结构
在Android中,xml文件经过编译后都是不可读的二进制文件。它的结构如图:
简单说一下大概的结构:
- Header Chunk:8个byte,type+headersize+fileszie
- String Chunk:字符串池
- ResourceId Chunk: 系统的资源id,即0x01xxxxxx这种
- XMLContent Chunk:xml内容的结
其中XMLContent Chunk是主要部分,是一个集合,从图中可以看到它包含多种类型的数据结构,其中Start Namespace Chunk、End Namespace Chunk和Text Chunk这三种类型数据有可能不存在。而Start Tag Chunk和End Tag Chunk一定存在。
Start Tag Chunk和End Tag Chunk就是一个xml tag,如<View />
或<View ></>
,所以它们是成对出现的。
但是要注意不是一个Start Tag Chunk后面一定跟着一个End Tag Chunk,比如嵌套<Layout ><View /></Layout>
,这种情况则是Start Tag Chunk + Start Tag Chunk + End Tag Chunk + End Tag Chunk
。
所以End Tag Chunk中基本没有什么信息,大部分信息都在Start Tag Chunk中,这也是我们这篇文章讨论的重点。
Start Tag Chunk Header
下面是Start Tag Chunk的一个实例:
下面我们一步步解析它。
从上面的图中可以清楚的看到Start Tag Chunk的结构。不过没展示的是Start Tag Chunk包含header和body结构。
Header的结构如下:
struct ResXMLTree_node { struct ResChunk_header header; uint32_t lineNumber; struct ResStringPool_ref comment; }; 复制代码
这里type(2byte)+ headersize(2byte,header大小固定是16byte)+ chunksize(4byte)+ lineNum(4byte)+ comment(4byte)
结合示例来看,其中橙色部分02011000 74000000 00000000 FFFFFFFF便是header。其中:
0201是type;1000是headersize,考虑字节就是16;74000000是chunksize,是116byte,除去header的16byte,body的大小应该是100byte,后面会验证;00000000是lineNum;FFFFFFFF是没有comment的默认值。
Start Tag Chunk Body
header后面就是body,body主要分两部分,第一部分是有关tag的基本信息,第二部分则是Atrributes。
第一部分结构如下:
struct ResXMLTree_attrExt { struct ResStringPool_ref ns; struct ResStringPool_ref name; uint16_t attributeStart; uint16_t attributeSize; uint16_t attributeCount; uint16_t idIndex; uint16_t classIndex; uint16_t styleIndex; }; 复制代码
其中:
- ns: 命名空间,在字符串池中的索引。如果没有就是0xFFFFFFFF。(4byte)
- name: 元素名称,在字符串池中的索引 。(4byte)
- AttributeStart: 属性段的相对body的偏移,由于这部分大小固定,所以这个偏移也是固定的20byte 。(2byte)
- AttributeSize: 每个属性的大小,固定是20byte 。(2byte)
- AttributeCount: 属性的总数 。(2byte)
- idIndex: 第几个属性表示id 。(2byte)
- classIndex: 第几个属性表示class 。(2byte)
- styleIndex: 第几个属性表示style 。(2byte)
结合上面的例子来看,其中绿色部分FFFFFFFF 09000000 14001400 04000100 00000000便是这一部分,其中:
FFFFFFFF表示没有命名空间;09000000表示元素名是字符串池第10个;第一个1400是偏移量,即20byte;第二个1400表示每个属性大小是20byte;0400表示一共有4个属性;0100表示第一个属性是id;00000000表示没有class和style。
这个tag实际是下面的代码:
<ImageView android:id="@+id/image1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/fio" /> 复制代码
可以看到它一共有四个属性,而第一个属性是id。如果我们去字符串池中找,会发现第10字符串是ImageView。
注意:我们可以验证上面的body大小,这部分大小固定是20byte,而每个属性大小是20byte,一共四个,所以body的大小正好是上面说过的100byte。
Atrributes
接下就是Atrributes,Atrributes是一系列Entry结构的集合,每个Entry的结构如下:
struct ResXMLTree_attribute{ struct ResStringPool_ref ns; struct ResStringPool_ref name; struct ResStringPool_ref rawValue; struct Res_value typedValue; }; 复制代码
其中:
- Ns: 属性的命名空间,在字符串池中的索引,比如
xmlns:android="http://schemas.android.com/apk/res/android
这种。(4byte) - Name: 属性的名称,在字符串池中的索引。(4byte)
- rawValue: 属性的值的原始 XML 文字中字符串在 string indices 中的索引。(4byte)
- TypeValue: 是一个表示属性值的结构体。(8byte)
这里的TypeValue的结构如下
struct Res_value { uint16_t size; uint8_t res0; uint8_t dataType; uint32_t data; } 复制代码
其中:
- Size:表示 Res_value 的大小。可以看到这里固定是8byte大小,所以size固定是0x80。(2byte)
- Res0: 始终为 0 (1byte)
- dataType: 值的类型。(1byte)
- data:属性值。(4byte)
其中dataType类型如下:
public static final int TYPE_NULL = 0x00; public static final int TYPE_REFERENCE = 0x01; public static final int TYPE_ATTRIBUTE = 0x02; public static final int TYPE_STRING = 0x03; public static final int TYPE_FLOAT = 0x04; public static final int TYPE_DIMENSION = 0x05; public static final int TYPE_FRACTION = 0x06; public static final int TYPE_DYNAMIC_REFERENCE = 0x07; public static final int TYPE_FIRSTINT = 0x10; // Beginning of integer flavors... public static final int TYPE_INT_DEC = 0x10; // n..n. public static final int TYPE_INT_HEX = 0x11; // 0xn..n. public static final int TYPE_INT_BOOLEAN = 0x12; // 0 or 1, "false" or "true" public static final int TYPE_FIRST_COLOR_INT = 0x1c; // Beginning of color integer flavors... public static final int TYPE_INT_COLOR_ARGB8 = 0x1c; // #aarrggbb. public static final int TYPE_INT_COLOR_RGB8 = 0x1d; // #rrggbb. public static final int TYPE_INT_COLOR_ARGB4 = 0x1e; // #argb. public static final int TYPE_INT_COLOR_RGB4 = 0x1f; // ##rgb. public static final int TYPE_LAST_COLOR_INT = 0x1f; // ..end of integer flavors. public static final int TYPE_LAST_INT = 0x1f; // ...end of integer flavors. 复制代码
结合上面的例子来看,蓝色部分便是Atrributes,我们取其中一个来看,比如:
0D000000 03000000 FFFFFFFF 08000001 3800077F
其中:
0D000000表示字符串池第15个是命名空间;03000000表示字符串池第4个是属性名称;FFFFFFFF表示没有rawValue。
0800表示TypeValue的大小是8byte;00是resId,固定值;01表示属性值是一个资源索引;3800077F就是资源索引,即resId是0x7F070038。
再结合实际代码,这个属性上面代码中的第一个属性,所以是android:id="@+id/image1"。所以它的属性值是@+id/image1,是一个资源索引,在
R.java中
public static final int image1=0x7F070038; 复制代码
可以看到image1正是0x7F070038。
总结
这样Start Tag Chunk结构我们就分析完了,有些细节没有说,比如属性值不同的type的表现形式,大体上是差不多的,大家有兴趣可以自己研究一下。