由于简书不支持页内跳转,所有页内链接不可用,造成不便,请谅解,臣妾也表示很无奈~。如果你有一定的编程基础,且知道什么是xml,什么是axml,你可以点击干货直接进入干货环节,但是我还是建议你听我絮叨一番,毕竟我还是想对你说...
APK是什么?
APK是安卓的可执行程序,它的本质就是一个zip格式的压缩包文件,只是它可以被安卓系统识别、解释和运行。下图是apk内部的目录结构。看起来文件有点多,不过别担心,今天的主角只有xml文件,例如:图中看到的二进制的AndroidManifest.xml,以及res目录下的所有二进制的xml文件。安卓中二进制化的xml简称axml。
XML是什么?
XML是可扩展标记语言,标准通用标记语言的子集,简称XML。是一种用于标记电子文件使其具有结构性的标记语言。以上是百度百科的解释。
xml文件内容如下图所示。
AXML是什么?(Android Binary XML)是安卓项目中、清单文件、布局文件以及其它所有xml资源,经过编译后产生的:二进制化的xml文件。上图的xml文件编译后产生的axml文件内容如下图所示。你是否会觉得:xml看着挺友好的,简单易懂,为什么要大费周折的搞成二进制呢?这太反人类了。我简单讲两句二进制化的好处。
文件尺寸缩减。例如:文件中大量使用到android:属性=“内容”,经过二进制化以后,xml中所有的android字符就只需要一个索引值指向android的命名空间即可,原本需要7个字符才能表示的信息,现在只需要一个索引ID,直接省掉6个字符的空间。何况是全文共用这一个ID,这节省量是相当可观的。当然,其中的属性和内容也是同理。所以这种减过肥的xml看起来会更加性感。
执行效率更高。xml文件对人类而言,结构清晰,逻辑明确。但是对安卓系统来说并非如此。如果安卓直接使用xml,它就需要先解析xml,然后将文字信息一一对应到它所能识别的逻辑上(这种将你懂的事情变成它能懂的事情,是一件耗时费力的过程)。所以说:对人说人话,对机器就要说01话。二进制化所产生的那一系列ID是直接指向安卓所能识别的逻辑上的,所以AXML对程序的执行来说,效率是非常哇塞的。
通过以上讲解,你应该大致明白了什么是XML,什么是AXML,以及为什么安卓要把XML编译成AXML。那么至此,新的问题就产生了:如果我只能拿到一个安卓的APK安装包,我没有源代码。那里面这种只给系统看不给我看的,二进制化的产物,到底会跟我的手机联合起来干些什么勾当?我是不是就无从得知了?万一它要对我做什么羞羞的事?我该怎么办?
您不用担心,本作文就是为了解决这种问题而准备的。万事万物皆有规律可循,XML编译成AXML也同样如此。它并非编译器,对XML风流过后,不负责任的产物。它是有道理可讲,有规律可循的正经事。所以我们就是要顺着它的规律,由着它的道理,一步一步抽丝剥茧,理清AXML的来龙去脉,揪出创造它的元凶XML本身,让XML自己出来接受盘查。
那么,XML到AXML之间到底有什么道理,又有什么规律,我们该从何查起呢?所以这里就有了第一个自问自答环节:到底谁能看懂这AXML?既然这AXML最终是给安卓系统使用的,那当然就是安卓系统明白其中的道理啦,所以这线索就在安卓底层找。安卓底层代码我给您放这里了。您想看的话就点进去吧。里面有安卓的各个版本的各种代码。喜欢侦探游戏的都可以进去探索一番,相信一定会有不小的收获的。
现在有了系统源码,接下来的问题就是该看哪个代码?安卓4.4版本有10个G的源代码。茫茫代码海,该游向何方?所以这里就有了第二个自问自答环节:AXML对安卓来讲到底算个什么东西?既然它不是程序逻辑,那它就是程序资源。所以去寻找资源相关的东西。以上只是给出寻找思路,答案我就直接放在这里,感兴趣的话,可以自己去源码里一探究竟。AXML用到的格式在ResourceTypes.h已有定义。
接下来就是干货时间。首先我直接给出我的分析结果,我已经把我所理解的AXML的组成原理,整理为一张结构图,就是下面这张图了。如果想看我的源码的话,它已经托管至全球最大的同性交友网站,直接点击那两个高亮部分,进去把玩一番吧。接下来我会以此图为主,把一些重要的注意事项讲给你听,希望你会喜欢这篇作文,并从此爱上我。
ResourceTypes.h里提出了一个概念,叫做chunk块。整个AXML文档是由一块(chunk)又一块(chunk)的结构堆叠起来的,为了便于理解,可以把chunk理解为一个盒子。
整个axml是最大的一个盒子(chunk),它里面依次是字符串盒子(String Chunk)、资源盒子(System Resource Chunk)和一个大盒子:命名空间盒子([Start and End] Name Space Chunk)。
字符串盒子(String Chunk)存放的是整个文档用到的字符串信息,文档用到字符串的地方都会拿着索引来这里领取字符串。例如,在安卓源码的android.R$attr.**中可以找到这些索引的身影。
资源盒子(System Resource Chunk)存放的是该文档用到的安卓的系统属性对应的索引。一个文档内可以有多个命名空间并存,例如系统的android空间与自定义控件用到的app空间可以共存。命名空间内部存放的就是整个文档的具体结构信息。
命名空间盒子([Start and End] Name Space Chunk)是整个xml文档的开始,整个文档以Start命名空间为开始,又以End命名空间为结束,所有的文档标签都在它所属的命名空间内展开。命名空间内所存放的结构盒子(chunk)依次是开始标签(Start Tag Chunk)、开始标签所需具体属性信息(Attribute)和结束标签(End Tag Chunk)。开始标签是具体标签的开始,同时里面存放了该标签所需要的所有具体的属性信息。例如,application中的android:theme=“@style/Theme.TestApk”以及android:icon=“@mipmap/ic_launcher”等等所有的属性都在属性信息(Attribute)里存储。这里的icon是属性名,android是该属性所在的命名空间,@mipmap/ic_launcher是该属性的值。需要注意的是,这里存的不是@mipmap/ic_launcher这个字符串,而是它对应到resources.arsc文件中记录的索引值,由于arsc文件不是本作文的主角,所以它没有资格在这篇作文里占太多篇幅。最后是结束标签(End Tag Chunk),所有的开始标签都有一个与之对应的结束标签。
结构体的基本属性,我们分别从以下几个方面进行详细解析:
1. Chunk Type(块类型):表示该数据块的类型,例如:字符串、像素图片、矢量图形等。在axml文件中,每个标签都有一个对应的类型,例如`
2. Header Size(头部大小):表示该数据块头部信息的字节数。头部信息包含了数据块的大小、类型等关键信息。
3. Chunk Size(数据块大小):表示该数据块实际数据的字节数。这个值需要根据头部信息和数据类型来计算得出。
接下来,我们将结合ResourceTypes.h中定义的结构体,深入axml的体内来一探究竟。因为涉及到代码的分析和整理,这里希望你具备一定的编程基础,最好是理解C++数据结构的定义,同时懂一点安卓的正向开发,知道安卓的项目结构。当然你不清楚C++也没关系,我会将C++的结构体转写为java的Object。
代码分析之前有一件非常重要,但是 ResourceTypes.h 没有提到事情,你一定不要忽视,由于安卓系统主流的CPU是arm的架构,所以axml的二进制整篇是以小端编码的方式存储的。小端编码也不是本文的主体,更专业的解释需要你另外补充营养。这里只粗略的解释一下例如下面这两个字节00 0F大端编码它的结果就是00 0F值等于15小端编码需要字节倒序就是0F 00值等于3840结构图第一行也已经注明little endian byte order请不要忽视它的存在上面axml截图中开始的两个字节是03 00但是结构图备注的FileType却是0x0003正是这个原因接下来正文开始ResourceTypes.h开篇的第一个结构体是Res_png_9patch安卓的.9图不是本文所care的东西直接跳过之后的无关成员也会一并跳过header定义通过上面的结构图你或许注意到每个盒子的开始都会有一些共同的属性[Chunk Type Header Size Chunk Size]这几个共同的属性就是该结构体的基本属性我们分别从以下几个方面进行详细解析:
1. Chunk Type(块类型):表示该数据块的类型例如:字符串像素图片矢量图形等在axml文件中每个标签都有一个对应的类型例如
2. Header Size(头部大小):表示该数据块头部信息的字节数头部信息包含了数据块的大小类型等关键信息
3. Chunk Size(数据块大小):表示该数据块实际数据的字节数这个值需要根据头部信息和数据类型来计算得出接下来我们将结合ResourceTypes.h中定义的结构体深入axml的体内来一探究竟因为涉及到代码的分析和整理这里希望你具备一定的编程基础最好是理解C++数据结构的定义同时懂一点安卓的正向开发知道安卓的项目结构当然你不清楚C++也没关系我会将C++的结构体转写为java的Object代码分析之前有一件非常重要但是ResourceTypes.h没有提到事情由于安卓系统主流的CPU是arm的架构所以axml的二进制整篇是以小端编码的方式存储的小端编码也不是本文的主体更专业的解释需要你另外补充营养这里只粗略的解释一下例如下面这两个字节00 0F大端编码它的结果就是00 0F值等于15小端编码需要字节倒序就是0F 00值等于3840结构图第一行也已经注明little endian byte order请不要忽视它的存在上面axml截图中开始的两个字节是03 00但是结构图备注的FileType却是0x0003正是这个原因接下来正文开始ResourceTypes.h开篇的第一个结构体是Res_png_9patch安卓的.9图不是本文所care的东西直接跳过之后的无关成员也会一并跳过header定义通过上面的结构图你或许注意到每个盒子的开始都会有一些共同的属性[Chunk Type Header Size Chunk Size]这几个共同的属性就是该结构体的基本属性我们分别从以下几个方面进行详细解析:
1. Chunk Type(块类型):表示该数据块的类型,例如:字符串、像素图片、矢量图形等。在axml文件中,每个标签都有一个对应的类型,例如
2. Header Size(头部大小):表示该数据块头部信息的字节数。头部信息包含了数据块的大小、类型等关键信息。
3. Chunk Size(数据块大小):表示该数据块实际数据的字节数。这个值需要根据头部信息和数据类型来计算得出。
以下是重构后的内容:
盒子(chunk)的基础头信息(header)包含了对盒子的基本描述和大小等信息的解释。其中,姓甚名谁指的是文件的类型和大小。不同的盒子具有不同的头信息数据量,而头信息的大小则可以通过Header Size来表示。在图中,头信息的部分内容已经用粗体标出,以便与子盒子的信息区别开来。需要注意的是,文件最开始的FileType和FileSize与子盒子的ChunkType和ChunkSize实际上是同一种东西,只是根盒子的size也等同于该文件的size,根盒子的type也同等于该文件的type。因此,为了方便理解和使用,我们将其命名为了FileType和FileSize。
接下来,我们需要查看ResourceTypes.h中对基础头信息的定义。具体代码位于第160行。
以下是将提供的C语言代码重构为Java代码的结果:
```java
/**
* Header that appears at the front of every data chunk in a resource.
*/
class ResChunk_header {
/**
* Type identifier for this chunk. The meaning of this value depends on the containing chunk.
*/
short chunkType;
/**
* 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).
*/
short 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.
*/
int size;
}
```
注意,Java的byte类型对应C语言的uint8_t类型,short类型对应C语言的uint16_t类型,int类型对应C语言的uint32_t类型。因此,我们使用这些对应的数据类型来转换C语言的结构体到Java。
```java
public enum ChunkType {
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;
}
```
以下是重构后的代码:
```java
public enum ChunkType {
CHUNK_STRING(0x0001),
CHUNK_RESOURCE(0x0180),
CHUNK_START_NAMESPACE(0x0100),
CHUNK_END_NAMESPACE(0x0101),
CHUNK_START_TAG(0x0102),
CHUNK_END_TAG(0x0103);
public final int TYPE;
ChunkType(int TYPE) {
this.TYPE = TYPE;
}
}
```
在这段代码中,我们定义了一个名为`ChunkType`的枚举类型,其中包含了6个不同的块类型。每个块类型都有一个与之关联的整数值,表示该类型的ID。接下来,我们将详细介绍这些块类型及其对应的ID。
以下是根据您提供的内容重构的代码:
```cpp
/**
* 定义一个字符串池。此块的数据是一个指向字符串池中字符串的数组,其中每个字符串在字符串start处开始;
* 每个字符串以uint16_t表示其长度,然后以0x0000终止符结束。如果字符串长度大于32767个字符,将设置其高位,
* 表示将其15位作为高字,紧跟在其后的uint16_t包含低字。
*/ struct ResStringPool_header {
/**
* 结构体ResChunk_header的头部。
*/ struct ResChunk_header header;
/**
* 此池中字符串的数量(跟随数据中的uint32_t索引数)。
*/ uint32_t stringCount;
/**
* 此池中样式跨度数组的数量(跟随字符串索引数)。
*/ uint32_t styleCount;
/**
* 标志。
*/ enum {
/**
* 如果设置,则按照基于strcmp16()的字符串值对字符串索引进行排序。
*/ SORTED_FLAG = 1 << 0,
/**
* 字符串池采用UTF-8编码。
*/ UTF8_FLAG = 1 << 8
}; uint32_t flags;
/**
* 从头部索引到字符串数据的索引。
*/ uint32_t stringsStart;
/**
* 从头部索引到样式数据的索引。
*/ uint32_t stylesStart;
};
```
接下来,我们将结合结构图逐一分析StringChunk中的信息。
1. chunkType:其值为0x0001,与上述enum的结果对应。
2. headerSize:为0x001C = 28个字节,表明从chunkType开始包含chunType的前28个字节信息都是stringChunk的头信息,28字节后的信息是该chunk的内容信息。
3. chunkSize:表示整个StringChunk所占的字节大小。
4. StringCount:字符串的数量。
5. StyleCount:测试时发现StyleCount总是为零,个人理解它可能是类似html代码中标签的style属性分段后存储下来的信息,但是安卓的标签不会用到类似style这种单属性里面拥有多个结果的属性配置。
6. isUTF8:注意结构体里的flag是由两部分组成的,其中utf8的属性需要左移8位,证明utf8是在sorted之前出现的。在java中,我们将flag拆解成了isUTF8和isSorted两部分,因为这很重要,接下来会着重解释它的重要性。
7. isSorted:这里指明C++是否对字符串池进行了排序(如果排序会采用strcmp16进行两个字符串的比对),实际观测到的结果是没有排序,并且值总是为0。
8. StringStart:首个字符串开始的位置,注意它不是从整个文件开始计算,而是从该盒子(chunk)的0点坐标开始计算的。
9. StringOffsets:这是一段连续的Index数组,每个index占4字节(对应java的Integer),各个数字表示对应的各个字符串,从StringStart开始的偏移量。
10. StyleOffsets:由于StyleCount总是为0,所以这里也就总是为空,占0字节,不占位置,只占心情。
11. StringPool:从这里开始,才是StringChunk的重点,也是我刚才需要解释isUTF8的重要性的地方。header的注释信息已经提到,字符默认是UTF16的编码,除非isUTF8不为0,才说明这里的字符串需要UTF8编码。这里有好几个坑需要刨一下,接下来展开讲StringPool。
12. StringPool:首先上文已经讲过,axml全文小端编码,所以这里默认的字符编码是UTF_16LE。
在处理二进制文件时,需要注意一个小坑。isUtf8不为0的时候才表示是UTF8编码。之所以说isUtf8不为零即为真,是因为isUtf8这个值是以大端编码记录的。所以,在二进制文件中,我们可以看到的是00 01(数字1)。但是,经过小端编码后,得到的值变成了01 00(数字256)。
在代码中,有这样一行:
```java
//utf8(0x0100) or default utf16le(0x0000) this.isUTF8 = byteBuffer.getShort() != 0;
```
我猜测它的取值只有0或1,所以直接用了“不为零即为真”的判断方式。当你知道为什么要这样做时,就知道了其中的奥妙。
然而,在重新存储该信息时,我们需要先切回大端编码写回,然后再切回小端编码写后续内容。这样做的目的是为了确保在不同平台和系统之间传输数据时,能够正确解析出原始数据。
重构后的内容如下:
```java
@Override
protected void toBytes(ByteArrayOutputStream stream) throws IOException {
stringCount = stringList.size();
ByteBuffer byteBuffer = ByteBuffer.allocate(5 * 4 + stringOffsets.length * 4 + styleOffsets.length * 4);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
byteBuffer.putInt(stringCount);
byteBuffer.putInt(styleCount);
byteBuffer.order(ByteOrder.BIG_ENDIAN);
byteBuffer.putShort((short) (isUTF8 ? 1 : 0));
byteBuffer.putShort((short) (isSorted ? 1 : 0));
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
byteBuffer.putInt(stringStart);
byteBuffer.putInt(styleStart);
}
```
接下来继续讲StringPool里面的内容。StringPool里面是每一条的字符串信息,结构图中Each One (String)已经简单阐明了它包含几层结构。不过这里的每一条信息都是一个坑。开始的CharLength和ByteLength是第一个坑,这两个属性在ResourceTypes.h中没有提及,是我一次又一次遭受程序的毒打后,总结经验并验证出来的。在UTF_16LE的编码下,CharLength和ByteLength整体是一个CharLength,即CharLength直接占两个字节,没有ByteLength属性。个人揣测,这可能是因为UTF_16LE本身已经足够容纳所需的文字信息,不需要额外的。
ByteLength属性在处理字符串时具有重要意义,尤其是在使用不同的字符编码(如UTF_16LE和UTF8)时。UTF_16LE中的每个字符直接占用2个字节,而UTF8中的每个字符仅占用1个字节,当然,UTF8是可变长的。因此,在UTF8编码下,CharLength和ByteLength都有各自的意义。
CharLength表示字符数量,即java的String.length()。ByteLength则表示该字符所占的字节大小,即String.getBytes(StandardCharsets.UTF_8).length。通常情况下,CharLength和ByteLength的值是相同的。然而,在处理中文字符时,由于英文字符只占用一个字节,所以CharLength与ByteLength可能不相等,导致系统崩溃。例如,对于中文字符“测试”,其字节长度为3,而CharLength为4(包括ASCII码为74的't'和ASCII码为105的'i'两个字符)。
在这个例子中,ResourceTypes.h文件并未提及这个坑。因此,在分析StringChunk时,需要注意不同编码下的CharLength和ByteLength之间的差异。
为了巩固学习成果,以下是一些截图示例:
首先是Manifest的源文件:
```diff
isUTF8 = 0
```
这是Manifest的axml文件,二进制下面已经备注了它对应的意义:
```bash
isUTF8 = 0 // UTF16LE编码,每个字符占两个字节,如第一个字符[7400]
```
接下来是layout的源文件:
```diff
isUTF8 = 1
```
这是Layout的axml文件,二进制下面也已经备注上了它对应的意义:
```bash
isUTF8 = 1 // UTF8编码,每个字符占一个字节,如第一个字符[74]
```
实际上,还有一个技巧可以帮助你更直观地查看文字信息。既然我们知道Manifest中的字符是UTF16LE编码,那么我们可以直接以UTF16LE编码的方式打开它就可以看到文字了。同样地,如果我们知道Layout中的字符是UTF8编码,那么我们可以直接以UTF8编码的方式打开它,就可以看到文字了。就像这样:
现在用UTF16LE打开AndroidManifest:
<0x0800> 测试APK1230x0800>
然后用UTF8打开Layout:<0x0000> 测试 AP K T 0x0000>
长度为8的测试APK123:
```
测试APK123
```
在截图中,长度为8,但实际写法是0x08。这是因为UTF-16LE中,每个字符占两个字节,所以这里展示的是它的真实长度。而在UTF-8编码中,中文字符占用三个字节,因此这里的长度是0x0800。
```
测试APK123
0x00
长度8 字节12
测试APK123
结束符
```
TIPS:
- 在UTF-16编码中,字符占用双字节;
- 在UTF-8编码中,字符占用单字节。
- 只有使用双字节的UTF-16编码时,才需要考虑LE(小端)和BE(大端)的区别;
- 只有使用单字节的UTF-8编码时,不需要考虑LE或BE的区别。
关于StringChunk的解释已经完成,如果你想了解更多细节,可以查看我的代码。接下来是SystemResourceIDChunk,它的Type是0x0180。除了基本的Header信息外,它的主体只有一个ResourceIDs。ResourceIDs是一段连续的32位整数数组,记录了整个文档中使用到的系统属性所对应的索引ID。你可以在安卓源码的android.R$attr.**中找到这些索引。
如果你也想运行ResourceChunk.java的代码,只需插入相应的打印语句即可。例如:
```java
// Start NameSpace Chunk & End NameSpace Chunk
// 命名空间由开始和结束两组标签成对出现,除了基本的header信息外,还有4个信息
LineNumber // 该命名空间出现在文档中的第几行
Comment // 该命名空间的备注信息,类似html里的注释信息,所以不用在意它,因为安卓软件编译后会直接丢弃备注信息。
Prefix // 该命名空间中的前缀,即"android"
Uri // 该命名空间的地址,即""
```
最后,还有一个Start NameSpace Chunk & End NameSpace Chunk部分,它是用来表示命名空间的起始和结束位置。命名空间由开始和结束两组标签成对出现,除了基本的header信息外,还有4个信息:LineNumber(命名空间出现在文档中的第几行)、Comment(命名空间的备注信息)、Prefix(命名空间中的前缀,如"android")和Uri(命名空间的地址)。
从这里开始,我们需要注意的是,不管是Prefix还是Uri,甚至之后所有的TagChunk中出现的所有文字信息,存储的都不是具体文本,而是指向StringChunk中StringPool的index索引。假设现在这个Prefix拿到了int值等于5,那么你在StringChunk中StringPool的第6串(即索引为5)的字符串里,会得到内容为"android"的字符串。
接下来,我们来看一下Start Tag Chunk和End Tag Chunk。EndTag是对StartTag标签的关闭,这里只讲述一下StartTag。理解了StartTag,EndTag就很简单了。先来看一下ResourceTypes.h中对TagChunk的结构定义[代码564行]。
以下是重构后的内容:
```cpp
/**
* 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;
};
```
对应的java代码如下:
```java
public class StartTagChunk extends BaseContentChunk {
public final int namespaceUri;
public final int name;
public short attributeStart;
public short attributeSize;
public short attributeCount;
public short idIndex;
public short classIndex;
public short styleIndex;
public List
public static class Attribute {
public final int namespaceUri;
public final int name;
public final int value;
public final short structureSize;
public final byte Res0;
public final byte type;
public final int data;
}
}
```
上文没有讲过的,但是StartTag里独有的属性有:
1. namespaceUri:当前标签所属的命名空间的字符串索引。
2. name:当前标签名的字符串索引。
3. attributeStart:从当前chunk的header之后的偏移量,即Comment之后的多少字节,是属性的开始,固定为20,即从namespaceUri到styleIndex之后刚好是20byte,然后开始属性信息。
4. attributeSize:每个属性的大小,即Each One (attribute) 的所有属性之和的大小=固定值20byte。
5. attributeCount:该TGA标签用到的,属性的数量。
6. idIndex:如果使用了的id属性(例如layout文件中)。
```
在解析Android资源文件时,可以通过以下几个步骤来获取属性的详细信息:
1. 首先,需要找到对应的StartTag,例如:
```
android:id="@+id/text_dashboard" android:layout_width="wrap_content" android:layout_height="wrap_content" /> ``` 在这个例子中,我们需要找到`android:text`属性的详细信息。 2. 获取StartTag的index。在解析过程中,会记录每个StartTag在System ResourceID Chunk中的索引。例如,对于上述例子,`android:id`的index为2,`android:layout_width`和`android:layout_height`的index分别为3和4。 3. 根据index查找对应的System ResourceID Chunk中的int值。例如,对于`android:id`,它的int值为16842960。 4. 在android.R$attr中查找对应的成员。例如,我们可以找到`public static final int id = 16842960;`这一行。 5. 根据classIndex、styleIndex和attributes查找具体的属性信息。例如,我们已经知道classIndex为0,styleIndex为0(因为没有使用),现在需要查找attributes中的详细信息。 6. 在attributes中,有多个attribute配置项。例如: ``` android:id="@+id/text_dashboard" android:layout_width="wrap_content" android:layout_height="wrap_content" /> ``` 这里的attribute配置如下: - namespaceUri:指向StringPool的下标,对应"@+id/text_dashboard"; - name:指向StringPool的下标,对应"android"; - value:指向StringPool的下标,对应";"; - structureSize:表示ResValue的大小; - type:属性值的数据类型; - data:由type推算的数据结果。 7. 如果data不是索引类型,而是直接的字符串,那么使用value属性值去StringPool里取字符串。在这个例子中,value为";",所以从StringPool中取出的内容就是";"。 8. EndTag是对StartTag的结束,Tag可以相互嵌套。一个简单的XML结构示例如下: ``` android:id="@+id/text_dashboard" android:layout_width="wrap_content" android:layout_height="wrap_content" /> ``` 请根据提供的内容完成内容重构,并保持段落结构: ```xml <根标签 xmlns:命名空间="命名空间地址"> <标签 命名空间:属性名="属性值"> <子标签 命名空间:属性名="属性值">...子标签> ... 标签> ... 根标签> ``` 如果还有什么不理解的,可以在评论区提问,虽然我不一定会现身(我很懒的)。但是我还是期待着与你的再次相遇。 如果还想知道更多的技术细节或者想看更多的代码,请直奔全球最大的同性交友网站,我将在那里,用最妖娆的代码再次与你缠绵,拜拜~ 最后别忘了点关注啊!!!你应该不忍心白嫖我吧?关注****关注****关注****关注****关注****关注****关注****关注*****关注。