Android 手把手分析resources.arsc

9,035 阅读12分钟

背景

resources.arsc是Android编译后生成的产物,主要是用来建立资源映射关系,为了清晰地理解其中的映射逻辑,有必要剖析resources.arsc的结构。

结构分析

resources.arsc是一个二进制文件,其内部结构的定义在ResourceTypes.h文件中有详细的描述,文件的详细结构图已经有人画好了,这里直接拿来用,其中的后面一部分有一些错误,先忽略:

可以看到整个结构分为几大类:

  • RES_TABLE_TYPE
  • RES_STRING_POOL_TYPE
  • RES_TABLE_PACKAGE_TYPE
  • RES_TABLE_TYPE_SPEC_TYPE
  • RES_TABLE_TYPE_TYPE 

下面详细解析上述结构。

索引结构

索引是指在R.java文件中生的资源ID,如下所示:

public final class R {
    public static final class attr {
    }
    public static final class drawable {
        public static final int icon=0x7f020000;
    }
    public static final class layout {
        public static final int main=0x7f030000;
    }
    public static final class string {
        public static final int app_name=0x7f040001;
        public static final int hello=0x7f040000;
    }
}

其格式为一个8位的16进制:0xPPTTEEEE 释义:

  • PP:Package ID,包的命名空间,取值范围为[0x01, 0x7f],一般第三方应用均为7f,我们在做插件化时为了防止宿主和插件中的资源ID重复就需要对插件中的资源的包命名空间做修改 
  • TT:资源类型,如上所示资源有attr, drawable, layout, string等资源类型,这两位代表资源的类型,这里并不是固定的,是动态生成的
  • EEEE:代表某一类资源在偏移数组中的值 上面分析了资源索引的结构,后面会结合实例来分析

Chunk Header

chunk Header是所有类型块都会有的结构,主要用于描述chunk的结构信息:

struct ResChunk_header
{
    // Type identifier for this chunk.  The meaning of this value depends
    // on the containing chunk.
    uint16_t type;

    // 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).
    uint16_t 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.
    uint32_t size;
};

1.type:资源类型,主要用于区分每个chunk块的类型,定义如下:

enum {
    RES_STRING_POOL_TYPE        = 0x0001,
    RES_TABLE_TYPE              = 0x0002,
    RES_TABLE_PACKAGE_TYPE      = 0x0200,
    RES_TABLE_TYPE_TYPE         = 0x0201,
    RES_TABLE_TYPE_SPEC_TYPE    = 0x0202,
    RES_TABLE_LIBRARY_TYPE      = 0x0203
};

2.headerSize:每个chunk块的header的大小 

3.size:每个chunk块的大小

RES_TABLE_TYPE

RES_TABLE_TYPE描述的是整个resources.arsc的属性:

struct ResTable_header
{
    struct ResChunk_header header;

    // The number of ResTable_package structures.
    uint32_t packageCount;
};

packageCount:resources.arsc中有多少个ResTable_package,一般只有一个

RES_STRING_POOL_TYPE

字符串资源池,主要存储字符串,注意这里的字符串资源池不包括资源类型和资源名,举个例子:

<string name="app_name">Demo</string>

这里只会存储Demo字符串,其中的资源类型string和app_name会在PACKAGE chunk块中,这个后面详细描述。 字符串资源池的结构如下:

struct ResStringPool_header
{
    struct ResChunk_header header;
    
    // Number of strings in this pool (number of uint32_t indices that follow
    // in the data).
    uint32_t stringCount;

    // Number of style span arrays in the pool (number of uint32_t indices
    // follow the string indices).
    uint32_t styleCount;

    // Flags.
    enum {
        // If set, the string index is sorted by the string values (based
        // on strcmp16()).
        SORTED_FLAG = 1<<0,

        // String pool is encoded in UTF-8
        UTF8_FLAG = 1<<8
    };
    uint32_t flags;

    // Index from header of the string data.
    uint32_t stringsStart;

    // Index from header of the style data.
    uint32_t stylesStart;
};

