Skip to content

Instantly share code, notes, and snippets.

@duangsuse
Last active April 13, 2018 03:05
Show Gist options
  • Save duangsuse/3ae94e339eb188fa4ec8a87b6e105331 to your computer and use it in GitHub Desktop.
Save duangsuse/3ae94e339eb188fa4ec8a87b6e105331 to your computer and use it in GitHub Desktop.

一、 简述

AXML 即 Android Binary XML, 是 Android 应用程序 APK 包中保存 xml 文件数据的一种方式, 可以减小 xml 文件的大小.

本文章与我的另一篇文章手把手教你解析 resources.arsc 是相关联的, resources.arsc 文件是 apk 的资源索引文件, 而 xml 文件是 apk 的资源文件, resources.arsc 保存了 xml 中需要用到的资源的索引, 它们之间有非常强的依赖关系, apk在执行的时候缺一不可.

另外大家在用 AXMLPrinter 去解码 AXML 文件时会看见输出中有很多属性的值都是一串数字, 很难明白是什么意思, 实际上这串数字就是资源的索引 id 值, 需要用这个 id 去 resources.arsc 中查找才能得到具体的字符串, 知道这个属性的值是 @string/XXXX 或者 @drawable/XXXX 等等.

在上面的文章中我介绍了 resources.arsc 文件的解析方式, 本文我来介绍怎样来解析 AXML 文件.

二、 AXML 文件格式

AXML 文件的格式与 resources.arsc 的文件格式有点类似, 如果你清楚 resources.arsc 文件格式的话, 那学习 AXML 文件格式就更方便了.

AXML 文件格式所需要的数据结构和 resources.arsc 的数据结构在同一个文件中定义, 都在系统源码的 /frameworks/base/include/androidfw/ResourceType.h 中, 也用到了 chunk 的概念, 文件是以 chunk 为单位划分的, 每个 chunk 都有 chunk header 和 chunk body.

chunk的类型在 ResourceType.h 文件中有定义:

struct ResChunk_header
 {
     enum
     {
         RES_NULL_TYPE               = 0x0000,
         RES_STRING_POOL_TYPE        = 0x0001,
         RES_TABLE_TYPE              = 0x0002,

         // Chunk types in RES_XML_TYPE
         RES_XML_TYPE                = 0x0003,
         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
     };
     // 当前这个 chunk 的类型
     uint16_t type;
     // 当前这个 chunk 的头部大小
     uint16_t headerSize;
     // 当前这个 chunk 的大小
     uint32_t size;
 };

具体格式如下图:

文件格式结构图

可以看到和 resources.arsc 一样, 在文件的头部后面紧接着是一个 string pool, 存放着文件中需要用到的字符串.

后面是一个XMLResourceMap, 它的作用是: This contains a uint32_t array mapping strings in the string pool back to resource identifiers. It is optional.

由于我对应用中使用资源文件不太熟悉, 所以没看明白, 以后明白了再更新, 但这个 chunk 是 optional 的

再往后是一个 namespace, 个人理解和 C 里面的 namespace 一个意思吧, 类型是 RES_XML_START_NAMESPACE_TYPE, namespace 里面是一个个的 element, 类型是 RES_XML_START_ELEMENT_TYPE, 实际上对应着 xml 中的一个个 tag, 每个 element 里面还有 attribute, 每个 element 里面还可以有子 element, 和 xml 文件中的标签结构是对应的, 后面还有结束标签 end element 和 end namespace, 后面会举例具体说明.

每个 tag 被描述为一个 ResXMLTree_node 结构体, 定义如下:

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;
};

三、 举例

举例就和手把手教你解析 resources.arsc 用同一个工程的例子吧, 以工程中的 AndroidManifest.xml 的二进制文件作为分析目标, 其原始的内容如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.cert"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="21" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme
        android:name="myApplication" >
        <activity
            android:name=".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>

        <service android:name="com.example.services.TestService1" android:process="com.example.services.TestService1">
            <intent-filter>
                <action android:name="com.example.services.TestService1" />
            </intent-filter>
        </service>

    </application>  

</manifest>

下面把手把手教你解析 resources.arsc 文件中用到的 /res/values/strings.xmlR.java 对应部分也贴出来:

/res/values/strings.xml 内容:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Cert</string>
    <string name="hello_world">Hello world!</string>
    <string name="action_settings">Settings</string>
</resources>

R.java 内容:

