返回介绍

5.2 格式解析

发布于 2024-10-10 22:32:15 字数 12882 浏览 0 评论 0 收藏 0

通过前一节的格式分析,大致知道了编译之后的 AndroidManifest.xml 文件格式,本节通过代码案例来详细解析文件中的各个模块。

5.2.1 解析头部信息

任何一个文件格式都会有头部信息,而且头部信息也很重要,同时,头部一般都有固定格式头部信息还有以下这些字段信息:

1)文件魔数:四个字节。

2)文件大小:四个字节。

下面就开始解析所有的字段 Chunk 内容了,其实每个 Chunk 的内容都有一个相似点,就是头部信息:ChunkType(四个字节)和 ChunkSize(四个字节)。

5.2.2 解析 String Chunk

String Chunk 主要用于存放 AndroidManifest 文件中所有的字符串信息,如图 5-3 所示。

说明如下:

·ChunkType:StringChunk 的类型,固定四个字节:0x001C0001。

·ChunkSize:StringChunk 的大小,四个字节。

·StringCount:StringChunk 中字符串的个数,四个字节。

·StyleCount:StringChunk 中样式的个数,四个字节,但是在实际解析过程中,这个值一直是 0x00000000。

·Unknown:位置区域,四个字节,在解析的过程中,这里需要略过四个字节。

·StringPoolOffset:字符串池的偏移值,四个字节,这个偏移值是相对于 StringChunk 的头部位置。

·StylePoolOffset:样式池的偏移值,四个字节,这里没有 Style,所以这个字段可忽略。

·StringOffsets:每个字符串的偏移值,它的大小应该是 StringCount*4 个字节。

·SytleOffsets:每个样式的偏移值,它的大小应该是 SytleCount*4 个字节。

后面就是字符串内容和样式内容了。

图 5-3 String Chunk 结构

下面介绍代码,由于代码的篇幅有点长,所以这里分段说明。后面会给出整个项目代码下载地址的。

1)首先需要把 AndroidManifest.xml 文件读入到一个 byte 数组中:

2)下面来解析头部信息:

按照上面说的格式解析即可:

3)解析 StringChunk 信息:

这里需要解释几点:

1)在上面的格式说明中,有一个 Unknow 字段,四个字节,所以需要略过。

2)在解析字符串内容的时候,字符串内容的结束符是 0x0000。

3)每个字符串开始的前两个字节是字符串的长度。

有了每个字符串的偏移值和大小,那么解析字符串内容就简单了,如下所示:

可以看到 0x000B(高位和低位相反)就是字符串的大小,结尾是 0x0000:

一个字符对应的是两个字节,而且这里有一个方法:

逻辑就是过滤空字符串:在 C 语言中是 NULL,在 Java 语言中就是 00,如果不过滤的话,会出现下面的这种情况:

每个字符是宽字符,很难看,其原因是每个字符后面多了一个 00,所以过滤之后就可以了,如下所示:

上面解析了 AndroidManifest.xml 中所有的字符串内容。这里需要用一个全局的字符列表来存储这些字符串的值,后面会用索引来获取这些字符串的值。

5.2.3 解析 ResourceId Chunk

ResourceId Chunk 主要是用来存放 AndroidManifest 中用到的系统属性值对应的资源 ID,如图 5-4 所示,比如 android:versionCode 中的 versionCode 属性,android 是前缀,后面会说到。

图 5-4 ResourceId Chunk 结构

·ChunkType:ResourceId Chunk 的类型,固定四个字节 0x00080108。

·ChunkSize:ResourceId Chunk 的大小,四个字节。

·ResourceIds:ResourceId 的内容,这里大小是 ResourceId Chunk 大小除以 4,减去头部的大小 8 个字节(ChunkType 和 ChunkSize)。

解析代码如下:

解析结果如下:

这里解析出来的 ID 到底是什么呢?

Android 中的 ID 值

在写 Android 程序的时候,都会发现有一个 R 文件,那里面存放着每个资源对应的 ID,那么这些 ID 值是怎么得到的呢?

Package ID 相当于一个命名空间,限定资源的来源。Android 系统当前定义了两个资源命令空间,其中一个是系统资源命令空间,其 Package ID 等于 0x01;另外一个是应用程序资源命令空间,其 Package ID 等于 0x7f。所有位于[0x01,0x7f]之间的 Package ID 都是合法的,而在这个范围之外的都是非法的。前面提到的系统资源包 package-export.apk 的 Package ID 就等于 0x01,而在应用程序中定义的资源 Package ID 的值都等于 0x7f,这一点可以通过生成的 R.java 文件来验证。

Type ID 是指资源的类型 ID。资源的类型有 animator、anim、color、drawable、layout、menu、raw、string 和 xml 等若干种,每一种都会被赋予一个 ID。