stringCount:chunk中字符串的数量

styleCount:字符串的样式数量

flags:字符串的编码,UTF-8还是UTF-16

stringsStart:字符串数据块在当前块内的偏移,这里主要是读取字符串的时候需要从这里开始读取

stylesStart:字符串样式数据块在当前块内的偏移

这里重点讲一下读取字符串的注意点:

  • 上图中知道当ResStringPool_header读取完后面跟着两个数组,分别是字符串偏移数组和字符串样式偏移数组,数组的类型为int, 数组的大小为header中stringCount和styleCount,当读取完头后就需要填充这两个数组
  • 读取字符串不能直接读完偏移数组跟着后面直接度取,每个字符串的读取首地址为:
int offset = chunkoffset + stringsStart + stringIndex[i]

offset:是每个字符串读取的开始位置

chunkoffset:当前chunk在整个resources.arsc中的起始位置

stringsStart:header中字符串的偏移

stringIndex:上面读取的字符串偏移数组

  • 字符串长度由字符串读取开始位置的两个字节决定,其长度的计算和字符串编码有关,计算长度代码如下:
    int len = data[0];
    if (flags == UTF8_FLAG) {
        if ((data[0] & 0x80) != 0) {
            len  = ((data[0] & 0x7F) << 8) | data[1];
        }
    } else {
        if ((len & 0x8000) != 0) {
            len  = ((len & 0x7FFF) << 16) | data[1];
        }
        len = len * 2;
    }

当字符串长度获取到后直接读取相应长度的字节然后根据编码格式转换成字符串即可

RES_TABLE_PACKAGE_TYPE

RES_TABLE_PACKAGE_TYPE是一个包的概念,这个结构包含了后面的RES_TABLE_TYPE_SPEC_TYPE和RES_TABLE_TYPE_TYPE以及字符串资源池,先看下结构图:

这里只截取其中一小块,其他部分在后面说明。

struct ResTable_package
{
    struct ResChunk_header header;

    // If this is a base package, its ID.  Package IDs start
    // at 1 (corresponding to the value of the package bits in a
    // resource identifier).  0 means this is not a base package.
    uint32_t id;

    // Actual name of this package, \0-terminated.
    uint16_t name[128];

    // Offset to a ResStringPool_header defining the resource
    // type symbol table.  If zero, this package is inheriting from
    // another base package (overriding specific values in it).
    uint32_t typeStrings;

    // Last index into typeStrings that is for public use by others.
    uint32_t lastPublicType;

    // Offset to a ResStringPool_header defining the resource
    // key symbol table.  If zero, this package is inheriting from
    // another base package (overriding specific values in it).
    uint32_t keyStrings;

    // Last index into keyStrings that is for public use by others.
    uint32_t lastPublicKey;0xPPTTEEEE

    uint32_t typeIdOffset;
};

id:package id,一般为7f

name:包名

typeStrings:类型字符串池偏移,这里偏移和上面解释的一样,所谓类型字符串池就是attr,drawable,layout这种类型的字符串池,结构就是上面介绍的字符串资源池

keyStrings:关键字字符串池偏移,这个的字符串池存储的是关键子如R.string.appName中appName就存储在这个字符串池中

其他几个属性我在解析时没有用到,就没太仔细研究

RES_TABLE_TYPE_SPEC_TYPE

RES_TABLE_TYPE_SPEC_TYPE 代表资源类型,Android中资源有attr,drawable,layout等,每一个类型都有这样的一个结构,所以在PACKAGE中有多个,每个RES_TABLE_TYPE_SPEC_TYPE结构后面会跟着RES_TABLE_TYPE_TYPE的数组,如drawable类型有多个尺寸的,所以有多少种尺寸后面就会跟着多少个RES_TABLE_TYPE_TYPE块,如下所示:

现在先看下RES_TABLE_TYPE_SPEC_TYPE的结构:

struct ResTable_typeSpec
{
    struct ResChunk_header header;

    // The type identifier this chunk is holding.  Type IDs start
    // at 1 (corresponding to the value of the type bits in a
    // resource identifier).  0 is invalid.
    uint8_t id;
    
    // Must be 0.
    uint8_t res0;
    // Must be 0.
    uint16_t res1;
    
    // Number of uint32_t entry configuration masks that follow.
    uint32_t entryCount;

    enum : uint32_t {
        // Additional flag indicating an entry is public.
        SPEC_PUBLIC = 0x40000000u,

        // Additional flag indicating an entry is overlayable at runtime.
        // Added in Android-P.
        SPEC_OVERLAYABLE = 0x80000000u,
    };
};

id:资源ID,这里就是上面介绍索引串0xPPTTEEEE中的TT的值 entryCount:这个值不是说后面RES_TABLE_TYPE_TYPE的数量,可以先不用管,基本也用不到,后面RES_TABLE_TYPE_TYPE中再介绍

RES_TABLE_TYPE_TYPE

RES_TABLE_TYPE_TYPE代表资源数据了,先看下结构:

这里有几个点先说一下:

  • 每一个RES_TABLE_TYPE_TYPE里面包含一个ResTable_entry的数组,每一个资源的具体内容
  • 注意上图中有一个ResTable_entry的偏移数组,这个数组就是0xPPTTEEEE中的EEEE
  • 资源分bag和非bag类型,bag类型是值是确定的,非bag类型的值是有多个的,如下是bag类型:
<string name="app_name">Demo</string>

下面是非bag类型:

<resources>
    <attr name="custom_orientation">
        <enum name="custom_vertical" value="100" />
        <enum name="custom_horizontal" value="200" />
    </attr>
</resources>
  • bag类型的资源的结构是ResTableEntry,非bag类型的结构是ResTableMapEntry,里面有多个数据值
  • RES_TABLE_TYPE_TYPE有多个的原因是ResTable_config的原因导致的,如屏幕地区等原因

下面先看下结构:

struct ResTable_type
{
    struct ResChunk_header header;

    enum {
        NO_ENTRY = 0xFFFFFFFF
    };
    
    // The type identifier this chunk is holding.  Type IDs start
    // at 1 (corresponding to the value of the type bits in a
    // resource identifier).  0 is invalid.
    uint8_t id;
    
    enum {
        // If set, the entry is sparse, and encodes both the entry ID and offset into each entry,
        // and a binary search is used to find the key. Only available on platforms >= O.
        // Mark any types that use this with a v26 qualifier to prevent runtime issues on older
        // platforms.
        FLAG_SPARSE = 0x01,
    };
    uint8_t flags;

    // Must be 0.
    uint16_t reserved;
    
    // Number of uint32_t entry indices that follow.
    uint32_t entryCount;

    // Offset from header where ResTable_entry data starts.
    uint32_t entriesStart;

    // Configuration this collection of entries is designed for. This must always be last.
    ResTable_config config;
};

id:type ID ,如果值为NO_ENTRY = 0xFFFFFFFF,则说明没有当前配置类型的 entryCount:后面ResTableEntry的数量 entriesStart:ResTableEntry的偏移,前面已经介绍过偏移的概念了 ResTable_config:配置信息,如语言,屏幕尺寸等,这里先不管 上面header读取结束后就是ResTableEntry的偏移数组了,这个也和上面分析字符串资源池一样,就不详细介绍了。 下面看下ResTableEntry的结构:

struct ResTable_entry
{
    // Number of bytes in this structure.
    uint16_t size;

    enum {
        // If set, this is a complex entry, holding a set of name/value
        // mappings.  It is followed by an array of ResTable_map structures.
        FLAG_COMPLEX = 0x0001,
        // If set, this resource has been declared public, so libraries
        // are allowed to reference it.
        FLAG_PUBLIC = 0x0002,
        // If set, this is a weak resource and may be overriden by strong
        // resources of the same name/type. This is only useful during
        // linking with other resource tables.
        FLAG_WEAK = 0x0004
    };
    uint16_t flags;
    
    // Reference into ResTable_package::keyStrings identifying this entry.
    struct ResStringPool_ref key;
};

