之前有一篇介绍解析及精简xml文件的文章,仅覆盖了protobuf格式,对于二进制格式没有深究。 # android apk二进制资源优化
今天继续实现解析二进制resources.arsc文件。
在实现之前,看了官网的格式定义及一些分析文章,考虑到这个是固定格式,查看了一些demo后直接基于以下demo进行验证学习和完善。
# Android 手把手分析resources.arsc
再次感谢作者的分享!
先放上完整的结构图帮助理解各部分内容,结合代码更容易理解。
优化后的代码地址
gitgub-resourcesParse
主要是补充了解析多个package的情况;下面结合代码与结构图进行比较。
首先定义chunk_header,每个块的前三字段都是固定格式,即{type(2),headerSize(4),contentSize(4)}。可以根据type来决定后面的内容如何解析,使用headerSize可以跳过header解析content,使用contentSize可以跳过整块内容,继续解析下一块内容。
public class ResChunkHeader {
public short type;
public short headerSize;
public int size;
public int getHeaderSize() {
return 2 + 2 + 4;
}
}
chunk的类型定义如下:
public static final int RES_NULL_TYPE = 0x0000;
public static final int RES_STRING_POOL_TYPE = 0x0001;
public static final int RES_TABLE_TYPE = 0x0002;
public static final int RES_XML_TYPE = 0x0003;
// Chunk types in RES_XML_TYPE
public static final int RES_XML_FIRST_CHUNK_TYPE = 0x0100;
public static final int RES_XML_START_NAMESPACE_TYPE= 0x0100;
public static final int RES_XML_END_NAMESPACE_TYPE = 0x0101;
public static final int RES_XML_START_ELEMENT_TYPE = 0x0102;
public static final int RES_XML_END_ELEMENT_TYPE = 0x0103;
public static final int RES_XML_CDATA_TYPE = 0x0104;
public static final int RES_XML_LAST_CHUNK_TYPE = 0x017f;
// This contains a uint32_t array mapping strings in the string
// pool back to resource identifiers. It is optional.
public static final int RES_XML_RESOURCE_MAP_TYPE = 0x0180;
// Chunk types in RES_TABLE_TYPE
public static final int RES_TABLE_PACKAGE_TYPE = 0x0200;
public static final int RES_TABLE_TYPE_TYPE = 0x0201;
public static final int RES_TABLE_TYPE_SPEC_TYPE = 0x0202;
public static final int RES_TABLE_LIBRARY_TYPE = 0x0203;
继续就主要的类型做拆解
| 类型 | 结构类 | 主要字段 |
|---|---|---|
| RES_TABLE_TYPE=2 | ResTableHeader | {ResChunkHeader(8),packageCount(4)} |
| RES_STRING_POOL_TYPE=1 | 未定义 | {ResStringPoolHeader(28),stringCount4,styleCount4,strings,styles} |
| RES_TABLE_PACKAGE_TYPE=512 | ResTablePackage | {ResChunkHeader,id,name...} id是resourceId(0xPPTTEEEE)里的PP |
| RES_TABLE_TYPE_SPEC_TYPE=514 | ResTableTypeSpec | {ResChunkHeader,id,res0,res1,entryCount,List<ResTableType>,specs} |
| RES_TABLE_TYPE_TYPE=513 | ResTableType | {ResChunkHeader,id,flags,reserved,entryCount,entryStart,ResTableConfig,List<ResTableEntry>} |
| RES_TABLE_LIBRARY_TYPE=515 | 未处理 |
整体结构如下
ResTableHeader
ResStringPoolHeader,strings,styles
ResTablePackage
----ResStringPoolHeader,typeStrings
----ResStringPoolHeader,keyStrings
----ResTableTypeSpec
--------ResTableType
------------ResTableConfig,List<ResTableEntry>
----------------ResStringPoolRef,ResValue
--------ResTableType...
----ResTableTypeSpec...
--------ResTableType...
ResTablePackage...
----ResStringPoolHeader,srings
----ResStringPoolHeader,srings
----ResTableTypeSpec...
--------ResTableType...
...
下面是一个简单demo工程的节选解析日志
input file length 1014220
ResTableHeader{chunkHeader=ResChunkHeader{type=table(2), headerSize=12, size=1014220}, packageCount=1}
ResStringPoolHeader{chunkHeader=ResChunkHeader{type=string_pool(1), headerSize=28, size=294688}, stringCount=7884, styleCount=0, flags=256, stringsStart=31564, stylesStart=0}
----start parse string pool
----end parse string pool index=7884
----start parse string style
----end parse string style
ResTablePackage{chunkHeader=ResChunkHeader{type=table_package(512), headerSize=288, size=719520}, id=7f, name=com.barran.androidsample, typeStrings=288, lastPublicType=0, keyStrings=644, lastPublicKey=0, typeIdOffset=0}
ResStringPoolHeader{chunkHeader=ResChunkHeader{type=string_pool(1), headerSize=28, size=356}, stringCount=17, styleCount=0, flags=0, stringsStart=96, stylesStart=0}
----start parse string pool
----end parse string pool index=17
----start parse string style
----end parse string style
ResStringPoolHeader{chunkHeader=ResChunkHeader{type=string_pool(1), headerSize=28, size=177924}, stringCount=4988, styleCount=0, flags=256, stringsStart=19980, stylesStart=0}
----start parse string pool
----end parse string pool index=4988
----start parse string style
----end parse string style
ResTableTypeSpec{chunkHeader=ResChunkHeader{type=table_type_spec(514), headerSize=16, size=192}, id=1, entryCount=44, resTableTypes=[], spec size=44}
ResTableType{chunkHeader=ResChunkHeader{type=table_type(513), headerSize=84, size=964}, id=1, entryCount=44, entriesStart=260, resConfig=ResTableConfig{size=64}, entrys size=44, resTableEntries={size=44,non_null_count=44,1=ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=1}, resValue=ResValue{size=8, res0=0, dataType=3, data=83}}}}
ResTableTypeSpec{chunkHeader=ResChunkHeader{type=table_type_spec(514), headerSize=16, size=148}, id=2, entryCount=33, resTableTypes=[], spec size=33}
ResTableType{chunkHeader=ResChunkHeader{type=table_type(513), headerSize=84, size=744}, id=2, entryCount=33, entriesStart=216, resConfig=ResTableConfig{size=64}, entrys size=33, resTableEntries={size=33,non_null_count=33,1=ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=45}, resValue=ResValue{size=8, res0=0, dataType=3, data=118}}}}
ResTableTypeSpec{chunkHeader=ResChunkHeader{type=table_type_spec(514), headerSize=16, size=4884}, id=3, entryCount=1217, resTableTypes=[], spec size=1217}
ResTableType{chunkHeader=ResChunkHeader{type=table_type(513), headerSize=84, size=45736}, id=3, entryCount=1217, entriesStart=4952, resConfig=ResTableConfig{size=64}, entrys size=1217, resTableEntries={size=1217,non_null_count=1217,1=ResTableMapEntry{parent=ResTableRef{index=0}, count=1, resValues=[ResValue{size=0, res0=0, dataType=1, data=268435464}], size=16, flags=1, key=ResStringPoolRef{index=78}, resValue=ResValue{size=0, res0=0, dataType=0, data=0}}}}
ResTableTypeSpec{chunkHeader=ResChunkHeader{type=table_type_spec(514), headerSize=16, size=28}, id=4, entryCount=3, resTableTypes=[], spec size=3}
ResTableType{chunkHeader=ResChunkHeader{type=table_type(513), headerSize=84, size=144}, id=4, entryCount=3, entriesStart=96, resConfig=ResTableConfig{size=64}, entrys size=3, resTableEntries={size=3,non_null_count=3,1=ResTableEntry{size=8, flags=0, key=ResStringPoolRef{index=1295}, resValue=ResValue{size=8, res0=0, dataType=18, data=-1}}}}