Entry ID 是指每一个资源在其所属的资源类型中所出现的次序。注意,不同类型的资源的 Entry ID 有可能是相同的,但是由于它们的类型不同,仍然可以通过其资源 ID 区别开来。

关于资源 ID 的更多描述,以及资源的引用关系,可以参考 frameworks/base/libs/utils 目录下的 README 文件。

可以得知系统资源对应 ID 的 XML 文件在这里:frameworks\base\core\res\res\values\public.xml,代码如下:

用上面解析到的 ID 去 public.xml 文件中查询一下,得到如下结果:

查到了,是 versionCode,这个系统资源 ID 存放的文件 public.xml 是很重要的,后面在讲解 resource.arsc 文件格式的时候还会用到。

5.2.4 解析 Start Namespace Chunk

这个 Chunk 主要包含一个 AndroidManifest 文件中的命令空间的内容,Android 中的 XML 都是采用 Schema 格式的,所以肯定有 Prefix 和 URI 的,如图 5-5 所示。

图 5-5 Start Namespace Chunk

XML 格式有两种:DTD 和 Schema。

说明如下:

·Chunk Type:Chunk 的类型,固定四个字节 0x00100100。

·Chunk Size:Chunk 的大小,四个字节。

·Line Number:在 AndroidManifest 文件中的行号,四个字节。

·Unknown:未知区域,四个字节。

·Prefix:命名空间的前缀(在字符串中的索引值),比如:android。

·Uri:命名空间的 URI(在字符串中的索引值),比如: http://schemas.android.com/apk/res/android

解析代码如下:

解析的结果如下:

这里的内容就是上面我们解析完 String 之后对应的字符串索引值。这里需要注意的是,一个 XML 中可能会有多个命名空间,所以这里用 Map 存储 Prefix 和 URI 对应的关系,后面在解析节点内容的时候会用到。

5.2.5 解析 Start Tag Chunk

这个 Chunk 主要是存放 AndroidManifest.xml 中的标签信息,是最核心的内容,当然也是最复杂的内容,如图 5-6 所示。

图 5-6 Start Tag Chunk 结构

说明如下:

·Chunk Type:Chunk 的类型,固定四个字节:0x00100102。

·Chunk Size:Chunk 的大小,固定四个字节。

·Line Number:对应于 AndroidManifest 中的行号,四个字节。

·Unknown:未知领域,四个字节。

·Namespace Uri:这个标签用到的命名空间的 URI,比如用到了 android 这个前缀,那么就需要用 http://schemas.android.com/apk/res/android 这个 URI 去获取,四个字节。

·Name:标签名称(在字符串中的索引值),四个字节。

·Flags:标签的类型,四个字节,比如是开始标签还是结束标签等。

·Attribute Count:标签包含的属性个数,四个字节。

·Class Atrribute:标签包含的类属性,四个字节。

·Atrributes:属性内容,每个属性算是一个 Entry,Entry 是大小为 5 的字节数组[Namespace,URI,Name,ValueString,Data],在解析的时候需要注意第四个值,要做一次处理:需要右移 24 位。所以这个字段的大小是:“属性个数×5×4 个字节”。

解析属性代码如下:

可以看到,第四个值需要额外的处理一下,就是需要右移 24 位。解析完属性之后,可以得到一个标签的名称、属性名称、属性值:

看解析的结果:

标签 manifest 包含的属性如下:

这里有几个问题需要解释一下:

1)为什么看到的是三个属性,但是解析打印的结果是 5 个?

因为系统在编译 apk 的时候,会添加两个属性:platformBuildVersionCode 和 platform-BuildVersionName。

这是发布的设备版本号和版本名称:

这个是解析之后的结果。

2)当没有 android 这样的前缀时,NamespaceUri 是 null。

3)当 dataType 不同,对应的 data 值也不同,如下所示:

这个方法就是用来转义的,后面在解析 resource.arsc 的时候也会用到这个方法。

4)每个属性理论上都会含有一个 NamespaceUri,这也决定了属性的前缀 Prefix 默认都是 Android,但是有时候会自定义一个控件,这时候就需要导入 NamespaceUri 和 Prefix 了。所以一个 XML 中可能会有多个 Namespace,每个属性都会包含 NamespaceUri。

到这里就算解析完大部分的工作了,至于 EndTagChunk,与 StartTagChunk 非常类似,这里就不再详解了,下面代码也加了解释:

在解析的时候,需要做一个循环操作:

因为 Android 中在解析 XML 的时候提供了很多种方式,但是这里没有用任何一种方式,而是用纯代码编写的,所以用一个循环来遍历解析 Tag。其实这种方式类似于用 SAX 解析 XML,这时候本节开头提到的 Flag 字段就大有用途了。这里还做了一个工作就是将解析之后的 XML 格式化一下,如下所示:

难度不大,这里也就不继续解释了。有一个地方需要优化,就是可以利用 LineNumber 属性来精确到格式化后文件的行数,不过这个工作量有点大,这里就不做了。有兴趣的同学可以考虑一下,格式化完之后的结果如下:

这里就把之前的 16 进制的内容解析出来了。

还有一个问题,就是看到还有很多 @7F070001 这类的东西,这其实是资源 Id,这需要解析完 resource.arsc 文件之后,才能对应上这个资源,参见下一章。

当发现可以解析 AndroidManifest 文件了,那么同样也可以解析其他的 XML 文件:

这时解析其他 XML 时报错了,定位代码发现是在解析 String Chunk 的地方报错了,修改如下:

因为其他的 XML 中的字符串格式和 AndroidManifest.xml 中的不一样,所以这里需要单独解析一下:

修改之后就可以了。

在反编译的时候,有时候只想反编译 AndroidManifest 内容,所以 ApkTool 工具就有点繁琐了,不过网上有个已经写好了的工具 AXMLPrinter.jar,这个工具很好用:

将 xxx.xml 解析之后输出到 demo.xml 中即可。

提示:AXMLPrinter.jar 工具下载地址:

http://download.csdn.net/detail/jiangwei0910410003/9415323

源代码下载地址:

http://download.csdn.net/detail/jiangwei0910410003/9415342

AXMLPrinter.jar 工具项目见图 5-7。

从项目结构可以发现,它用的是 Android 中自带的 Pull 解析 XML 的,主函数是:

图 5-7 AXMLPrinter 项目

到这里还需要告诉大家一件事,对上面的解析工作有一个更简单的方法,那就是 aapt 命令。关于这个 aapt 是干什么的,网上有很多资料,其实就是将 Android 中的资源文件打包成 resource.arsc 即可,参见图 5-8。

图 5-8 aapt 工具

类型为 res/animator、res/anim、res/color、res/drawable(非 Bitmap 文件,即非.png、.9.png、.jpg、.gif 文件)、res/layout、res/menu、res/values 和 res/xml 的资源文件均会从文本格式的 XML 文件编译成二进制格式的 XML 文件。

要从文本格式编译成二进制格式的原因如下:

·二进制格式的 XML 文件占用空间更小。这是由于所有 XML 元素的标签、属性名称、属性值和内容所涉及的字符串都会被统一收集到一个字符串资源池中去,并且会去重。有了这个字符串资源池,原来使用字符串的地方就会被替换成一个索引到字符串资源池的整数值,从而可以减少文件的大小。

·二进制格式的 XML 文件解析速度更快。这是由于二进制格式的 XML 元素里面不再包含有字符串值,因此就避免了进行字符串解析,从而提高速度。

将 XML 资源文件从文本格式编译成二进制格式,解决了空间占用以及解析效率的问题,但是对于 Android 资源管理框架来说,这只是完成了其中的一部分工作。Android 资源管理框架的另外一个重要任务就是要根据资源 ID 来快速找到对应的资源。

那么下面用 aapt 命令查看一下。aapt 命令在 AndroidSdk 目录中,如图 5-9 所示。

图 5-9 aapt 命令的目录

看到路径了:Android SDK 目录/build-tools/下,这个目录下全是 Android 中构建 apk 的所有工具,再看一下这些工具的用途,参见图 5-10。

说明如下:

·aapt.exe 生成 R.java 类文件。

·aidl.exe 把.aidl 转成.java 文件(如果没有 aidl,则跳过这一步)。

·javac.exe 编译.java 类文件生成 class 文件。

图 5-10 构建 apk 的工具

·dx.bat 命令行脚本生成 classes.dex 文件。

·aapt.exe 生成资源包文件(包括 res、assets、androidmanifest.xml 等)。

·apkbuilder.bat 生成未签名的 apk 安装文件。

·arsigner.exe 对未签名的包进行 apk 签名。

原来可以不借助任何 IDE 工具也是可以打出一个 apk 包的。

继续看 aapt 命令的用法:

将输入的结果定向到 demo.txt 中,如图 5-11 所示。

图 5-11 aapt 命令输出结果

看到解析出来的内容就是上面解析的 AndroidManifest.xml 内容,所以这也是一个解析方法。当然 aapt 命令是系统提供给一个很好的工具,可以在反编译的过程中借助这个工具。这里记得有一个 aapt 命令就好了,它的用途还有很多,可以单独编译成一个 resource.arsc 文件来,后面会用到这个命令。

提示:项目下载地址: https://github.com/fourbrother/parse_androidxml

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文