size:当前结构的大小

flags:判断当前是bag类型还是非bag类型

ResStringPool_ref:这个结构里面有一个int值,是当前关键字字符串池中的索引,举个例子:R.string.appName,这个int就是appName在关键字字符串池中的索引

ResTable_entry后面跟着ResValue,如果ResTable_entry为bag类型,则后面跟着的是ResValue数组,先看下ResValue的结构:

struct Res_value
{
    // Number of bytes in this structure.
    uint16_t size;

    // Always set to 0.
    uint8_t res0;
        
    // Type of the data value.
    enum : uint8_t {
        // The 'data' is either 0 or 1, specifying this resource is either
        // undefined or empty, respectively.
        TYPE_NULL = 0x00,
        // The 'data' holds a ResTable_ref, a reference to another resource
        // table entry.
        TYPE_REFERENCE = 0x01,
        // The 'data' holds an attribute resource identifier.
        TYPE_ATTRIBUTE = 0x02,
        // The 'data' holds an index into the containing resource table's
        // global value string pool.
        TYPE_STRING = 0x03,
        // The 'data' holds a single-precision floating point number.
        TYPE_FLOAT = 0x04,
        // The 'data' holds a complex number encoding a dimension value,
        // such as "100in".
        TYPE_DIMENSION = 0x05,
        // The 'data' holds a complex number encoding a fraction of a
        // container.
        TYPE_FRACTION = 0x06,
        // The 'data' holds a dynamic ResTable_ref, which needs to be
        // resolved before it can be used like a TYPE_REFERENCE.
        TYPE_DYNAMIC_REFERENCE = 0x07,
        // The 'data' holds an attribute resource identifier, which needs to be resolved
        // before it can be used like a TYPE_ATTRIBUTE.
        TYPE_DYNAMIC_ATTRIBUTE = 0x08,

        // Beginning of integer flavors...
        TYPE_FIRST_INT = 0x10,

        // The 'data' is a raw integer value of the form n..n.
        TYPE_INT_DEC = 0x10,
        // The 'data' is a raw integer value of the form 0xn..n.
        TYPE_INT_HEX = 0x11,
        // The 'data' is either 0 or 1, for input "false" or "true" respectively.
        TYPE_INT_BOOLEAN = 0x12,

        // Beginning of color integer flavors...
        TYPE_FIRST_COLOR_INT = 0x1c,

        // The 'data' is a raw integer value of the form #aarrggbb.
        TYPE_INT_COLOR_ARGB8 = 0x1c,
        // The 'data' is a raw integer value of the form #rrggbb.
        TYPE_INT_COLOR_RGB8 = 0x1d,
        // The 'data' is a raw integer value of the form #argb.
        TYPE_INT_COLOR_ARGB4 = 0x1e,
        // The 'data' is a raw integer value of the form #rgb.
        TYPE_INT_COLOR_RGB4 = 0x1f,

        // ...end of integer flavors.
        TYPE_LAST_COLOR_INT = 0x1f,

        // ...end of integer flavors.
        TYPE_LAST_INT = 0x1f
    };
    uint8_t dataType;
    
    // The data for this item, as interpreted according to dataType.
    typedef uint32_t data_type;
    data_type data;
};

dataType:当前数据的类型,这个类型在上面有定义,这个很重要 data:数据,根据上面的数据类型定,如果类型为string,则当前的值为字符串资源池中的索引

上面就把resources.arsc的结构分析完了,其中有一些细枝末节的地方就没有再深入去看了,不影响对整体的结构的分析。

索引分析实战

