欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Apk解析之 —— AndroidManifest.xml

程序员文章站 2024-03-24 09:59:52
...

本篇解析AndroidManifest.xml文件,参考文章:Reference
项目源码:ApkParser

Manifest文件结构

Apk解析之 —— AndroidManifest.xml

一. 头部信息

  1. 文件魔数:4bytes 0x00080003
  2. 文件大小:4bytes

二、String Chunk

这个Chunk主要存放的是AndroidManifest文件中所有的字符串信息

字段名 含义 长度
ChunkType StringChunk的类型 固定四个字节:0x001C0001
ChunkSize StringChunk的大小 四个字节
StringCount StringChunk中字符串的个数 四个字节
StyleCount StringChunk中样式的个数,在实际解析过程中,这个值一直是0x00000000 四个字节
Unknown 位置区域,在解析的过程中需要略过 四个字节
StringPoolOffset 字符串池的偏移值,这个偏移值是相对于StringChunk的头部位置 四个字节
StylePoolOffset 样式池的偏移值,这里没有Style,所以这个字段可忽略 四个字节
StringOffsets 每个字符串的偏移值 大小应该是:StringCount*4个字节
SytleOffsets 每个样式的偏移值 大小应该是SytleCount*4个字节

字符串是utf-16的宽字符,每个字符占2个字节,每个字符串块的前两个字节标识字符串的长度,以0x0000结尾,长度不包括尾部的结束符0x0000。

三、ResourceIdChunk

这个Chunk主要是存放的是AndroidManifest中用到的系统属性值对应的资源Id(0x01xxxxxx),比如android:versionCode中的versionCode属性

字段名 含义 长度
ChunkType StringChunk的类型 固定四个字节:0x00080180
ChunkSize StringChunk的大小 四个字节
ResourceIds 资源ID列表 (chunkSize-8)/4 * 4bytes

Package ID相当于是一个命名空间,限定资源的来源,0x01属于系统资源命名空间,0x7f属于应用程序资源命名空间,所有位于[0x01, 0x7f]之间的Package ID都是合法的,而在这个范围之外的都是非法的Package ID。

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

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

系统资源对应id的xml文件所在路径:frameworks\base\core\res\res\values\public.xml,版本越高的Sdk定义的ID项越多。

ID 资源类型type
0x0101xxxx attr
0x0102xxxx id
0x0103xxxx style
0x0104xxxx string
0x0105xxxx dimen
0x0106xxxx color
0x0107xxxx array
0x0108xxxx drawable
0x0109xxxx layout
0x010axxxx anim
0x010bxxxx animator
0x010cxxxx interpolator
0x010dxxxx mipmap
0x010exxxx integer
0x010fxxxx transition
0x0110xxxx raw

四、StartNamespaceChunk

