阿里熱修復如何精簡優化補丁資源?
阿里妹導讀:Sophix 是阿里推出的史上首個非侵入式移動熱更新解決方案,自去年推出已有一年的時間了。這一年來,阿里集團內外成千上萬的app踴躍接入。由於接入簡便,操作流暢,功能可靠,資源佔用極小,Sophix得到了廣大開發者的好評,網上也出現了大量開發者親身實踐的接入文章。
今天,我們選取了其中一個改進點——資源補丁的精簡優化,來詳細介紹一下Sophix 背後的技術。
這一年,關於Sophix熱修復我們陸續做了很多優化和改進,包括:
兼容最新Android版本至Android P dp3
JIT混合編譯的兼容
第三方加固的全面兼容
新增穩健接入方式
三星低版本特殊機型的兼容
補丁工具加速與初始化檢查
資源補丁深度優化
其他穩定性和性能的改進
在此基礎上,我們繼續改進了資源補丁,對resources.arsc中的字元串池進行裁剪,在不損耗運行時性能的情況下讓補丁包大小精簡到了極致。
resources.arsc結構
resources.arsc文件集結了所有帶id的資源項,其粗略概貌可以由以下這張圖展現:
這裡我們不需要太關注細節,只大致說明一下。每個arsc文件的開頭是一個類型為RES_TABLE_TYPE的ResTable_header結構頭,它指定了這個arsc文件所包含的其他結構,一般來說,只有一個全局字元串池和其他包資源塊,通常情況下(Android Studio默認編譯出來的)也僅有一個包,包id為0x7f,也就是說該包下的所有資源編號都是0x7fXXXXXX。
我們發現,每個包中還有兩個字元串池,分別是類型字元串池和資源項字元串池,這兩個字元串池和全局字元串池又有怎樣的關係呢?
類型字元串池只表示類型對應的名稱,像layout、string、color、integer等這些字元串,在arsc中只有一個類型id(比如0、1、2、3等)來表示他們。下面還有例子會詳細解釋。類型字元串池是比較獨立的,而且所佔空間很小,與其他結構也沒有太大關聯。
而資源項字元串池中存儲的是鍵字元串,與全局字符串池中存儲的是值字元串相對應。這裡的鍵和值就是我們通常理解中鍵值對(Key-Value)的鍵和值。之所以值字元串放在全局,應該是Android在設計之初打算在一個resources.arsc中的各個包中進行資源值的復用,然而由於目前默認只有一個0x7f包,自然也沒有復用這一說了。
只看這個結構會比較抽象,我們舉個例子,對於以下這個字元串資源:
假設這個資源在編譯進arsc之後,對應的id為0x7f010000
此時arsc中0x7f包中類型字元串池是
0x7f包中鍵字元串池是
arsc文件中的全局值字元串池是
那麼,在解析這個資源項的時候,由於它的包id為0x7f,就會找到這個0x7f包中來解析,類型id為0x01,表示類型字元串池的第0x01個字元串,也就是這裡的string類型,剩下的0x0000,表示該類型的第0個資源項。
我們從第0個資源項中解析出它是一個字元串類型的資源(這裡省略解析過程),並且得到他的key值為0x1,value的值為0x3。而從前面列出的信息中可以看到,鍵字元串池第1個字元串為app_name,值字元串池的第3個字元串為MyDemo。由此就可以得到這個MyDemo資源的完整信息了。
這裡我們可以看出,一個資源中占空間最大的正是字元串池,其他結構只是一些索引數字,所佔空間很小,因此如果能對字元串池進行精簡,將節省很多空間。
字元串池的構造
首先,我們得先弄清字元串池的結構是怎樣的,它的關鍵入口是ResStringPool_header這個結構頭,系統會以通過這個結構頭解析出完整的字元串池。
接下來我們從StringPool解析過程的系統源碼入手,探尋其具體的構造。核心解析邏輯在ResStringPool::setTo,簡單起見,以下代碼去掉了與主流程無關的檢查代碼:
這裡很清楚地展示了解析的過程,對ResStringPool的各個欄位進行賦值。
其中有幾個比較重要的欄位:
mEntries:字元串偏移數組指針
mStringPoolSize:字元串個數
mStrings:字元串塊的起始地址
mEntryStyles:樣式偏移數組指針
mStylePoolSize:樣式個數
mStyles:所有樣式的存儲的起始地址
mEntries與mEntryStyles保存是都是每個字元串在字元串塊中的偏移,字元串塊就是所有字元串的集合,以