根据上面的分析后直接写了一个分析工具,然后根据这个工具解析了一个Demo的resources.arsc,下面根据解析的输入来实战分析一下,先用JADX打开DEMO,找到resources.arsc,点开后如下所示:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <public type="color" name="colorAccent" id="2130771968" />
    <public type="color" name="colorPrimary" id="2130771969" />
    <public type="color" name="colorPrimaryDark" id="2130771970" />
    <public type="drawable" name="$ic_launcher_foreground__0" id="2130837504" />
    <public type="drawable" name="ic_launcher_background" id="2130837505" />
    <public type="drawable" name="ic_launcher_foreground" id="2130837506" />
    <public type="id" name="btn_start_service" id="2130903040" />
    <public type="id" name="btn_uInit" id="2130903041" />
    <public type="layout" name="activity_main" id="2130968576" />
    <public type="layout" name="second" id="2130968577" />
    <public type="mipmap" name="ic_launcher" id="2131034112" />
    <public type="mipmap" name="ic_launcher_round" id="2131034113" />
    <public type="string" name="app_name" id="2131099648" />
</resources>

先分析一下app_name 将id=2131099648转为16进制7f060000,这个拆一下: packageID:7f TT:06 EEEE:0000,下面是写的工具解析的结果,

ResTableHeader{chunkHeader=ResChunkHeader{type=2, headerSize=12, size=4204}, packageCount=1}
ResStringPoolHeader{chunkHeader=ResChunkHeader{type=1, headerSize=28, size=1148}, stringCount=24, styleCount=0, flags=256, stringsStart=124, stylesStart=0}
---------value ==Demo---index:0
---------value ==res/drawable-anydpi-v21/ic_launcher_background.xml---index:1
---------value ==res/drawable-hdpi-v4/ic_launcher_background.png---index:2
---------value ==res/drawable-ldpi-v4/ic_launcher_background.png---index:3
---------value ==res/drawable-mdpi-v4/ic_launcher_background.png---index:4
---------value ==res/drawable-v24/$ic_launcher_foreground__0.xml---index:5
---------value ==res/drawable-v24/ic_launcher_foreground.xml---index:6
---------value ==res/drawable-xhdpi-v4/ic_launcher_background.png---index:7
---------value ==res/drawable-xxhdpi-v4/ic_launcher_background.png---index:8
---------value ==res/drawable-xxxhdpi-v4/ic_launcher_background.png---index:9
---------value ==res/layout/activity_main.xml---index:10
---------value ==res/layout/second.xml---index:11
---------value ==res/mipmap-anydpi-v26/ic_launcher.xml---index:12
---------value ==res/mipmap-anydpi-v26/ic_launcher_round.xml---index:13
---------value ==res/mipmap-hdpi-v4/ic_launcher.png---index:14
---------value ==res/mipmap-hdpi-v4/ic_launcher_round.png---index:15
---------value ==res/mipmap-mdpi-v4/ic_launcher.png---index:16
---------value ==res/mipmap-mdpi-v4/ic_launcher_round.png---index:17
---------value ==res/mipmap-xhdpi-v4/ic_launcher.png---index:18
---------value ==res/mipmap-xhdpi-v4/ic_launcher_round.png---index:19
---------value ==res/mipmap-xxhdpi-v4/ic_launcher.png---index:20
---------value ==res/mipmap-xxhdpi-v4/ic_launcher_round.png---index:21
---------value ==res/mipmap-xxxhdpi-v4/ic_launcher.png---index:22
---------value ==res/mipmap-xxxhdpi-v4/ic_launcher_round.png---index:23
start parse string style
ResTablePackage{chunkHeader=ResChunkHeader{type=512, headerSize=288, size=3044}, id=7f, name=[c,  , o,  , m,  , .,  , e,  , x,  , a,  , m,  , p,  , l,  , e,  , .,  , t,  , e,  , c,  , h,  , a,  , i,  , n,  , h,  , o,  , s,  , t,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ], typeStrings=288, lastPublicType=0, keyStrings=432, lastPublicKey=0, typeIdOffset=0}
ResStringPoolHeader{chunkHeader=ResChunkHeader{type=1, headerSize=28, size=144}, stringCount=6, styleCount=0, flags=0, stringsStart=52, stylesStart=0}
---------value ==color---index:0
---------value ==drawable---index:1
---------value ==id---index:2
---------value ==layout---index:3
---------value ==mipmap---index:4
---------value ==string---index:5
start parse string style
ResStringPoolHeader{chunkHeader=ResChunkHeader{type=1, headerSize=28, size=312}, stringCount=13, styleCount=0, flags=256, stringsStart=80, stylesStart=0}
---------value ==colorAccent---index:0
---------value ==colorPrimary---index:1
---------value ==colorPrimaryDark---index:2
---------value ==$ic_launcher_foreground__0---index:3
---------value ==ic_launcher_background---index:4
---------value ==ic_launcher_foreground---index:5
---------value ==btn_start_service---index:6
---------value ==btn_uInit---index:7
---------value ==activity_main---index:8
---------value ==second---index:9
---------value ==ic_launcher---index:10
---------value ==ic_launcher_round---index:11
---------value ==app_name---index:12
start parse string style
ResTableTypeSpec{chunkHeader=ResChunkHeader{type=514, headerSize=16, size=28}, id=1, res0=0, res1=0, entryCount=3, resTableTypes=[], spec=[0, 0, 0]}
ResTableType{chunkHeader=ResChunkHeader{type=513, headerSize=84, size=144}, id=1, flags=0, reserved=0, entryCount=3, entriesStart=96, entrys=[0, 10, 20], resTableEntries=[ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=0}, resValue=ResValue{size=8, res0=0, dataType=29, data=-2614432}}, ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=1}, resValue=ResValue{size=8, res0=0, dataType=29, data=-16743049}}, ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=2}, resValue=ResValue{size=8, res0=0, dataType=29, data=-16754869}}]}
ResTableTypeSpec{chunkHeader=ResChunkHeader{type=514, headerSize=16, size=28}, id=2, res0=0, res1=0, entryCount=3, resTableTypes=[], spec=[0, 1280, 0]}
ResTableType{chunkHeader=ResChunkHeader{type=513, headerSize=84, size=128}, id=2, flags=0, reserved=0, entryCount=3, entriesStart=96, entrys=[0, ffffffff, 10], resTableEntries=[ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=3}, resValue=ResValue{size=8, res0=0, dataType=3, data=5}}, null, ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=5}, resValue=ResValue{size=8, res0=0, dataType=3, data=6}}]}
ResTableType{chunkHeader=ResChunkHeader{type=513, headerSize=84, size=112}, id=2, flags=0, reserved=0, entryCount=3, entriesStart=96, entrys=[ffffffff, 0, ffffffff], resTableEntries=[null, ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=4}, resValue=ResValue{size=8, res0=0, dataType=3, data=3}}, null]}
ResTableType{chunkHeader=ResChunkHeader{type=513, headerSize=84, size=112}, id=2, flags=0, reserved=0, entryCount=3, entriesStart=96, entrys=[ffffffff, 0, ffffffff], resTableEntries=[null, ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=4}, resValue=ResValue{size=8, res0=0, dataType=3, data=4}}, null]}
ResTableType{chunkHeader=ResChunkHeader{type=513, headerSize=84, size=112}, id=2, flags=0, reserved=0, entryCount=3, entriesStart=96, entrys=[ffffffff, 0, ffffffff], resTableEntries=[null, ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=4}, resValue=ResValue{size=8, res0=0, dataType=3, data=2}}, null]}
ResTableType{chunkHeader=ResChunkHeader{type=513, headerSize=84, size=112}, id=2, flags=0, reserved=0, entryCount=3, entriesStart=96, entrys=[ffffffff, 0, ffffffff], resTableEntries=[null, ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=4}, resValue=ResValue{size=8, res0=0, dataType=3, data=7}}, null]}
ResTableType{chunkHeader=ResChunkHeader{type=513, headerSize=84, size=112}, id=2, flags=0, reserved=0, entryCount=3, entriesStart=96, entrys=[ffffffff, 0, ffffffff], resTableEntries=[null, ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=4}, resValue=ResValue{size=8, res0=0, dataType=3, data=8}}, null]}
ResTableType{chunkHeader=ResChunkHeader{type=513, headerSize=84, size=112}, id=2, flags=0, reserved=0, entryCount=3, entriesStart=96, entrys=[ffffffff, 0, ffffffff], resTableEntries=[null, ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=4}, resValue=ResValue{size=8, res0=0, dataType=3, data=9}}, null]}
ResTableType{chunkHeader=ResChunkHeader{type=513, headerSize=84, size=112}, id=2, flags=0, reserved=0, entryCount=3, entriesStart=96, entrys=[ffffffff, 0, ffffffff], resTableEntries=[null, ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=4}, resValue=ResValue{size=8, res0=0, dataType=3, data=1}}, null]}
ResTableTypeSpec{chunkHeader=ResChunkHeader{type=514, headerSize=16, size=24}, id=3, res0=0, res1=0, entryCount=2, resTableTypes=[], spec=[0, 0]}
ResTableType{chunkHeader=ResChunkHeader{type=513, headerSize=84, size=124}, id=3, flags=0, reserved=0, entryCount=2, entriesStart=92, entrys=[0, 10], resTableEntries=[ResTableEntry{size=8, flags=4, key=ResStringPoolRef{index=6}, resValue=ResValue{size=8, res0=0, dataType=18, data=0}}, ResTableEntry{size=8, flags=4, key=ResStringPoolRef{index=7}, resValue=ResValue{size=8, res0=0, dataType=18, data=0}}]}
ResTableTypeSpec{chunkHeader=ResChunkHeader{type=514, headerSize=16, size=24}, id=4, res0=0, res1=0, entryCount=2, resTableTypes=[], spec=[0, 0]}
ResTableType{chunkHeader=ResChunkHeader{type=513, headerSize=84, size=124}, id=4, flags=0, reserved=0, entryCount=2, entriesStart=92, entrys=[0, 10], resTableEntries=[ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=8}, resValue=ResValue{size=8, res0=0, dataType=3, data=10}}, ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=9}, resValue=ResValue{size=8, res0=0, dataType=3, data=11}}]}
ResTableTypeSpec{chunkHeader=ResChunkHeader{type=514, headerSize=16, size=24}, id=5, res0=0, res1=0, entryCount=2, resTableTypes=[], spec=[1280, 1280]}
ResTableType{chunkHeader=ResChunkHeader{type=513, headerSize=84, size=124}, id=5, flags=0, reserved=0, entryCount=2, entriesStart=92, entrys=[0, 10], resTableEntries=[ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=10}, resValue=ResValue{size=8, res0=0, dataType=3, data=16}}, ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=11}, resValue=ResValue{size=8, res0=0, dataType=3, data=17}}]}
ResTableType{chunkHeader=ResChunkHeader{type=513, headerSize=84, size=124}, id=5, flags=0, reserved=0, entryCount=2, entriesStart=92, entrys=[0, 10], resTableEntries=[ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=10}, resValue=ResValue{size=8, res0=0, dataType=3, data=14}}, ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=11}, resValue=ResValue{size=8, res0=0, dataType=3, data=15}}]}
ResTableType{chunkHeader=ResChunkHeader{type=513, headerSize=84, size=124}, id=5, flags=0, reserved=0, entryCount=2, entriesStart=92, entrys=[0, 10], resTableEntries=[ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=10}, resValue=ResValue{size=8, res0=0, dataType=3, data=18}}, ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=11}, resValue=ResValue{size=8, res0=0, dataType=3, data=19}}]}
ResTableType{chunkHeader=ResChunkHeader{type=513, headerSize=84, size=124}, id=5, flags=0, reserved=0, entryCount=2, entriesStart=92, entrys=[0, 10], resTableEntries=[ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=10}, resValue=ResValue{size=8, res0=0, dataType=3, data=20}}, ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=11}, resValue=ResValue{size=8, res0=0, dataType=3, data=21}}]}
ResTableType{chunkHeader=ResChunkHeader{type=513, headerSize=84, size=124}, id=5, flags=0, reserved=0, entryCount=2, entriesStart=92, entrys=[0, 10], resTableEntries=[ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=10}, resValue=ResValue{size=8, res0=0, dataType=3, data=22}}, ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=11}, resValue=ResValue{size=8, res0=0, dataType=3, data=23}}]}
ResTableType{chunkHeader=ResChunkHeader{type=513, headerSize=84, size=124}, id=5, flags=0, reserved=0, entryCount=2, entriesStart=92, entrys=[0, 10], resTableEntries=[ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=10}, resValue=ResValue{size=8, res0=0, dataType=3, data=12}}, ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=11}, resValue=ResValue{size=8, res0=0, dataType=3, data=13}}]}
ResTableTypeSpec{chunkHeader=ResChunkHeader{type=514, headerSize=16, size=20}, id=6, res0=0, res1=0, entryCount=1, resTableTypes=[], spec=[0]}
ResTableType{chunkHeader=ResChunkHeader{type=513, headerSize=84, size=104}, id=6, flags=0, reserved=0, entryCount=1, entriesStart=88, entrys=[0], resTableEntries=[ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=12}, resValue=ResValue{size=8, res0=0, dataType=3, data=0}}]}

其中ResTablePackage的输出可以看到id=7f

ResTablePackage{chunkHeader=ResChunkHeader{type=512, headerSize=288, size=3044}, id=7f

然后再看下ResTableTypeSpec id=6的输出:

ResTableTypeSpec{chunkHeader=ResChunkHeader{type=514, headerSize=16, size=20}, id=6, res0=0, res1=0, entryCount=1, resTableTypes=[], spec=[0]}
ResTableType{chunkHeader=ResChunkHeader{type=513, headerSize=84, size=104}, id=6, flags=0, reserved=0, entryCount=1, entriesStart=88, entrys=[0], resTableEntries=[ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=12}, resValue=ResValue{size=8, res0=0, dataType=3, data=0}}]}

id=6,先找下资源项的名称,下面是打印的资源项字符串池:

---------value ==color---index:0
---------value ==drawable---index:1
---------value ==id---index:2
---------value ==layout---index:3
---------value ==mipmap---index:4
---------value ==string---index:5

资源项index是从1开始,所以index=6的value是string

上面的偏移数组是entrys=[0],所以EEEE=0000就找到了,就是上面输出里面的ResTableEntry,里面的key的内容是key=ResStringPoolRef{index=12},也就是资源项名称,资源项名称的输出如下:

---------value ==colorAccent---index:0
---------value ==colorPrimary---index:1
---------value ==colorPrimaryDark---index:2
---------value ==$ic_launcher_foreground__0---index:3
---------value ==ic_launcher_background---index:4
---------value ==ic_launcher_foreground---index:5
---------value ==btn_start_service---index:6
---------value ==btn_uInit---index:7
---------value ==activity_main---index:8
---------value ==second---index:9
---------value ==ic_launcher---index:10
---------value ==ic_launcher_round---index:11
---------value ==app_name---index:12

index=12的是app_name,再看下ResValue里面的内容,其中的dataType=3, data=0,前面分析过dataType=3的是字符串,所以data就是字符串池的索引:

---------value ==Demo---index:0
---------value ==res/drawable-anydpi-v21/ic_launcher_background.xml---index:1
---------value ==res/drawable-hdpi-v4/ic_launcher_background.png---index:2
---------value ==res/drawable-ldpi-v4/ic_launcher_background.png---index:3
---------value ==res/drawable-mdpi-v4/ic_launcher_background.png---index:4

index=0,所以value=Demo

所以索引值0x7f060000 对应R.string.appName=Demo,这样就解析出来了

启发

resources.arsc的结构分析完了,这里对我们插件化处理资源,混淆资源缩减安装包都很有启发,其中再简单说下防止反编译的做法,之前使用APKtool反编译会出现找不资源ID的错误,这个原理其实很简单,就是定义一个不使用的资源,如定义一个字符串R.string.test, 然后使用二进制工具打开resources.arse,找个这个字符串的索引,然后修改,由于是不使用,所以不影响APK的正常使用,但是反编译工具在解析的时候找不就报错了。

解析工具地址:github.com/LiweiGogoin…