这个Chunk主要包含AndroidManifest文件中的命令空间的内容,Android中的xml都是采用Schema格式的,所以肯定有Prefix和Uri的。(xml格式有两种:DTD和Schema

字段名 含义 长度
ChunkType Chunk的类型 固定四个字节:0x00100100
ChunkSize Chunk的大小 四个字节
LineNumber 在AndroidManifest文件中的行号 四个字节
Unknown 未知区域 四个字节
Prefix 命名空间的前缀(在字符串中的索引值),比如:android 四个字节
Uri 命名空间的uri(在字符串中的索引值):比如:http://schemas.android.com/apk/res/android 四个字节

五、StratTagChunk

这个Chunk主要是存放了AndroidManifest.xml中的标签信息了,也是最核心的内容,当然也是最复杂的内容

字段名 含义 长度
ChunkType Chunk的类型 固定四个字节:0x00100102
ChunkSize Chunk的大小 四个字节
LineNumber 在AndroidManifest文件中的行号 四个字节
Unknown 未知区域 四个字节
NamespaceUri 这个标签用到的命名空间的Uri,比如用到了android这个前缀,那么就需要用http://schemas.android.com/apk/res/android 这个Uri去获取 四个字节
Name 标签名称(在字符串中的索引值) 四个字节
Flags 标签的类型,比如是开始标签还是结束标签等 四个字节
AttributeCount 标签包含的属性个数 四个字节
ClassAtrribute 标签包含的类属性(没什么用) 四个字节
Atrributes 属性内容,每个属性算是一个Entry,这个Entry固定大小是大小为5的字节数组 四个字节

Entry的结构:

字段名 含义 长度
NamespaceUri 属性命名空间的Uri下标 四个字节
Name 属性名称 四个字节
valueString 如果属性是String类型,这个字段保存常量池的下标 四个字节
type 标签类型,需要右移24位再使用 四个字节
data 标签的数据 四个字节

注意Entry.type不同,对应的Entry.data含义也不同,这些类型定义可以在AOSP源码的framework/base/include/androidfw/ResourceTypes.h头文件中找到:

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.

根据Entry.type获取Entry.data的数据:

public static String getAttributeData(AttributeEntry entry, StringChunk stringChunk) {
    String attrData;
    if (entry.type == TYPE_REFERENCE) {
        attrData = String.format("@%s%08x", getPackage(entry.data), entry.data);
    } else if (entry.type == TYPE_ATTRIBUTE) {
        attrData = String.format("?%s%08x", getPackage(entry.data), entry.data);
    } else if (entry.type == TYPE_STRING) {
        attrData = stringChunk.getString(entry.data);
    } else if (entry.type == TYPE_FLOAT) {
        attrData = String.valueOf(Float.intBitsToFloat((int)entry.data));
    } else if (entry.type == TYPE_DIMENSION) {
        attrData = Float.toString(Float.intBitsToFloat((int) entry.data)) + getDimenUnit(entry.data);
    } else if (entry.type == TYPE_FRACTION) {
        attrData = Float.toString(Float.intBitsToFloat((int) entry.data)) + getFractionUnit(entry.data);
    } else if (entry.type == TYPE_DYNAMIC_REFERENCE) {
        attrData = "TYPE_DYNAMIC_REFERENCE";  // To be continue
    } else if (entry.type == TYPE_INT_DEC) {
        attrData = String.format("%d", entry.data);
    } else if (entry.type == TYPE_INT_HEX) {
        attrData = String.format("0x%08x", entry.data);
    } else if (entry.type == TYPE_INT_BOOLEAN) {
        attrData = entry.data == 0 ? "false" : "true";
    } else if (entry.type == TYPE_INT_COLOR_ARGB8) {
        attrData = String.format("#%08x", entry.data);
    } else if (entry.type == TYPE_INT_COLOR_RGB8) {
        attrData = String.format("#ff%06x", 0xffffff & entry.data);
    } else if (entry.type == TYPE_INT_COLOR_ARGB4) {
        attrData = String.format("#%04x", 0xffff & entry.data);
    } else if (entry.type == TYPE_INT_COLOR_RGB4) {
        attrData = String.format("#f%03x", 0x0fff & entry.data);
    } else {
        attrData = String.format("<0x%08x, type 0x%08x>", entry.data, entry.type);
    }

    return attrData;
}

六、EndTagChunk

EndTagChunk的结构和StartTagChunk类似,只是少了

字段名 含义 长度
ChunkType Chunk的类型 固定四个字节:0x00100103
ChunkSize Chunk的大小 四个字节
LineNumber 在AndroidManifest文件中的行号 四个字节
Unknown 未知区域 四个字节
NamespaceUri 这个标签用到的命名空间的Uri,比如用到了android这个前缀,那么就需要用http://schemas.android.com/apk/res/android 这个Uri去获取 四个字节
Name 标签名称(在字符串中的索引值) 四个字节

七、EndNamespaceChunk

EndNamespaceChunk与StartNamespaceChunk对应,结构也完全相同。

字段名 含义 长度
ChunkType Chunk的类型 固定四个字节:0x00100101
ChunkSize Chunk的大小 四个字节
LineNumber 在AndroidManifest文件中的行号 四个字节
Unknown 未知区域 四个字节
Prefix 命名空间的前缀(在字符串中的索引值),比如:android 四个字节
Uri 命名空间的uri(在字符串中的索引值):比如:http://schemas.android.com/apk/res/android 四个字节

八、格式化输出Xml文档

按读入的顺序遍历StratTagChunk和EndTagChunk就可以把文档对象格式化为Xml格式:

/**
 * Convert MfFile object to XML string.
 * @return xml
 */
public String toXmlString() {
    StringBuilder builder = new StringBuilder(4096);
    int depth = 0;
    builder.append("<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>\n");
    for (TagChunk tagChunk : tagChunks) {
        if (tagChunk instanceof StartTagChunk) {
            builder.append(createStartTagXml((StartTagChunk) tagChunk, depth));
            ++depth;
        } else if (tagChunk instanceof EndTagChunk) {
            --depth;
            builder.append(createEndTagXml((EndTagChunk) tagChunk, depth));
        }
    }
    return builder.toString();
}

九、END