編輯:關於Android編程
對於APK裡面的Resources.arsc文件大家應該都知道是干什麼的,它實際上就是App的資源索引表。下面我會結合實例對它的格式做一下剖析,讀完這篇文章應該能夠知道Resources.arsc的格式,並可以從二進制的文件中查找到資源的相關信息,或者根據資源的id可以定位到二進制文件中的位置。不過本人對Android資源文件的有一些相關概念並不是特別熟悉,所以文章中有很多地方也並不明白,如有錯誤歡迎指正!
首先先介紹一下我們在Android應用開發過程中程序中用的資源的id,相信大家都知道R.java文件,這個是通過aapt對資源文件進行編譯生成的資源id文件,這樣我們程序中使用資源文件更加方便。舉例我們先看一下原始的資源文件res/values/strings.xml內容如下:
Cert Hello world! Settings
代碼段1
這裡先介紹幾個概念,上面的app_name和hello_world這些叫做資源項名稱(其它的還有windowActionBar、ActionBarTabStyle類似這種),而它們對應的資源項類型就是string(其它的還有attr、drawable類似這些),資源項的值就是Cert和Hello world!這些。
下面是對應R.java文件的內容:
public final class R { ... public static final class string { ... /** Description of the choose target button in a ShareActionProvider (share UI). [CHAR LIMIT=NONE] */ public static final int abc_shareactionprovider_share_with=0x7f0a000c; /** Description of a share target (both in the list of such or the default share button) in a ShareActionProvider (share UI). [CHAR LIMIT=NONE] */ public static final int abc_shareactionprovider_share_with_application=0x7f0a000b; public static final int action_settings=0x7f0a000f; public static final int app_name=0x7f0a000d; public static final int hello_world=0x7f0a000e; } ... }
代碼段2
可以看到每個資源文件在R中都是一個class,每個資源項名稱都分配了一個id,id值是一個四字節無符號整數,格式是這樣的:0xpptteeee,(p代表的是package,t代表的是type,e代表的是entry),最高字節代表Package ID,次高字節代表Type ID,後面兩個字節代表Entry ID。
Package ID相當於是一個命名空間,限定資源的來源。Android系統當前定義了兩個資源命令空間,其中一個系統資源命令空間,它的Package ID等於0x01,另外一個是應用程序資源命令空間,它的Package ID等於0x7f。所有位於[0x01, 0x7f]之間的Package ID都是合法的,而在這個范圍之外的都是非法的Package ID。前面提到的系統資源包package-export.apk的Package ID就等於0x01,而我們在應用程序中定義的資源的Package ID的值都等於0x7f,這一點可以通過生成的R.java文件來驗證。
Type ID是指資源的類型ID。資源的類型有animator、anim、color、drawable、layout、menu、raw、string和xml等等若干種,每一種都會被賦予一個ID。
Entry ID是指每一個資源在其所屬的資源類型中所出現的次序。注意,不同類型的資源的Entry ID有可能是相同的,但是由於它們的類型不同,我們仍然可以通過其資源ID來區別開來。
下面我們開始看Resources.arsc(後面截圖給出的resources.arsc文件的二進制內容都是與上面代碼段1和代碼段2相對應的),首先看一下文件的格式,如下面兩個圖:
圖1
圖2
以上兩個圖都是Resources.arsc文件的格式,圖1是從網上找的,其中很多項都展開了,不了解對應的數據結構肯定看不懂,所以我自己畫了圖2(畫圖好蛋疼的說~),相對來說更容易接受一點,這裡都放出來做個對照吧。Resources.arsc對應的數據結構的定義在Android源碼/frameworks/base/include/androidfw/ResourceType.h中,大家可以自己去看一下。
下面我來從上到下介紹一下文件的格式,首先是chunk概念,整個文件是由一系列的chunk構成的,算是整個文件劃分的基本單位吧,實際上就是把整個文件無差別的劃分成多個模塊,每個模塊就是一個chunk,結構更加清晰。每個chunk是最前面是一個ResChunk_header的結構體,描述這個chunk的信息,ResChunk_header如下:
struct ResChunk_header { enum { RES_NULL_TYPE = 0x0000, RES_STRING_POOL_TYPE = 0x0001, RES_TABLE_TYPE = 0x0002, RES_XML_TYPE = 0x0003, RES_XML_FIRST_CHUNK_TYPE = 0x0100, RES_XML_START_NAMESPACE_TYPE= 0x0100, RES_XML_END_NAMESPACE_TYPE = 0x0101, RES_XML_START_ELEMENT_TYPE = 0x0102, RES_XML_END_ELEMENT_TYPE = 0x0103, RES_XML_CDATA_TYPE = 0x0104, RES_XML_LAST_CHUNK_TYPE = 0x017f, RES_XML_RESOURCE_MAP_TYPE = 0x0180, RES_TABLE_PACKAGE_TYPE = 0x0200, RES_TABLE_TYPE_TYPE = 0x0201, RES_TABLE_TYPE_SPEC_TYPE = 0x0202 }; //當前這個chunk的類型 uint16_t type; //當前這個chunk的頭部大小 uint16_t headerSize; //當前這個chunk的大小 uint32_t size; };
代碼段3
Resources.arsc文件的最開始是整個文件的header,結構是ResTable_header:
struct ResTable_header { struct ResChunk_header header; // The number of ResTable_package structures. uint32_t packageCount; };
代碼段4 可以看到header就是一個chunk,以ResChunk_header結構開頭來描述這個chunk。resources.arsc文件的header內容如下圖中選中部分:
圖3
圖中選中的部分就是header,可以看到類型是0x0002,對應類型是RES_TABLE_TYPE,headerSize是0x0c,整個chunk的大小也就是文件的大小是0x019584,package的數量是1個。
緊接著是Global String Pool,全局字符串池,這也是Resources.arsc存在最重要的一個原因之一,就是把所有字符串放到這個池子裡,大家復用這些字符串,可以很大的減小APK包的尺寸。從圖1和圖2可以看到後面還有兩個字符串池,那麼什麼字符串會放到這個全局字符串池中呢?所有的資源文件的路徑名,以及資源文件中所定義的資源的值,比如代碼段1中的Cert和Hello world!都存在這裡。
字符串池的結構體如下:
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; };
代碼段5 對應的二進制內容如下圖選中部分:
圖4
從圖中可以看到類型是0x0001,對應代碼段3中RES_STRING_POOL_TYPE,整個chunk的大小是0x919C,stringCount是0x03E1,styleCount是0,flags是0x0100即UTF8格式,stringsStart即字符串相對頭部起始位置的偏移是0x0FA0。
從圖2中可以看到緊接著header的是stringCount個字符串偏移數組,數組每一個元素記錄著每個字符串的起始位置相對於stringsStart的偏移。字符串池中每個UTF8格式字符串都是以字符串結束符0x00結束的,UTF16是0x0000。
style偏移數組與string是一樣的就不多說了,但這個style是干什麼的現在我還不清楚,以後知道了再更新。
下面要介紹重頭戲Package了。首先是一個package的header,結構體如下:
struct ResTable_package { struct ResChunk_header header; //包的ID,等於Package Id,一般用戶包的值Package Id為0X7F,系統資源包的Package Id為0X01。 uint32_t id; //包名稱 char16_t name[128]; //類型字符串資源池相對頭部的偏移 uint32_t typeStrings; //最後一個導出的Public類型字符串在類型字符串資源池中的索引,目前這個值設置為類型字符串資源池的元素個數。 uint32_t lastPublicType; //資源項名稱字符串相對頭部的偏移 uint32_t keyStrings; //最後一個導出的Public資源項名稱字符串在資源項名稱字符串資源池中的索引,目前這個值設置為資源項名稱字符串資源池的元素個數。 uint32_t lastPublicKey; };
代碼段6 圖4中全局字符串池的起始位置是0xC,而整個chunk的大小是0x919C,那麼package的起始位置就是兩者相加得到0x91A8,對應二進制內容如下圖選中部分:
圖5
從上圖可以看到chunk類型是0x0200,對應代碼段3中的RES_TABLE_PACKAGE_TYPE,id是0x7F(這與R.java中的每個資源id的最高字節是一樣的),這個package的名字是com.example.cert,類型字符串池typeStrings相對於package header起始位置的偏移是0x011C,類型字符串的個數是0x0C,資源項名稱字符串池keyStrings相對於package header起始位置的偏移是0x01C8,個數是0x01E1。
對於類型字符串池和資源項名稱字符串池的結構和內容我這裡就不貼出來了,結構和全局字符串池是一樣的。類型字符串池中存儲的是所有類型相關的字符串,比如attr,drawable,layout這些;而資源項名稱字符串池中存儲的是應用所有資源文件中的資源項名稱相關的字符串,比如代碼段1中的app_name,hello_world,action_settings。
今天先寫到這裡,明天再更新。。。
PraiseTextView說明我是將朋友圈分成了幾個獨立模塊單獨自定義的View,通過回調完成交互,耦合性算是非常低了,主要有以下及部分:1.評論布局(自定義TextV
前言 為了保證每周一篇的進度,又由於Vitamio新版本沒有發布, 決定推遲本地播放的一些功能(截圖、視頻時間、尺寸等),跳過直接寫在線播放部分的章節。從V
Person實體類復制代碼 代碼如下:package com.ljq.domain;public class Person { pri
Android中的控件的使用方式和iOS中控件的使用方式基本相同,都是事件驅動。給控件添加事件也有接口回調和委托代理的方式。今天這篇博客就總結一下Android中常用的基