快過年了,先提早祝賀你們新年快樂,這篇文章也是今年最後一篇了。今天咱們繼續來看逆向的相關知識,前篇文章中咱們介紹瞭如何解析Android中編譯以後的AndroidManifest.xml文件格式:http://blog.csdn.net/jiangwei0910410003/article/details/50568487java
當時我說到其實後續還要繼續介紹兩個文件一個是resource.arsc和classes.dex,今天咱們就來看看resource.arsc文件個格式解析,classes.dex的解析要等年後了。android
咱們在使用apktool工具進行反編譯的時候,會發現有一個:res/values/public.xml這個文件:數組
咱們查看一下public.xml文件內容:微信
看到了,這個文件就保存了apk中全部的類型和對應的id值,咱們看到這裏面的每一個條目內容都是:數據結構
type:類型名app
name:資源名框架
id:資源的idide
類型的話有這麼幾種:工具
drawable,menu,layout,string,attr,color,style等ui
因此咱們會在反編譯以後的文件夾中看到這幾個類型的文件xml內容。
上面咱們介紹瞭如何使用apktool反編譯以後的內容,下面咱們要作的事情就是如何來解析resource.arsc文件,解析出這些文件。
咱們解壓一個apk獲得對應的resource.arsc文件。按照國際慣例,每一個文件的格式描述都是有對應的數據結構的,resource也不例外:frameworks\base\include\androidfw\ResourceTypes.h,這就是resource中定義的全部數據結構。
下面再來看一張神圖:
每次咱們在解析文件的時候都會有一張神圖,咱們按照這張圖來進行數據解析工做。
這個是項目工程結構,咱們看到定義了不少的數據結構
Resources.arsc文件格式是由一系列的chunk構成,每個chunk均包含以下結構的ResChunk_header,用來描述這個chunk的基本信息
package com.wjdiankong.parseresource.type; import com.wjdiankong.parseresource.Utils; /** 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; }; * @author i * */ public class ResChunkHeader { public short type; public short headerSize; public int size; public int getHeaderSize(){ return 2+2+4; } @Override public String toString(){ return "type:"+Utils.bytesToHexString(Utils.int2Byte(type))+",headerSize:"+headerSize+",size:"+size; } }type:是當前這個chunk的類型
headerSize:是當前這個chunk的頭部大小
size:是當前這個chunk的大小
Resources.arsc文件的第一個結構是資源索引表頭部。其結構以下,描述了Resources.arsc文件的大小和資源包數量。
package com.wjdiankong.parseresource.type; /** struct ResTable_header { struct ResChunk_header header; // The number of ResTable_package structures. uint32_t packageCount; }; * @author i * */ public class ResTableHeader { public ResChunkHeader header; public int packageCount; public ResTableHeader(){ header = new ResChunkHeader(); } public int getHeaderSize(){ return header.getHeaderSize() + 4; } @Override public String toString(){ return "header:"+header.toString()+"\n" + "packageCount:"+packageCount; } }
header:就是標準的Chunk頭部信息格式
packageCount:被編譯的資源包的個數
Android中一個apk可能包含多個資源包,默認狀況下都只有一個就是應用的包名所在的資源包
實例:
圖中藍色高亮的部分就是資源索引表頭部。經過解析,咱們能夠獲得以下信息,這個chunk的類型爲RES_TABLE_TYPE,頭部大小爲0XC,整個chunk的大小爲1400252byte,有一個編譯好的資源包。
緊跟着資源索引表頭部的是資源項的值字符串資源池,這個字符串資源池包含了全部的在資源包裏面所定義的資源項的值字符串,字符串資源池頭部的結構以下。
package com.wjdiankong.parseresource.type; /** 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; }; * @author i * */ public class ResStringPoolHeader { public ResChunkHeader header; public int stringCount; public int styleCount; public final static int SORTED_FLAG = 1; public final static int UTF8_FLAG = (1<<8); public int flags; public int stringsStart; public int stylesStart; public ResStringPoolHeader(){ header = new ResChunkHeader(); } public int getHeaderSize(){ return header.getHeaderSize() + 4 + 4 + 4 + 4 + 4; } @Override public String toString(){ return "header:"+header.toString()+"\n" + "stringCount:"+stringCount+",styleCount:"+styleCount+",flags:"+flags+",stringStart:"+stringsStart+",stylesStart:"+stylesStart; } }
header:標準的Chunk頭部信息結構
stringCount:字符串的個數
styleCount:字符串樣式的個數
flags:字符串的屬性,可取值包括0x000(UTF-16),0x001(字符串通過排序)、0X100(UTF-8)和他們的組合值
stringStart:字符串內容塊相對於其頭部的距離
stylesStart:字符串樣式塊相對於其頭部的距離
實例:
圖中綠色高亮的部分就是字符串資源池頭部,經過解析,咱們能夠獲得以下信息,這個chunk的類型爲RES_STRING_POOL_TYPE,即字符串資源池。頭部大小爲0X1C,整個chunk的大小爲369524byte,有8073條字符串,72個字符串樣式,爲UTF-8編碼,無排序,字符串內容塊相對於此chunk頭部的偏移爲0X7F60,字符串樣式塊相對於此chunk頭部的偏移爲0X5A054。
緊接着頭部的的是兩個偏移數組,分別是字符串偏移數組和字符串樣式偏移數組。這兩個偏移數組的大小分別等於stringCount和styleCount的值,而每個元素的類型都是無符號整型。整個字符中資源池結構以下。
字符串資源池中的字符串前兩個字節爲字符串長度,長度計算方法以下。另外若是字符串編碼格式爲UTF-8則字符串以0X00做爲結束符,UTF-16則以0X0000做爲結束符。
len = (((hbyte & 0x7F) << 8)) | lbyte;
字符串與字符串樣式有一一對應的關係,也就是說若是第n個字符串有樣式,則它的樣式描述位於樣式塊的第n個元素。 字符串樣式的結構包括以下兩個結構體,ResStringPool_ref和ResStringPool_span。 一個字符串能夠對應多個ResStringPool_span和一個ResStringPool_ref。ResStringPool_span在前描述字符串的樣式,ResStringPool_ref在後固定值爲0XFFFFFFFF做爲佔位符。樣式塊最後會以兩個值爲0XFFFFFFFF的ResStringPool_ref做爲結束。
package com.wjdiankong.parseresource.type; /** struct ResStringPool_ref { uint32_t index; }; * @author i * */ public class ResStringPoolRef { public int index; public int getSize(){ return 4; } @Override public String toString(){ return "index:"+index; } }
實例:
圖中藍色高亮的部分就是樣式內容塊,按照格式解析能夠得出,第一個字符串和第二字符串無樣式,第三個字符串第4個字符到第7個字符的位置樣式爲字符串資源池中0X1F88的字符,以此類推。
接着資源項的值字符串資源池後面的部分就是Package數據塊,這個數據塊記錄編譯包的元數據,頭部結構以下:
package com.wjdiankong.parseresource.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. char16_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; }; * @author i * */ public class ResTablePackage { public ResChunkHeader header; public int id; public char[] name = new char[128]; public int typeStrings; public int lastPublicType; public int keyStrings; public int lastPublicKey; public ResTablePackage(){ header = new ResChunkHeader(); } @Override public String toString(){ return "header:"+header.toString()+"\n"+",id="+id+",name:"+name.toString()+",typeStrings:"+typeStrings+",lastPublicType:"+lastPublicType+",keyStrings:"+keyStrings+",lastPublicKey:"+lastPublicKey; } }header:Chunk的頭部信息數據結構
id:包的ID,等於Package Id,通常用戶包的值Package Id爲0X7F,系統資源包的Package Id爲0X01;這個值很重要的,在後面咱們構建前面說到的那個public.xml中的id值的時候須要用到。
name:包名
typeString:類型字符串資源池相對頭部的偏移
lastPublicType:最後一個導出的Public類型字符串在類型字符串資源池中的索引,目前這個值設置爲類型字符串資源池的元素個數。在解析的過程當中沒發現他的用途
keyStrings:資源項名稱字符串相對頭部的偏移
lastPublicKey:最後一個導出的Public資源項名稱字符串在資源項名稱字符串資源池中的索引,目前這個值設置爲資源項名稱字符串資源池的元素個數。在解析的過程當中沒發現他的用途
實例:
圖中紫色高亮的部分就是ResTable_package,按照上面的格式解析數據,咱們能夠得出,此Chunk的Type爲RES_TABLE_PACKAGE_TYPE,頭部大小爲0X120,整個chunk的大小爲1030716byte,Package Id爲0X7F,包名稱爲co.runner.app,類型字符串資源池距離頭部的偏移是0X120,有15條字符串,資源項名稱字符串資源池0X1EC,有6249條字符串。
Packege數據塊的總體結構,能夠用如下的示意圖表示:
其中Type String Pool和Key String Pool是兩個字符串資源池,結構和資源項的值字符串資源池結構相同,分別對應類型字符串資源池和資源項名稱字符串資源池。
再接下來的結構體多是類型規範數據塊或者類型資源項數據塊,咱們能夠經過他們的Type來識別,類型規範數據塊的Type爲RES_TABLE_TYPE_SPEC_TYPE,類型資源項數據塊的Type爲RES_TABLE_TYPE_TYPE。
類型規範數據塊用來描述資源項的配置差別性。經過這個差別性描述,咱們就能夠知道每個資源項的配置情況。知道了一個資源項的配置情況以後,Android資源管理框架在檢測到設備的配置信息發生變化以後,就能夠知道是否須要從新加載該資源項。類型規範數據塊是按照類型來組織的,也就是說,每一種類型都對應有一個類型規範數據塊。其數據塊頭部結構以下。
package com.wjdiankong.parseresource.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 { // Additional flag indicating an entry is public. SPEC_PUBLIC = 0x40000000 }; }; * @author i * */ public class ResTableTypeSpec { public final static int SPEC_PUBLIC = 0x40000000; public ResChunkHeader header; public byte id; public byte res0; public short res1; public int entryCount; public ResTableTypeSpec(){ header = new ResChunkHeader(); } @Override public String toString(){ return "header:"+header.toString()+",id:"+id+",res0:"+res0+",res1:"+res1+",entryCount:"+entryCount; } }header:Chunk的頭部信息結構
id:標識資源的Type ID,Type ID是指資源的類型ID。資源的類型有animator、anim、color、drawable、layout、menu、raw、string和xml等等若干種,每一種都會被賦予一個ID。
res0:保留,始終爲0
res1:保留,始終爲0
entryCount:等於本類型的資源項個數,指名稱相同的資源項的個數。
實例:
圖中綠色高亮的部分就是ResTable_typeSpec,按照上面的格式解析數據,咱們能夠得出,此Chunk的Type爲RES_TABLE_TYPE_SPEC_TYPE,頭部大小爲0X10,整個chunk的大小爲564byte,資源ID爲1,本類型資源項數量爲137。
ResTable_typeSpec後面緊跟着的是一個大小爲entryCount的uint32_t數組,每個數組元素都用來描述一個資源項的配置差別性的。
類型資源項數據塊用來描述資源項的具體信息, 這樣咱們就能夠知道每個資源項的名稱、值和配置等信息。 類型資源項數據一樣是按照類型和配置來組織的,也就是說,一個具備n個配置的類型一共對應有n個類型資源項數據塊。其數據塊頭部結構以下
package com.wjdiankong.parseresource.type; /** 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; // Must be 0. uint8_t res0; // Must be 0. uint16_t res1; // 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. ResTable_config config; }; * @author i * */ public class ResTableType { public ResChunkHeader header; public final static int NO_ENTRY = 0xFFFFFFFF; public byte id; public byte res0; public short res1; public int entryCount; public int entriesStart; public ResTableConfig resConfig; public ResTableType(){ header = new ResChunkHeader(); resConfig = new ResTableConfig(); } public int getSize(){ return header.getHeaderSize() + 1 + 1 + 2 + 4 + 4; } @Override public String toString(){ return "header:"+header.toString()+",id:"+id+",res0:"+res0+",res1:"+res1+",entryCount:"+entryCount+",entriesStart:"+entriesStart; } }header:Chunk的頭部信息結構
id:標識資源的Type ID
res0:保留,始終爲0
res1:保留,始終爲0
entryCount:等於本類型的資源項個數,指名稱相同的資源項的個數。
entriesStart:等於資源項數據塊相對頭部的偏移值。
resConfig:指向一個ResTable_config,用來描述配置信息,地區,語言,分辨率等
實例:
圖中紅色高亮的部分就是ResTable_type,按照上面的格式解析數據,咱們能夠得出,RES_TABLE_TYPE_TYPE,頭部大小爲0X44,整個chunk的大小爲4086byte,資源ID爲1,本類型資源項數量爲137,資源數據塊相對於頭部的偏移爲0X268。
ResTable_type後接着是一個大小爲entryCount的uint32_t數組,每個數組元素都用來描述一個資源項數據塊的偏移位置。 緊跟在這個偏移數組後面的是一個大小爲entryCount的ResTable_entry數組,每個數組元素都用來描述一個資源項的具體信息。ResTable_entry的結構以下:
package com.wjdiankong.parseresource.type; import com.wjdiankong.parseresource.ParseResourceUtils; /** 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 }; uint16_t flags; // Reference into ResTable_package::keyStrings identifying this entry. struct ResStringPool_ref key; }; * @author i * */ public class ResTableEntry { public final static int FLAG_COMPLEX = 0x0001; public final static int FLAG_PUBLIC = 0x0002; public short size; public short flags; public ResStringPoolRef key; public ResTableEntry(){ key = new ResStringPoolRef(); } public int getSize(){ return 2+2+key.getSize(); } @Override public String toString(){ return "size:"+size+",flags:"+flags+",key:"+key.toString()+",str:"+ParseResourceUtils.getKeyString(key.index); } }ResTable_entry根據flags的不一樣,後面跟隨的數據也不相同,若是flags此位爲1,則ResTable_entry是ResTable_map_entry,ResTable_map_entry繼承自ResTable_entry,其結構以下。
package com.wjdiankong.parseresource.type; /** struct ResTable_map_entry : public ResTable_entry { //指向父ResTable_map_entry的資源ID,若是沒有父ResTable_map_entry,則等於0。 ResTable_ref parent; //等於後面ResTable_map的數量 uint32_t count; }; * @author i * */ public class ResTableMapEntry extends ResTableEntry{ public ResTableRef parent; public int count; public ResTableMapEntry(){ parent = new ResTableRef(); } @Override public int getSize(){ return super.getSize() + parent.getSize() + 4; } @Override public String toString(){ return super.toString() + ",parent:"+parent.toString()+",count:"+count; } }ResTable_map_entry其後跟隨則count個ResTable_map類型的數組,ResTable_map的結構以下:
package com.wjdiankong.parseresource.type; /** struct ResTable_map { //bag資源項ID ResTable_ref name; //bag資源項值 Res_value value; }; * @author i * */ public class ResTableMap { public ResTableRef name; public ResValue value; public ResTableMap(){ name = new ResTableRef(); value = new ResValue(); } public int getSize(){ return name.getSize() + value.getSize(); } @Override public String toString(){ return name.toString()+",value:"+value.toString(); } }
實例:
圖中顏色由深到淺就是一個完整的flags爲1的資源項,如今就一塊兒來解讀這段數據的含義,這個資源項頭部的大小爲0X10,flags爲1因此後面跟隨的是ResTable_map數組,名稱沒有在資源項引用池中,沒有父map_entry,有一個ResTable_map。
若是flags此位爲0,則ResTable_entry其後跟隨的是一個Res_value,描述一個普通資源的值,Res_value結構以下。
package com.wjdiankong.parseresource.type; import com.wjdiankong.parseresource.ParseResourceUtils; /** struct Res_value { //Res_value頭部大小 uint16_t size; //保留,始終爲0 uint8_t res0; enum { TYPE_NULL = 0x00, TYPE_REFERENCE = 0x01, TYPE_ATTRIBUTE = 0x02, TYPE_STRING = 0x03, TYPE_FLOAT = 0x04, TYPE_DIMENSION = 0x05, TYPE_FRACTION = 0x06, TYPE_FIRST_INT = 0x10, TYPE_INT_DEC = 0x10, TYPE_INT_HEX = 0x11, TYPE_INT_BOOLEAN = 0x12, TYPE_FIRST_COLOR_INT = 0x1c, TYPE_INT_COLOR_ARGB8 = 0x1c, TYPE_INT_COLOR_ARGB8 = 0x1c, TYPE_INT_COLOR_RGB8 = 0x1d, TYPE_INT_COLOR_ARGB4 = 0x1e, TYPE_INT_COLOR_RGB4 = 0x1f, TYPE_LAST_COLOR_INT = 0x1f, TYPE_LAST_INT = 0x1f }; //數據的類型,能夠從上面的枚舉類型中獲取 uint8_t dataType; //數據對應的索引 uint32_t data; }; * @author i * */ public class ResValue { //dataType字段使用的常量 public final static int TYPE_NULL = 0x00; public final static int TYPE_REFERENCE = 0x01; public final static int TYPE_ATTRIBUTE = 0x02; public final static int TYPE_STRING = 0x03; public final static int TYPE_FLOAT = 0x04; public final static int TYPE_DIMENSION = 0x05; public final static int TYPE_FRACTION = 0x06; public final static int TYPE_FIRST_INT = 0x10; public final static int TYPE_INT_DEC = 0x10; public final static int TYPE_INT_HEX = 0x11; public final static int TYPE_INT_BOOLEAN = 0x12; public final static int TYPE_FIRST_COLOR_INT = 0x1c; public final static int TYPE_INT_COLOR_ARGB8 = 0x1c; public final static int TYPE_INT_COLOR_RGB8 = 0x1d; public final static int TYPE_INT_COLOR_ARGB4 = 0x1e; public final static int TYPE_INT_COLOR_RGB4 = 0x1f; public final static int TYPE_LAST_COLOR_INT = 0x1f; public final static int TYPE_LAST_INT = 0x1f; public static final int COMPLEX_UNIT_PX =0, COMPLEX_UNIT_DIP =1, COMPLEX_UNIT_SP =2, COMPLEX_UNIT_PT =3, COMPLEX_UNIT_IN =4, COMPLEX_UNIT_MM =5, COMPLEX_UNIT_SHIFT =0, COMPLEX_UNIT_MASK =15, COMPLEX_UNIT_FRACTION =0, COMPLEX_UNIT_FRACTION_PARENT=1, COMPLEX_RADIX_23p0 =0, COMPLEX_RADIX_16p7 =1, COMPLEX_RADIX_8p15 =2, COMPLEX_RADIX_0p23 =3, COMPLEX_RADIX_SHIFT =4, COMPLEX_RADIX_MASK =3, COMPLEX_MANTISSA_SHIFT =8, COMPLEX_MANTISSA_MASK =0xFFFFFF; public short size; public byte res0; public byte dataType; public int data; public int getSize(){ return 2 + 1 + 1 + 4; } public String getTypeStr(){ switch(dataType){ case TYPE_NULL: return "TYPE_NULL"; case TYPE_REFERENCE: return "TYPE_REFERENCE"; case TYPE_ATTRIBUTE: return "TYPE_ATTRIBUTE"; case TYPE_STRING: return "TYPE_STRING"; case TYPE_FLOAT: return "TYPE_FLOAT"; case TYPE_DIMENSION: return "TYPE_DIMENSION"; case TYPE_FRACTION: return "TYPE_FRACTION"; case TYPE_FIRST_INT: return "TYPE_FIRST_INT"; case TYPE_INT_HEX: return "TYPE_INT_HEX"; case TYPE_INT_BOOLEAN: return "TYPE_INT_BOOLEAN"; case TYPE_FIRST_COLOR_INT: return "TYPE_FIRST_COLOR_INT"; case TYPE_INT_COLOR_RGB8: return "TYPE_INT_COLOR_RGB8"; case TYPE_INT_COLOR_ARGB4: return "TYPE_INT_COLOR_ARGB4"; case TYPE_INT_COLOR_RGB4: return "TYPE_INT_COLOR_RGB4"; } return ""; } /*public String getDataStr(){ if(dataType == TYPE_STRING){ return ParseResourceUtils.getResString(data); }else if(dataType == TYPE_FIRST_COLOR_INT){ return Utils.bytesToHexString(Utils.int2Byte(data)); }else if(dataType == TYPE_INT_BOOLEAN){ return data==0 ? "false" : "true"; } return data+""; }*/ public String getDataStr() { if (dataType == TYPE_STRING) { return ParseResourceUtils.getResString(data); } if (dataType == TYPE_ATTRIBUTE) { return String.format("?%s%08X",getPackage(data),data); } if (dataType == TYPE_REFERENCE) { return String.format("@%s%08X",getPackage(data),data); } if (dataType == TYPE_FLOAT) { return String.valueOf(Float.intBitsToFloat(data)); } if (dataType == TYPE_INT_HEX) { return String.format("0x%08X",data); } if (dataType == TYPE_INT_BOOLEAN) { return data!=0?"true":"false"; } if (dataType == TYPE_DIMENSION) { return Float.toString(complexToFloat(data))+ DIMENSION_UNITS[data & COMPLEX_UNIT_MASK]; } if (dataType == TYPE_FRACTION) { return Float.toString(complexToFloat(data))+ FRACTION_UNITS[data & COMPLEX_UNIT_MASK]; } if (dataType >= TYPE_FIRST_COLOR_INT && dataType <= TYPE_LAST_COLOR_INT) { return String.format("#%08X",data); } if (dataType >= TYPE_FIRST_INT && dataType <= TYPE_LAST_INT) { return String.valueOf(data); } return String.format("<0x%X, type 0x%02X>",data, dataType); } private static String getPackage(int id) { if (id>>>24==1) { return "android:"; } return ""; } public static float complexToFloat(int complex) { return (float)(complex & 0xFFFFFF00)*RADIX_MULTS[(complex>>4) & 3]; } private static final float RADIX_MULTS[]={ 0.00390625F,3.051758E-005F,1.192093E-007F,4.656613E-010F }; private static final String DIMENSION_UNITS[]={ "px","dip","sp","pt","in","mm","","" }; private static final String FRACTION_UNITS[]={ "%","%p","","","","","","" }; @Override public String toString(){ return "size:"+size+",res0:"+res0+",dataType:"+getTypeStr()+",data:"+getDataStr(); } }
size:ResValue的頭部大小
res0:保留,始終爲0
dataType:數據的類型,能夠從上面的枚舉類型中獲取
data:數據對應的索引
這裏咱們看到了有一個轉化的方法,這個咱們在解析AndroidManifest文件的時候也用到了這個方法。
圖中畫紅線的部分就是一個ResTable_entry其後跟隨的是一個Res_value的例子,從中咱們能夠得出如下信息,這個頭部大小爲8,flags等於0,因此後面跟隨的是Res_value,在資源項名稱字符串資源池中的索引爲150,對應的值是badge_continue_months,Res_value的大小爲8,數據的類型是TYPE_STRING,在資源項的值字符串資源池的索引爲1912,對應的值是res/drawable-nodpi-v4/badge_continue_months.png。
當咱們對arsc的文件格式有了瞭解事後,咱們就能夠開始咱們的探索之旅了,因爲在使用Android studio調試Apktool源碼的時候遇到不少障礙,在前輩的指導下才可以順利進行調試,因此下面簡單介紹下設置Android studio調試Apktool源碼的方法。
由於篇幅的緣由,這裏就不把全部的代碼都粘貼出來了,後面會列出來代碼下載地址
package com.wjdiankong.parseresource; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; public class ParseResourceMain { public static void main(String[] args){ byte[] srcByte = null; FileInputStream fis = null; ByteArrayOutputStream bos = null; try{ fis = new FileInputStream("resource/resources_gdt1.arsc"); bos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while((len=fis.read(buffer)) != -1){ bos.write(buffer, 0, len); } srcByte = bos.toByteArray(); }catch(Exception e){ System.out.println("read res file error:"+e.toString()); }finally{ try{ fis.close(); bos.close(); }catch(Exception e){ System.out.println("close file error:"+e.toString()); } } if(srcByte == null){ System.out.println("get src error..."); return; } System.out.println("parse restable header..."); ParseResourceUtils.parseResTableHeaderChunk(srcByte); System.out.println("++++++++++++++++++++++++++++++++++++++"); System.out.println(); System.out.println("parse resstring pool chunk..."); ParseResourceUtils.parseResStringPoolChunk(srcByte); System.out.println("++++++++++++++++++++++++++++++++++++++"); System.out.println(); System.out.println("parse package chunk..."); ParseResourceUtils.parsePackage(srcByte); System.out.println("++++++++++++++++++++++++++++++++++++++"); System.out.println(); System.out.println("parse typestring pool chunk..."); ParseResourceUtils.parseTypeStringPoolChunk(srcByte); System.out.println("++++++++++++++++++++++++++++++++++++++"); System.out.println(); System.out.println("parse keystring pool chunk..."); ParseResourceUtils.parseKeyStringPoolChunk(srcByte); System.out.println("++++++++++++++++++++++++++++++++++++++"); System.out.println(); int resCount = 0; while(!ParseResourceUtils.isEnd(srcByte.length)){ resCount++; boolean isSpec = ParseResourceUtils.isTypeSpec(srcByte); if(isSpec){ System.out.println("parse restype spec chunk..."); ParseResourceUtils.parseResTypeSpec(srcByte); System.out.println("++++++++++++++++++++++++++++++++++++++"); System.out.println(); }else{ System.out.println("parse restype info chunk..."); ParseResourceUtils.parseResTypeInfo(srcByte); System.out.println("++++++++++++++++++++++++++++++++++++++"); System.out.println(); } } System.out.println("res count:"+resCount); } }咱們看到代碼,首先咱們讀取resource.arsc文件到一個byte數組,而後開始解析。
/** * 解析頭部信息 * @param src */ public static void parseResTableHeaderChunk(byte[] src){ ResTableHeader resTableHeader = new ResTableHeader(); resTableHeader.header = parseResChunkHeader(src, 0); resStringPoolChunkOffset = resTableHeader.header.headerSize; //解析PackageCount個數(一個apk可能包含多個Package資源) byte[] packageCountByte = Utils.copyByte(src, resTableHeader.header.getHeaderSize(), 4); resTableHeader.packageCount = Utils.byte2int(packageCountByte); }解析結果:
/** * 解析Resource.arsc文件中全部字符串內容 * @param src */ public static void parseResStringPoolChunk(byte[] src){ ResStringPoolHeader stringPoolHeader = parseStringPoolChunk(src, resStringList, resStringPoolChunkOffset); packageChunkOffset = resStringPoolChunkOffset + stringPoolHeader.header.size; }這裏有一個核心的方法:parseStringPoolChunk
/** * 統一解析字符串內容 * @param src * @param stringList * @param stringOffset * @return */ public static ResStringPoolHeader parseStringPoolChunk(byte[] src, ArrayList<String> stringList, int stringOffset){ ResStringPoolHeader stringPoolHeader = new ResStringPoolHeader(); //解析頭部信息 stringPoolHeader.header = parseResChunkHeader(src, stringOffset); System.out.println("header size:"+stringPoolHeader.header.headerSize); System.out.println("size:"+stringPoolHeader.header.size); int offset = stringOffset + stringPoolHeader.header.getHeaderSize(); //獲取字符串的個數 byte[] stringCountByte = Utils.copyByte(src, offset, 4); stringPoolHeader.stringCount = Utils.byte2int(stringCountByte); //解析樣式的個數 byte[] styleCountByte = Utils.copyByte(src, offset+4, 4); stringPoolHeader.styleCount = Utils.byte2int(styleCountByte); //這裏表示字符串的格式:UTF-8/UTF-16 byte[] flagByte = Utils.copyByte(src, offset+8, 4); System.out.println("flag:"+Utils.bytesToHexString(flagByte)); stringPoolHeader.flags = Utils.byte2int(flagByte); //字符串內容的開始位置 byte[] stringStartByte = Utils.copyByte(src, offset+12, 4); stringPoolHeader.stringsStart = Utils.byte2int(stringStartByte); System.out.println("string start:"+Utils.bytesToHexString(stringStartByte)); //樣式內容的開始位置 byte[] sytleStartByte = Utils.copyByte(src, offset+16, 4); stringPoolHeader.stylesStart = Utils.byte2int(sytleStartByte); System.out.println("style start:"+Utils.bytesToHexString(sytleStartByte)); //獲取字符串內容的索引數組和樣式內容的索引數組 int[] stringIndexAry = new int[stringPoolHeader.stringCount]; int[] styleIndexAry = new int[stringPoolHeader.styleCount]; System.out.println("string count:"+stringPoolHeader.stringCount); System.out.println("style count:"+stringPoolHeader.styleCount); int stringIndex = offset + 20; for(int i=0;i<stringPoolHeader.stringCount;i++){ stringIndexAry[i] = Utils.byte2int(Utils.copyByte(src, stringIndex+i*4, 4)); } int styleIndex = stringIndex + 4*stringPoolHeader.stringCount; for(int i=0;i<stringPoolHeader.styleCount;i++){ styleIndexAry[i] = Utils.byte2int(Utils.copyByte(src, styleIndex+i*4, 4)); } //每一個字符串的頭兩個字節的最後一個字節是字符串的長度 //這裏獲取全部字符串的內容 int stringContentIndex = styleIndex + stringPoolHeader.styleCount*4; System.out.println("string index:"+Utils.bytesToHexString(Utils.int2Byte(stringContentIndex))); int index = 0; while(index < stringPoolHeader.stringCount){ byte[] stringSizeByte = Utils.copyByte(src, stringContentIndex, 2); int stringSize = (stringSizeByte[1] & 0x7F); if(stringSize != 0){ String val = ""; try{ val = new String(Utils.copyByte(src, stringContentIndex+2, stringSize), "utf-8"); }catch(Exception e){ System.out.println("string encode error:"+e.toString()); } stringList.add(val); }else{ stringList.add(""); } stringContentIndex += (stringSize+3); index++; } for(String str : stringList){ System.out.println("str:"+str); } return stringPoolHeader; }這裏在獲得一個字符串的時候,須要獲得字符串的開始位置和字符串的大小便可,這點和解析AndroidManifest.xml文件中的字符串原理是同樣的,就是一個字符串塊的頭兩個字節中的最後一個字節是字符串的長度。這裏咱們在解析完字符串以後,須要用一個列表將其存儲起來,後面有用到,須要經過索引來取字符串內容。
解析結果:
/** * 解析Package信息 * @param src */ public static void parsePackage(byte[] src){ System.out.println("pchunkoffset:"+Utils.bytesToHexString(Utils.int2Byte(packageChunkOffset))); ResTablePackage resTabPackage = new ResTablePackage(); //解析頭部信息 resTabPackage.header = parseResChunkHeader(src, packageChunkOffset); System.out.println("package size:"+resTabPackage.header.headerSize); int offset = packageChunkOffset + resTabPackage.header.getHeaderSize(); //解析packId byte[] idByte = Utils.copyByte(src, offset, 4); resTabPackage.id = Utils.byte2int(idByte); packId = resTabPackage.id; //解析包名 System.out.println("package offset:"+Utils.bytesToHexString(Utils.int2Byte(offset+4))); byte[] nameByte = Utils.copyByte(src, offset+4, 128*2);//這裏的128是這個字段的大小,能夠查看類型說明,是char類型的,因此要乘以2 String packageName = new String(nameByte); packageName = Utils.filterStringNull(packageName); System.out.println("pkgName:"+packageName); //解析類型字符串的偏移值 byte[] typeStringsByte = Utils.copyByte(src, offset+4+128*2, 4); resTabPackage.typeStrings = Utils.byte2int(typeStringsByte); System.out.println("typeString:"+resTabPackage.typeStrings); //解析lastPublicType字段 byte[] lastPublicType = Utils.copyByte(src, offset+8+128*2, 4); resTabPackage.lastPublicType = Utils.byte2int(lastPublicType); //解析keyString字符串的偏移值 byte[] keyStrings = Utils.copyByte(src, offset+12+128*2, 4); resTabPackage.keyStrings = Utils.byte2int(keyStrings); System.out.println("keyString:"+resTabPackage.keyStrings); //解析lastPublicKey byte[] lastPublicKey = Utils.copyByte(src, offset+12+128*2, 4); resTabPackage.lastPublicKey = Utils.byte2int(lastPublicKey); //這裏獲取類型字符串的偏移值和類型字符串的偏移值 keyStringPoolChunkOffset = (packageChunkOffset+resTabPackage.keyStrings); typeStringPoolChunkOffset = (packageChunkOffset+resTabPackage.typeStrings); }這裏咱們看到有一個特殊的地方,就是最後兩行,這裏須要獲得咱們後面須要重要解析的兩個內容,一個是資源值字符串的偏移值和資源類型字符串的偏移值。
解析結果:
/** * 解析類型字符串內容 * @param src */ public static void parseTypeStringPoolChunk(byte[] src){ System.out.println("typestring offset:"+Utils.bytesToHexString(Utils.int2Byte(typeStringPoolChunkOffset))); ResStringPoolHeader stringPoolHeader = parseStringPoolChunk(src, typeStringList, typeStringPoolChunkOffset); System.out.println("size:"+stringPoolHeader.header.size); }這裏也是用parseStringPoolChunk方法進行解析的,一樣也須要用一個字符串列表存儲內容
解析結果:
/** * 解析key字符串內容 * @param src */ public static void parseKeyStringPoolChunk(byte[] src){ System.out.println("keystring offset:"+Utils.bytesToHexString(Utils.int2Byte(keyStringPoolChunkOffset))); ResStringPoolHeader stringPoolHeader = parseStringPoolChunk(src, keyStringList, keyStringPoolChunkOffset); System.out.println("size:"+stringPoolHeader.header.size); //解析完key字符串以後,須要賦值給resType的偏移值,後續還須要繼續解析 resTypeOffset = (keyStringPoolChunkOffset+stringPoolHeader.header.size); }這裏也是同樣,使用parseStringPoolChunk方法來解析,解析完以後須要用一個字符串列表保存,後面須要使用索引值來訪問
解析結果:
這裏說到的正文內容就是ResValue值,也就是開始構建public.xml中的條目信息,和類型的分離不一樣的xml文件,因此這部分的內容的解析工做有點複雜
int resCount = 0; while(!ParseResourceUtils.isEnd(srcByte.length)){ resCount++; boolean isSpec = ParseResourceUtils.isTypeSpec(srcByte); if(isSpec){ System.out.println("parse restype spec chunk..."); ParseResourceUtils.parseResTypeSpec(srcByte); System.out.println("++++++++++++++++++++++++++++++++++++++"); System.out.println(); }else{ System.out.println("parse restype info chunk..."); ParseResourceUtils.parseResTypeInfo(srcByte); System.out.println("++++++++++++++++++++++++++++++++++++++"); System.out.println(); } } System.out.println("res count:"+resCount);這裏有一個循環解析,有兩個方法,一個是isEnd方法,一個是isTypeSpec方法
咱們若是仔細看上面的那張神圖的話,就能夠看到,後面的ResType和ResTypeSpec他們兩個內容是交替出現的,直到文件結束。
因此isEnd方法就是判斷是否到達文件結束位置:
/** * 判斷是否到文件末尾了 * @param length * @return */ public static boolean isEnd(int length){ if(resTypeOffset>=length){ return true; } return false; }
/** * 判斷是否是類型描述符 * @param src * @return */ public static boolean isTypeSpec(byte[] src){ ResChunkHeader header = parseResChunkHeader(src, resTypeOffset); if(header.type == 0x0202){ return true; } return false; }那麼就是分別來解析ResTypeSpec和ResType這兩個內容了:
一、解析ResTypeSpec
主要獲得Res的每一個類型名
/** * 解析ResTypeSepc類型描述內容 * @param src */ public static void parseResTypeSpec(byte[] src){ System.out.println("res type spec offset:"+Utils.bytesToHexString(Utils.int2Byte(resTypeOffset))); ResTableTypeSpec typeSpec = new ResTableTypeSpec(); //解析頭部信息 typeSpec.header = parseResChunkHeader(src, resTypeOffset); int offset = (resTypeOffset + typeSpec.header.getHeaderSize()); //解析id類型 byte[] idByte = Utils.copyByte(src, offset, 1); typeSpec.id = (byte)(idByte[0] & 0xFF); resTypeId = typeSpec.id; //解析res0字段,這個字段是備用的,始終是0 byte[] res0Byte = Utils.copyByte(src, offset+1, 1); typeSpec.res0 = (byte)(res0Byte[0] & 0xFF); //解析res1字段,這個字段是備用的,始終是0 byte[] res1Byte = Utils.copyByte(src, offset+2, 2); typeSpec.res1 = Utils.byte2Short(res1Byte); //entry的總個數 byte[] entryCountByte = Utils.copyByte(src, offset+4, 4); typeSpec.entryCount = Utils.byte2int(entryCountByte); System.out.println("res type spec:"+typeSpec); System.out.println("type_name:"+typeStringList.get(typeSpec.id-1)); //獲取entryCount個int數組 int[] intAry = new int[typeSpec.entryCount]; int intAryOffset = resTypeOffset + typeSpec.header.headerSize; System.out.print("int element:"); for(int i=0;i<typeSpec.entryCount;i++){ int element = Utils.byte2int(Utils.copyByte(src, intAryOffset+i*4, 4)); intAry[i] = element; System.out.print(element+","); } System.out.println(); resTypeOffset += typeSpec.header.size; }
解析結果:
二、解析ResType
主要獲得每一個res類型的全部條目內容
/** * 解析類型信息內容 * @param src */ public static void parseResTypeInfo(byte[] src){ System.out.println("type chunk offset:"+Utils.bytesToHexString(Utils.int2Byte(resTypeOffset))); ResTableType type = new ResTableType(); //解析頭部信息 type.header = parseResChunkHeader(src, resTypeOffset); int offset = (resTypeOffset + type.header.getHeaderSize()); //解析type的id值 byte[] idByte = Utils.copyByte(src, offset, 1); type.id = (byte)(idByte[0] & 0xFF); //解析res0字段的值,備用字段,始終是0 byte[] res0 = Utils.copyByte(src, offset+1, 1); type.res0 = (byte)(res0[0] & 0xFF); //解析res1字段的值,備用字段,始終是0 byte[] res1 = Utils.copyByte(src, offset+2, 2); type.res1 = Utils.byte2Short(res1); byte[] entryCountByte = Utils.copyByte(src, offset+4, 4); type.entryCount = Utils.byte2int(entryCountByte); byte[] entriesStartByte = Utils.copyByte(src, offset+8, 4); type.entriesStart = Utils.byte2int(entriesStartByte); ResTableConfig resConfig = new ResTableConfig(); resConfig = parseResTableConfig(Utils.copyByte(src, offset+12, resConfig.getSize())); System.out.println("config:"+resConfig); System.out.println("res type info:"+type); System.out.println("type_name:"+typeStringList.get(type.id-1)); //先獲取entryCount個int數組 System.out.print("type int elements:"); int[] intAry = new int[type.entryCount]; for(int i=0;i<type.entryCount;i++){ int element = Utils.byte2int(Utils.copyByte(src, resTypeOffset+type.header.headerSize+i*4, 4)); intAry[i] = element; System.out.print(element+","); } System.out.println(); //這裏開始解析後面對應的ResEntry和ResValue int entryAryOffset = resTypeOffset + type.entriesStart; ResTableEntry[] tableEntryAry = new ResTableEntry[type.entryCount]; ResValue[] resValueAry = new ResValue[type.entryCount]; System.out.println("entry offset:"+Utils.bytesToHexString(Utils.int2Byte(entryAryOffset))); //這裏存在一個問題就是若是是ResMapEntry的話,偏移值是不同的,因此這裏須要計算不一樣的偏移值 int bodySize = 0, valueOffset = entryAryOffset; for(int i=0;i<type.entryCount;i++){ int resId = getResId(i); System.out.println("resId:"+Utils.bytesToHexString(Utils.int2Byte(resId))); ResTableEntry entry = new ResTableEntry(); ResValue value = new ResValue(); valueOffset += bodySize; System.out.println("valueOffset:"+Utils.bytesToHexString(Utils.int2Byte(valueOffset))); entry = parseResEntry(Utils.copyByte(src, valueOffset, entry.getSize())); //這裏須要注意的是,先判斷entry的flag變量是否爲1,若是爲1的話,那就ResTable_map_entry if(entry.flags == 1){ //這裏是複雜類型的value ResTableMapEntry mapEntry = new ResTableMapEntry(); mapEntry = parseResMapEntry(Utils.copyByte(src, valueOffset, mapEntry.getSize())); System.out.println("map entry:"+mapEntry); ResTableMap resMap = new ResTableMap(); for(int j=0;j<mapEntry.count;j++){ int mapOffset = valueOffset + mapEntry.getSize() + resMap.getSize()*j; resMap = parseResTableMap(Utils.copyByte(src, mapOffset, resMap.getSize())); System.out.println("map:"+resMap); } bodySize = mapEntry.getSize() + resMap.getSize()*mapEntry.count; }else{ System.out.println("entry:"+entry); //這裏是簡單的類型的value value = parseResValue(Utils.copyByte(src, valueOffset+entry.getSize(), value.getSize())); System.out.println("value:"+value); bodySize = entry.getSize()+value.getSize(); } tableEntryAry[i] = entry; resValueAry[i] = value; System.out.println("======================================"); } resTypeOffset += type.header.size; } /** * 解析ResEntry內容 * @param src * @return */ public static ResTableEntry parseResEntry(byte[] src){ ResTableEntry entry = new ResTableEntry(); byte[] sizeByte = Utils.copyByte(src, 0, 2); entry.size = Utils.byte2Short(sizeByte); byte[] flagByte = Utils.copyByte(src, 2, 2); entry.flags = Utils.byte2Short(flagByte); ResStringPoolRef key = new ResStringPoolRef(); byte[] keyByte = Utils.copyByte(src, 4, 4); key.index = Utils.byte2int(keyByte); entry.key = key; return entry; } /** * 解析ResMapEntry內容 * @param src * @return */ public static ResTableMapEntry parseResMapEntry(byte[] src){ ResTableMapEntry entry = new ResTableMapEntry(); byte[] sizeByte = Utils.copyByte(src, 0, 2); entry.size = Utils.byte2Short(sizeByte); byte[] flagByte = Utils.copyByte(src, 2, 2); entry.flags = Utils.byte2Short(flagByte); ResStringPoolRef key = new ResStringPoolRef(); byte[] keyByte = Utils.copyByte(src, 4, 4); key.index = Utils.byte2int(keyByte); entry.key = key; ResTableRef ref = new ResTableRef(); byte[] identByte = Utils.copyByte(src, 8, 4); ref.ident = Utils.byte2int(identByte); entry.parent = ref; byte[] countByte = Utils.copyByte(src, 12, 4); entry.count = Utils.byte2int(countByte); return entry; } /** * 解析ResValue內容 * @param src * @return */ public static ResValue parseResValue(byte[] src){ ResValue resValue = new ResValue(); byte[] sizeByte = Utils.copyByte(src, 0, 2); resValue.size = Utils.byte2Short(sizeByte); byte[] res0Byte = Utils.copyByte(src, 2, 1); resValue.res0 = (byte)(res0Byte[0] & 0xFF); byte[] dataType = Utils.copyByte(src, 3, 1); resValue.dataType = (byte)(dataType[0] & 0xFF); byte[] data = Utils.copyByte(src, 4, 4); resValue.data = Utils.byte2int(data); return resValue; } /** * 解析ResTableMap內容 * @param src * @return */ public static ResTableMap parseResTableMap(byte[] src){ ResTableMap tableMap = new ResTableMap(); ResTableRef ref = new ResTableRef(); byte[] identByte = Utils.copyByte(src, 0, ref.getSize()); ref.ident = Utils.byte2int(identByte); tableMap.name = ref; ResValue value = new ResValue(); value = parseResValue(Utils.copyByte(src, ref.getSize(), value.getSize())); tableMap.value = value; return tableMap; } /** * 解析ResTableConfig配置信息 * @param src * @return */ public static ResTableConfig parseResTableConfig(byte[] src){ ResTableConfig config = new ResTableConfig(); byte[] sizeByte = Utils.copyByte(src, 0, 4); config.size = Utils.byte2int(sizeByte); //如下結構是Union byte[] mccByte = Utils.copyByte(src, 4, 2); config.mcc = Utils.byte2Short(mccByte); byte[] mncByte = Utils.copyByte(src, 6, 2); config.mnc = Utils.byte2Short(mncByte); byte[] imsiByte = Utils.copyByte(src, 4, 4); config.imsi = Utils.byte2int(imsiByte); //如下結構是Union byte[] languageByte = Utils.copyByte(src, 8, 2); config.language = languageByte; byte[] countryByte = Utils.copyByte(src, 10, 2); config.country = countryByte; byte[] localeByte = Utils.copyByte(src, 8, 4); config.locale = Utils.byte2int(localeByte); //如下結構是Union byte[] orientationByte = Utils.copyByte(src, 12, 1); config.orientation = orientationByte[0]; byte[] touchscreenByte = Utils.copyByte(src, 13, 1); config.touchscreen = touchscreenByte[0]; byte[] densityByte = Utils.copyByte(src, 14, 2); config.density = Utils.byte2Short(densityByte); byte[] screenTypeByte = Utils.copyByte(src, 12, 4); config.screenType = Utils.byte2int(screenTypeByte); //如下結構是Union byte[] keyboardByte = Utils.copyByte(src, 16, 1); config.keyboard = keyboardByte[0]; byte[] navigationByte = Utils.copyByte(src, 17, 1); config.navigation = navigationByte[0]; byte[] inputFlagsByte = Utils.copyByte(src, 18, 1); config.inputFlags = inputFlagsByte[0]; byte[] inputPad0Byte = Utils.copyByte(src, 19, 1); config.inputPad0 = inputPad0Byte[0]; byte[] inputByte = Utils.copyByte(src, 16, 4); config.input = Utils.byte2int(inputByte); //如下結構是Union byte[] screenWidthByte = Utils.copyByte(src, 20, 2); config.screenWidth = Utils.byte2Short(screenWidthByte); byte[] screenHeightByte = Utils.copyByte(src, 22, 2); config.screenHeight = Utils.byte2Short(screenHeightByte); byte[] screenSizeByte = Utils.copyByte(src, 20, 4); config.screenSize = Utils.byte2int(screenSizeByte); //如下結構是Union byte[] sdVersionByte = Utils.copyByte(src, 24, 2); config.sdVersion = Utils.byte2Short(sdVersionByte); byte[] minorVersionByte = Utils.copyByte(src, 26, 2); config.minorVersion = Utils.byte2Short(minorVersionByte); byte[] versionByte = Utils.copyByte(src, 24, 4); config.version = Utils.byte2int(versionByte); //如下結構是Union byte[] screenLayoutByte = Utils.copyByte(src, 28, 1); config.screenLayout = screenLayoutByte[0]; byte[] uiModeByte = Utils.copyByte(src, 29, 1); config.uiMode = uiModeByte[0]; byte[] smallestScreenWidthDpByte = Utils.copyByte(src, 30, 2); config.smallestScreenWidthDp = Utils.byte2Short(smallestScreenWidthDpByte); byte[] screenConfigByte = Utils.copyByte(src, 28, 4); config.screenConfig = Utils.byte2int(screenConfigByte); //如下結構是Union byte[] screenWidthDpByte = Utils.copyByte(src, 32, 2); config.screenWidthDp = Utils.byte2Short(screenWidthDpByte); byte[] screenHeightDpByte = Utils.copyByte(src, 34, 2); config.screenHeightDp = Utils.byte2Short(screenHeightDpByte); byte[] screenSizeDpByte = Utils.copyByte(src, 32, 4); config.screenSizeDp = Utils.byte2int(screenSizeDpByte); byte[] localeScriptByte = Utils.copyByte(src, 36, 4); config.localeScript = localeScriptByte; byte[] localeVariantByte = Utils.copyByte(src, 40, 8); config.localeVariant = localeVariantByte; return config; }看到這裏,咱們發現這裏的解析很複雜的,和咱們在講解數據結構的時候那裏同樣,他須要解析不少內容:
ResValue,ResTableMap,ResTableMapEntry,ResTableEntry,ResConfig
關於每一個數據結構如何解析這裏就很少說了,就是讀取字節便可。這裏有一個核心的代碼:
//這裏須要注意的是,先判斷entry的flag變量是否爲1,若是爲1的話,那就ResTable_map_entry if(entry.flags == 1){ //這裏是複雜類型的value ResTableMapEntry mapEntry = new ResTableMapEntry(); mapEntry = parseResMapEntry(Utils.copyByte(src, valueOffset, mapEntry.getSize())); System.out.println("map entry:"+mapEntry); ResTableMap resMap = new ResTableMap(); for(int j=0;j<mapEntry.count;j++){ int mapOffset = valueOffset + mapEntry.getSize() + resMap.getSize()*j; resMap = parseResTableMap(Utils.copyByte(src, mapOffset, resMap.getSize())); System.out.println("map:"+resMap); } bodySize = mapEntry.getSize() + resMap.getSize()*mapEntry.count; }else{ System.out.println("entry:"+entry); //這裏是簡單的類型的value value = parseResValue(Utils.copyByte(src, valueOffset+entry.getSize(), value.getSize())); System.out.println("value:"+value); bodySize = entry.getSize()+value.getSize(); }判斷flag的值,來進行不一樣的解析操做。這裏須要注意這點。
解析結果:
看到解析結果,仍是挺欣慰的,由於最難的地方咱們解析成功了,並且看到結果咱們很激動,就是咱們想要的結果,可是這裏須要解釋的是,有了這些值咱們構建public.xml內容和各個類型的xml內容是很簡單,固然這裏咱們去構建了,感興趣的同窗能夠去嘗試一下。
注意:這裏的ResId的構造方法是:
/** * 獲取資源id * 這裏高位是packid,中位是restypeid,地位是entryid * @param entryid * @return */ public static int getResId(int entryid){ return (((packId)<<24) | (((resTypeId) & 0xFF)<<16) | (entryid & 0xFFFF)); }這裏咱們能夠看到就是一個int類型的resId,
他的最高兩個字節表示packId,系統資源id是:0x01,普通應用資源id是:0x7F
他的中間的兩個字節表示resTypeId,類型id,這個值從0開始,好比咱們例子中第一個類型是attr,那麼他的resTypeId就是00
他的最低四個字節表示這個資源的順序id,從1開始,逐漸累加1
項目下載地址:http://download.csdn.net/detail/jiangwei0910410003/9426712
上面咱們就很蛋疼的解析完了全部的resource.arsc文件,固然內容有點多,因此有些地方可能沒介紹清楚或者是有錯誤的地方,請多指正。固然關於Android編譯以後的四個文件格式,咱們已經介紹了三個了:
so文件格式、AndroidManifest.xml格式/資源文件.xml、resource.arsc
那麼剩下就只有classes.dex這一個文件格式了,咱們就算大功告成了。可是我想在這裏說的是,這篇文章咱們主要是介紹解析resource.arsc文件格式,那麼寫這篇文章的目的是什麼呢?
有兩個:
一、咱們在使用apktool工具進行反編譯的時候,常常出現一些莫名的一場信息,最多的就是NotFound ResId 0x0000XXX這些內容,那麼這時候咱們就能夠去修復了,固然咱們能夠獲得apktool的源碼來解決這個問題,還能夠就是使用咱們本身寫的這套解析代碼也是能夠的。
二、咱們以前提過,解析resource.arsc文件以後,對resource.arsc文件格式若是有了解了以後,能夠對資源文件名進行混淆,從而來減少apk包大小,我在以前的一篇文章:
Apk的簽名機制:http://blog.csdn.net/jiangwei0910410003/article/details/50402000
由於META-INF文件夾下的三個文件大小很大,緣由就是他們內部保存了每一個資源名稱,咱們在項目中有時候爲了避免形成衝突,就把資源名起的很長,那麼這樣就會致使apk的包很大。
一樣resource.arsc文件也會很大,由於資源名都是須要保存的,可是咱們知道Android中的混淆是不會對資源文件進行混淆的,因此這時候咱們就能夠經過這個思路來減少包apk的大小了。這個後續我會繼續講解的。
注意:
到這裏咱們還須要告訴一件事,那就是其實咱們上面的解析工做,有一個更簡單的方法就能夠搞定了?那就是aapt命令?關於這個aapt是幹啥的?網上有不少資料,他其實很簡單就是將Android中的資源文件打包成resource.arsc便可:
只有那些類型爲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資源文件之所要從文本格式編譯成二進制格式,是由於:
1. 二進制格式的XML文件佔用空間更小。這是因爲全部XML元素的標籤、屬性名稱、屬性值和內容所涉及到的字符串都會被統一收集到一個字符串資源池中去,而且會去重。有了這個字符串資源池,原來使用字符串的地方就會被替換成一個索引到字符串資源池的整數值,從而能夠減小文件的大小。
2. 二進制格式的XML文件解析速度更快。這是因爲二進制格式的XML元素裏面再也不包含有字符串值,所以就避免了進行字符串解析,從而提升速度。
將XML資源文件從文本格式編譯成二進制格式解決了空間佔用以及解析效率的問題,可是對於Android資源管理框架來講,這只是完成了其中的一部分工做。Android資源管理框架的另一個重要任務就是要根據資源ID來快速找到對應的資源。
那麼下面咱們用aapt命令就能夠查看一下?
aapt命令在咱們的AndroidSdk目錄中:
看到路徑了:Android-SDK目錄/build-tools/下面
咱們也就知道了,這個目錄下全是Android中build成一個apk的全部工具,這裏再看一下這些工具的用途:
一、使用Android SDK提供的aapt.exe生成R.java類文件
二、使用Android SDK提供的aidl.exe把.aidl轉成.java文件(若是沒有aidl,則跳過這一步)
三、使用JDK提供的javac.exe編譯.java類文件生成class文件
四、使用Android SDK提供的dx.bat命令行腳本生成classes.dex文件
五、使用Android SDK提供的aapt.exe生成資源包文件(包括res、assets、androidmanifest.xml等)
六、使用Android SDK提供的apkbuilder.bat生成未簽名的apk安裝文件
七、使用jdk的jarsigner.exe對未簽名的包進行apk簽名
看到了吧。咱們原來能夠不借助任何IDE工具,也是能夠出一個apk包的。哈哈~~
繼續看aapt命令的用法,命令很簡單:
aapt l -a apk名稱 > demo.txt
將輸入的結果定向到demo.txt中
看到咱們弄出來的內容,發現就是咱們上面解析的AndroidManifest.xml內容,因此這個也是一個方法,固然aapt命令這裏我爲何最後說呢?以前咱們講解的AndroidManifest.xml格式確定是有用的,aapt命令只是系統提供給咱們一個很好的工具,咱們能夠在反編譯的過程當中藉助這個工具也是不錯的選擇。因此這裏我就想說,之後咱們記得有一個aapt命令就行了,他的用途仍是不少的,能夠單獨編譯成一個resource.arsc文件來,咱們後面會用到這個命令。
這篇文章篇幅有點長,因此我寫的很蛋疼,可是得耐心的看,由於resource.arsc文件格式比AndroidManifest.xml文件格式複雜得多,因此解析起來很費勁的。也但願大家看完以後能多多支持,後面還有一篇解析classes.dex文件格式,固然這篇文章要等年後來才能動筆了,因此盡請期待,最好注你們新年快樂~~
PS: 關注微信,最新Android技術實時推送