public final class R {
    // snip
    public static final class string {
        // snip
        /**  Description of the choose target button in a ShareActionProvider (share UI). [CHAR LIMIT=NONE] */
        public static final int abc_shareactionprovider_share_with=0x7f0a000c;
        /**  Description of a share target (both in the list of such or the default share button) in a ShareActionProvider (share UI). [CHAR LIMIT=NONE] /
        public static final int abc_shareactionprovider_share_with_application=0x7f0a000b;
        public static final int action_settings=0x7f0a000f;
        public static final int app_name=0x7f0a000d;
        public static final int hello_world=0x7f0a000e;
    }
    // snip
}

四、 解析二进制 AndroidManifest.xml

1. Header Chunk

AXML 文件的头部格式如下:

struct ResXMLTree_header
{
    struct ResChunk_header header;
};

很简单, AXML 文件二进制内容如下:

AXML 二进制

2. String Pool Chunk

字符串池与 resources.arsc 的字符串池的结构什么的是完全一样的, 现实一个 ResStringPool_header 结构体, 然后跟着 n 个字符串偏移数组, 然后是字符串, 这里就不多说了.

3. Resource ID 数组

这个部分不是必须的, 可以没有. 二进制内容如下图:

ResID 数组二进制

可以看到类型是 RES_XML_RESOURCE_MAP_TYPE(0x180), ResChunk_header 后面跟着 11 个四字节的 id 值, 这些 id 值对应 Android 源码中 /frameworks/base/core/res/res/values/public.xml 文件中的值, 看代码段 3 的例子中所有 tag 下用到的属性名在 public.xml 对应的 id 都会出现在这个数组里面.

比如 versionCode、versionName、name、label、icon、theme 这些, 这里可能表明需要用到系统资源文件, 这些 id 是系统资源的 id 值, 但是具体为什么我还不太清楚.

4. Start Namespace Chunk

整个 xml 文件是一个 namespace 的范围, namespace 的描述是以一个 start namespace 开始和一个 end namespace 结束的, 中间有多个 element.

start namespace 这个 chunk 的 header 是 ResXMLTree_node 描述, chunk 的 body 是一个 ResXMLTree_namespaceExt 结构体来描述, 二进制内容如下图:

StartNS Chunk

可以看到 chunk 的类型是 RES_XML_START_NAMESPACE_TYPE(0x0100), 在原始 AndroidManifest.xml 文件中的 line number 是 0x2, ResXMLTree_namespaceExt 结构体定义如下:

struct ResXMLTree_namespaceExt
{
    // The prefix of the namespace.
    struct ResStringPool_ref prefix;

    // The URI of the namespace.
    struct ResStringPool_ref uri;
};

结合上面数据可以看到 prefix 在 string pool 的字符串偏移数组的下标是 0xB, uri 的下标是 0xC, 到 string pool 中去查找得到分别对应字符串 "android" 和 "http://schemas.android.com/apk/res/android", 可以看到与代码段 2 AndroidManifest.xml 原始内容是相对照的.

第二行原始内容: <manifest xmlns:android="http://schemas.android.com/apk/res/android", xmlns 就表示这个 xml 的 namespace 了, 这个 namespace 是 android 官方标准定义的, uri 和 prefix 是固定的.

整个 xml 文件是属于 android=http://schemas.android.com/apk/res/android 这个 namespace 范围内.

5. Start Element Chunk

XML 文件中的每个 tag 在这里被描述为 element, 同样有起始 tag 和结束 tag, 这里对应 start element 和 end element, start element 这个 chunk 的 header 是 ResXMLTree_node 来描述, chunk 的 body 是一个 ResXMLTree_attrExt, 后面跟若干个 ResXMLTree_attribute, 定义如下:

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;
};

ResXMLTree_attrExt 描述了这个 element 的名字, 所属的 namespace 以及这个 element 中包含了几个 attribute 等相关信息.

ResXMLTree_attribute 描述了 attribute 的具体信息.

这里看二进制内容:

binary content pic

chunk 的类型是 RES_XML_START_ELEMENT_TYPE(0x0102), attribute 的个数是 3, offset 是 0x14, 0xFFFFFFFF 表示 null.

后面跟着三个 ResXMLTree_attribute 结构体, 第一个 name 是 0x0, 从 string pool 中查找是 versionCode, Res_value 的类型是 TYPE_FIRST_INT, 值是 0x01, 与代码段 3 中原始内容的 android:versionCode="1" 是对应的.

第二和第三个 ResXMLTree_attribute 就不分析了.

下面找一个与 resources.arsc 有关的 element 分析一下, 二进制内容如下:

与 resources.arsc 有关的二进制 element

这个 element 的 line number 是 0x0C, name 是 0x15 对应 string pool 中 "application", 与代码段 3 中我们的例子对应.

后面有 6 个 ResXMLTree_attribute, 我们看第二个, 内容是: 0C000000 07000000 FFFFFFFF 08000001 0D000A7F, name 是 0x07, 对应 string pool 中 "label",Res_value 的类型是 TYPE_FIRST_INT, 值是 0x7F0A000D, 看过手把手教你解析 resources.arsc 这篇文章的人会发现很眼熟, 这不就是 R.java 中为每个资源分配的 id 值吗?

对了, 系统会根据这个 id 值去 resources.arsc 文件中去找具体的资源内容, 方法这里就不说了, 可以到我的那篇文章中去看.

我们直接到代码段 5 的 R.java 中去找这个 id, 对应的是 app_name, 再看代码段 3 中原始的内容是 <application android:label="@string/app_name", 这样就对应起来了.

也就是说在 APK 中某个 AXML 文件如果引用到其它 AXML 文件的资源时, 都是通过 id 来引用的, 在解析的时候都需要通过 resources.arsc 去索引.

6. End Element Chunk

每个 tag 都有 start element 和 end element 来标识这个 tag 的范围, end element chunk 的 header 是由一个 ResXMLTree_node 来描述, 它的 body 是由一个 ResXMLTree_endElementExt 来描述, 定义如下:

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;
};  

其中 namespace 和 name 都是和 start element 对应的, 这里就不多说了.

7. End Namespace Chunk

End Namespace Chunk 的 header 由 ResXMLTree_node 来描述, chunk 的 body 由 ResXMLTree_namespaceExt 来描述, 同样内容与 Start Namespace Chunk 相对应, 这里我也不再多说了.

五、 总结

大家可以自己写个简单的例子跟着解析一下, 很容易就可以明白 AXML 的格式了, 非常容易.

当然上面我介绍的不太全面, 比如 Resource ID 以及一些结构的字段都没有详细的介绍, 大家可以去 ResourceType.h 里面去看具体的定义, 有一些我也不太明白所以就没敢说, 以免误人子弟.

由于本人才疏学浅, 上述内容如果有错误, 麻烦指正, 谢谢!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment