Android 手把手分析resources.arsc

背景

resources.arsc是Android編譯後生成的產物,主要是用來創建資源映射關係,爲了清晰地理解其中的映射邏輯,有必要剖析resources.arsc的結構。java

結構分析

resources.arsc是一個二進制文件,其內部結構的定義在ResourceTypes.h文件中有詳細的描述,文件的詳細結構圖已經有人畫好了,這裏直接拿來用,其中的後面一部分有一些錯誤,先忽略: git

能夠看到整個結構分爲幾大類:

  • RES_TABLE_TYPE
  • RES_STRING_POOL_TYPE
  • RES_TABLE_PACKAGE_TYPE
  • RES_TABLE_TYPE_SPEC_TYPE
  • RES_TABLE_TYPE_TYPE 

下面詳細解析上述結構。github

索引結構

索引是指在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 釋義:bash

  • PP:Package ID,包的命名空間,取值範圍爲[0x01, 0x7f],通常第三方應用均爲7f,咱們在作插件化時爲了防止宿主和插件中的資源ID重複就須要對插件中的資源的包命名空間作修改 
  • TT:資源類型,如上所示資源有attr, drawable, layout, string等資源類型,這兩位表明資源的類型,這裏並非固定的,是動態生成的
  • EEEE:表明某一類資源在偏移數組中的值 上面分析了資源索引的結構,後面會結合實例來分析

Chunk Header

chunk Header是全部類型塊都會有的結構,主要用於描述chunk的結構信息:app

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塊的類型,定義以下:ide

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塊的大小ui

RES_TABLE_TYPE

RES_TABLE_TYPE描述的是整個resources.arsc的屬性: this

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…

相關文章
相關標籤/搜索