真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Android資源熱修復(fù)技術(shù)怎么應(yīng)用

這篇文章主要介紹“Android資源熱修復(fù)技術(shù)怎么應(yīng)用”的相關(guān)知識(shí),小編通過實(shí)際案例向大家展示操作過程,操作方法簡單快捷,實(shí)用性強(qiáng),希望這篇“Android資源熱修復(fù)技術(shù)怎么應(yīng)用”文章能幫助大家解決問題。

創(chuàng)新互聯(lián)長期為上千余家客戶提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊(duì)從業(yè)經(jīng)驗(yàn)10年,關(guān)注不同地域、不同群體,并針對(duì)不同對(duì)象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺(tái),與合作伙伴共同營造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為海拉爾企業(yè)提供專業(yè)的成都網(wǎng)站設(shè)計(jì)、做網(wǎng)站,海拉爾網(wǎng)站改版等技術(shù)服務(wù)。擁有十余年豐富建站經(jīng)驗(yàn)和眾多成功案例,為您定制開發(fā)。

一、普遍的實(shí)現(xiàn)方式

目前市面上的很多資源熱修復(fù)方案基本上都是參考了 Instant Run的實(shí)現(xiàn)。

簡要說來,Instant Run中的資源熱修復(fù)分為兩步:

1.構(gòu)造一個(gè)新的 AssetManager,并通過反射調(diào)用 addAssetPath,把這個(gè)完 整的新資源包加入到AssetManager中。這樣就得到了一個(gè)含有所有新資源的 AssetManager。

2.找到所有之前引用到原有 AssetManager的地方,通過反射,把引用處替換 為 AssetManager。

一個(gè) Android 進(jìn)程只包含一個(gè) ResTable, ResTable 的成員變量 mPackageGroups 就是所有解析過的資源包的集合。任何一個(gè)資源包中都含有 resources.arsc,它記錄了所有資源的id分配情況以及資源中的所有字符串。這些信息是以二進(jìn)制方式存儲(chǔ)的。底層的AssetManager做的事就是解析這個(gè)文件,然后把相關(guān)信息存儲(chǔ)到 mPackageGroups 里面。

二、資源文件的格式

整個(gè) resources.arse 文件,實(shí)際上是由一個(gè)個(gè) ResChunk (以下簡稱 chunk) 拼接起來的。從文件頭開始,每個(gè) chunk 的頭部都是一個(gè) ResChunk_header結(jié)構(gòu),它指示了這個(gè)chunk的大小和數(shù)據(jù)類型。

Android資源熱修復(fù)技術(shù)怎么應(yīng)用

通過ResChunk_header中的type成員,可以知道這個(gè)chunk是什么類型, 從而就可以知道應(yīng)該如何解析這個(gè)chunko

解析完一個(gè) chunk 后,從這個(gè) chunk + size的位置開始,就可以得到下一個(gè) chunk 起始位置,這樣就可以依次讀取完整個(gè)文件的數(shù)據(jù)內(nèi)容。

一般來說,一個(gè) resources.arsc 里面包含若干個(gè)package,不過默認(rèn)情況下, 由打包工具aapt 打出來的包只有一個(gè) package。這個(gè) package里包含了 app中的 所有資源信息。

資源信息主要是指每個(gè)資源的名稱以及它對(duì)應(yīng)的編號(hào)。我們知道,Android中的每個(gè)資源,都有它唯一的編號(hào)。編號(hào)是一個(gè) 32 位數(shù)字,用十六進(jìn)制來表示就是0xPPTTEEEE。PP 為 package id, TT 為 type id, EEEE 為 entry id。

它們代表什么?在 resources.arse 里是以怎樣的方式記錄的呢?

  • 對(duì)于 package id,每個(gè) package 對(duì)應(yīng)的是類型為 RES_TABLE_PACKAG E_ TYPE 的 ResTable_package 結(jié)構(gòu)體,ResTable_package 結(jié)構(gòu)體的 id 成員變量就表示它的 package id。

  • 對(duì)于 type id,每個(gè)type對(duì)應(yīng)的是類型為 RES_TABLE_TYPE_SPEC_ TYPE 的 ResTable_typeSpec 結(jié)構(gòu)體。它的id成員變量就是type id。但是,該type id 具體對(duì)應(yīng)什么類型,是需要到package chunk 里的 Type String Pool 中去解析得到的。比如 Type String Pool 中依次有 attr、 drawablex mipmap、layout 字符串。就表示 attr 類型的 type id 為 1, drawable 類型的 type id 為 2, mipmap 類型的 type id 為 3, layout 類型的type id 為 4。所以,每個(gè) type id對(duì)應(yīng)了 Type String Pool里的字符順序 所指定的類型。

  • 對(duì)于 entry id,每個(gè) entry表示一個(gè)資源項(xiàng),資源項(xiàng)是按照排列的先后順序 自動(dòng)被標(biāo)機(jī)編號(hào)的。也就是說,一個(gè)type里按位置出現(xiàn)的第一個(gè)資源項(xiàng),其 entry id 為0x0000,第二個(gè)為 0x0001,以此類推。因此我們是無法直接指定entry id的,只能夠根據(jù)排布順序決定。資源項(xiàng)之間是緊密排布的,沒有空隙,但是可以指定資源項(xiàng)為ResTable_type::NO_ENTRY來填入一個(gè)空資源。

舉個(gè)例子,我們隨便找個(gè)帶資源的 apk,用 aapt解析一下,看到其中的一行是:

$ aapt d resources app-debug.apk

......

spec resource 0x7f040019 com.taobao.patch.demo:layout/activity_main: flags=0x00000000

......

這就表示,activity_main.xml 這個(gè)資源的編號(hào)是 0x7f040019。它的 package id 是 0x7f,資源類型的id為0x04, Type String Pool里的第四個(gè)字符串正是 layout 類型,而 0x04 類型的第 0x0019 個(gè)資源項(xiàng)就是 activity_main 這個(gè)資源。

三、運(yùn)行時(shí)資源的解析

默認(rèn)由 Android SDK 編出來的 apk,是由 aapt 具進(jìn)行打包的,其資源包的 package id 就是 0x7f。

系統(tǒng)的資源包,也就是 framework-res.jar, package id 為 0x01。

在走到 app的第一行代碼之前,系統(tǒng)就已經(jīng)幫我們構(gòu)造好一個(gè)已經(jīng)添加了安裝包資源的 AssetManager 了。

Android資源熱修復(fù)技術(shù)怎么應(yīng)用

因此,這個(gè) AssetManager里就已經(jīng)包含了系統(tǒng)資源包以及 app的安裝包,就是 package id 為 0x01 的 framework-res.jar 中的資源和 package id 為 0x7f 的 app 安裝包資源。

如果此時(shí)直接在原有 AssetManager 上繼續(xù) addAssetPath的完整補(bǔ)丁包的 話,由于補(bǔ)丁包里面的package id 也是 0x7f,就會(huì)使得同一個(gè) package id的包被 加載兩次。這會(huì)有怎樣的問題呢?

在 Android L 之后,這是沒問題的,他會(huì)默默地把后來的包添加到之前的包的同—個(gè) PackageGroup 下面。

而在解析的時(shí)候,會(huì)與之前的包比較同一個(gè) type id所對(duì)應(yīng)的類型,如果該類型 下的資源項(xiàng)數(shù)目和之前添加過的不一致,會(huì)打出一條warning log,但是仍舊加入到該類型的TypeList 中。

在獲取某個(gè) Type的資源時(shí),會(huì)從前往后遍歷,也就是說先得到原有安裝包里 的資源,除非后面的資源的config比前面的更詳細(xì)才會(huì)發(fā)生覆蓋。而對(duì)于同一個(gè) config 而言,補(bǔ)丁中的資源就永遠(yuǎn)無法生效了。所以在 Android L以上的版本,在原有AssetManager 上加入補(bǔ)丁包,是沒有任何作用的,補(bǔ)丁中的資源無法生效。

而在 Android 4.4 及以下版本,addAssetPath只是把補(bǔ)丁包的路徑添加到 了 mAssetPath中,而真正解析的資源包的邏輯是在app第一次執(zhí)行 AssetManager::getResTable 的時(shí)候。

而在執(zhí)行到加載補(bǔ)丁代碼的時(shí)候,getResTable已經(jīng)執(zhí)行過了無數(shù)次了。這是因?yàn)榫退阄覀冎皼]做過任何資源相關(guān)操作,Android framework里的代碼也會(huì)多 次調(diào)用到那里。所以,以后即使是addAssetPath,也只是添加到了 mAssetPath, 并不會(huì)發(fā)生解析。所以補(bǔ)丁包里面的資源是完全不生效的!

所以,像 Instant Run 這種方案,一定需要一個(gè)全新的 AssetManager時(shí),然后再加入完整的新資源包,替換掉原有的AssetManager。

四、另辟蹊徑的資源修復(fù)方案

而一個(gè)好的資源熱修復(fù)方案是怎樣的呢?

首先,補(bǔ)丁包要足夠小,像直接下發(fā)完整的補(bǔ)丁包肯定是不行的,很占用空間。

而像有些方案,是先進(jìn)行 bsdiff,對(duì)資源包做差量,然后下發(fā)差量包,在運(yùn)行時(shí) 合成完整包再加載。這樣確實(shí)減小了包的體積,但是卻在運(yùn)行時(shí)多了合成的操作,耗費(fèi)了運(yùn)行時(shí)間和內(nèi)存。合成后的包也是完整的包,仍舊會(huì)占用磁盤空間。

而如果不采用類似 Instant Run 的方案,市面上許多實(shí)現(xiàn),是自己修改aapt, 在打包時(shí)將補(bǔ)丁包資源進(jìn)行重新編號(hào)。這樣就會(huì)涉及到修改 Android SDK工具包, 即不利于集成也無法很好地對(duì)將來的aapt 版本進(jìn)行升級(jí)。

針對(duì)以上幾個(gè)問題,一個(gè)好的資源熱修復(fù)方案,既要保證補(bǔ)丁包足夠小,不在 運(yùn)行時(shí)占用很多資源,又要不侵入打包流程。我們提出了一個(gè)目前市面上未曾實(shí)現(xiàn) 的方案。

簡單來說,我們構(gòu)造了一個(gè) package id 為 0x66的資源包,這個(gè)包里只包含改變了的資源項(xiàng),然后直接在原有AssetManager 中 addAssetPath 這個(gè)包。然后就可以了。真的這么簡單?

沒錯(cuò)!由于補(bǔ)丁包的 package id 為 0x66,不與目前已經(jīng)加載的 0x7f沖突,因 此直接加入到已有的AssetManager中就可以直接使用了。補(bǔ)丁包里面的資源,只包含原有包里面沒有而新的包里面有的新增資源,以及原有內(nèi)容發(fā)生了改變的資源。

而資源的改變包含增加、減少' 修改這三種情況,我們分別是如何處理的呢?

  • 對(duì)于新增資源,直接加入補(bǔ)丁包,然后新代碼里直接引用就可以了,沒什么好說的。

  • 對(duì)于減少資源,我們只要不使用它就行了,因此不用考慮這種情況,它也不影響補(bǔ)丁包。

  • 對(duì)于修改資源,比如替換了一張圖片之類的情況。我們把它視為新增資源, 在打入補(bǔ)丁的時(shí)候,代碼在引用處也會(huì)做相應(yīng)修改,也就是直接把原來使用舊資源 id 的地方變?yōu)樾?id。

用一張圖來說明補(bǔ)丁包的情況,是這樣的:

Android資源熱修復(fù)技術(shù)怎么應(yīng)用

圖中綠線表示新增資源。紅線表示內(nèi)容發(fā)生修改的資源。黑線表示內(nèi)容沒有變 化,但是id 發(fā)生改變的資源。x 表示刪除了的資源。

4.1、新增的資源及其導(dǎo)致 id 偏移

可以看到,新的資源包與舊資源包相比,新增了 holo_grey 和 dropdn_item2 資源,新增的資源被加入到 patch中。并分配了 0x66 開頭的資源 id。

而新增的兩個(gè)資源導(dǎo)致了在它們所屬的 type 中跟在它們之后的資源 id發(fā)生了 位移。比如 holojight, id 由 0x7f020002 變?yōu)?0x7f020003,而 abc_dialog 由 0x7f030004 變?yōu)?0x7f030003。新資源插入的位置是隨機(jī)的,這與每次 aapt打包 時(shí)解析xml 的順序有關(guān)。發(fā)生位移的資源不會(huì)加入 patch,但是在 patch的代碼中會(huì)調(diào)整id 的引用處。

比如說在代碼里,我們是這么寫的

imageView.setImageResource(R.drawable.holo_light);

這個(gè) R.drawable.holojight 是一個(gè)int 值,它的值是 aapt指定的,對(duì)于開發(fā)者 透明,即使點(diǎn)進(jìn)去,也會(huì)直接跳到對(duì)應(yīng)res/drawable/holo_light.jpg,無法查看。不過可以用反編譯工具,看到它的真實(shí)值是0x7f020002。所以這行代碼其實(shí)等價(jià)于:

imageView.setImageResource(0x7f020002);

而當(dāng)打出了一個(gè)新包后,對(duì)開發(fā)者而言,holojight的圖片內(nèi)容沒變,代碼引用處也沒變。但是新包里面,同樣是這句話,由于新資源的插入導(dǎo)致的id改變,對(duì)于 R.drawable.holojight 的引用已經(jīng)變成了:

imageView.setImageResource(0x7f020003);

但實(shí)際上這種情況并不屬于資源改變,更不屬于代碼的改變,所以我們?cè)趯?duì)比新舊代碼之前,會(huì)把新包里面的這行代碼修正回原來的id。

imageView.setImageResource(0x7f020002);

然后再進(jìn)行后續(xù)代碼的對(duì)比。這樣后續(xù)代碼對(duì)比時(shí)就不會(huì)檢測(cè)到發(fā)生了改變。

4.2、內(nèi)容發(fā)生改變的資源

而對(duì)于內(nèi)容發(fā)生改變的資源(類型為 layout 的 activity_main,這可能是我們修 改了 activity_main.xml 的文件內(nèi)容。還有類型為 string 的 no,可能是我們修改了這個(gè)字符串的值),它們都會(huì)被加入到 patch 中,并重新編號(hào)為新 id。而相應(yīng)的代碼,也會(huì)發(fā)生改變,比如,

setContentView(R.layout.activity_main);

實(shí)際上也就是

setContentView(0x7f030000);

在生成對(duì)比新舊代碼之前,我們會(huì)把新包里面的這行代碼變?yōu)?/p>

setContentView(0x6 6020000);

這樣,在進(jìn)行代碼對(duì)比時(shí),會(huì)使得這行代碼所在函數(shù)被檢測(cè)到發(fā)生了改變。于是相應(yīng)的代碼修復(fù)會(huì)在運(yùn)行時(shí)發(fā)生,這樣就引用到了正確的新內(nèi)容資源。

4.3、刪除了的資源

對(duì)于刪除的資源,不會(huì)影響補(bǔ)丁包。

這很好理解,既然資源被刪除了,就說明新的代碼中也不會(huì)用到它,那資源放在那里沒人用,就相當(dāng)于不存在了。

4.4、對(duì)于type的影響

可以看到,由于 type0x01 的所有資源項(xiàng)都沒有變化,所以整個(gè) type0x01資源都沒有加入到patch 中。這也使得后面的 type 的 id 都往前移了一位。因此 Type String Pool 中的字符串也要進(jìn)行修正,這樣才能使得 0x01 的 type 指向 drawable, 而不是原來的 attr。

所以我們可以看到,所謂簡單,指的是運(yùn)行時(shí)應(yīng)用patch變的簡單了。

而真正復(fù)雜的地方在于構(gòu)造 patch 。我們需要把新舊兩個(gè)資源包解開,分別解析 其中的resources.arsc 文件,對(duì)比新舊的不同,并將它們重新打成帶有新 package id 的新資源包。這里補(bǔ)丁包指定的 package id 只要不是 0x7f 和 0x01就行,可以是 任意0x7f 以下的數(shù)字,我們默認(rèn)把它指定為 0x66。

構(gòu)造這樣的補(bǔ)丁資源包,需要對(duì)整個(gè)resources.arsc的結(jié)構(gòu)十分了解,要對(duì)二 進(jìn)制形式的一個(gè)一個(gè)chunk進(jìn)行解析分類,然后再把補(bǔ)丁信息一個(gè)一個(gè)重新組裝成 二進(jìn)制的chunk。這里面很多工作與 aapt做的類似,實(shí)際上開發(fā)打包工具的時(shí)候也是參考了很多aapt和系統(tǒng)加載資源的代碼。

五、更優(yōu)雅地替換AssetManager

對(duì)于 Android L 以后的版本,直接在原有 AssetManager 上應(yīng)用 patch就行 了。并且由于用的是原來的AssetManager,所以原先大量的反射修改替換操作就 完全不需要了,大大提高了加載補(bǔ)丁的效率。

但之前提到過,在 Android KK 和以下版本,addAssetPath是不會(huì)加載資源 的,必須重新構(gòu)造一個(gè)新的AssetManager 并加入 patch,再換掉原來的。那么我們不就又要和Instant Run —樣,做一大堆兼容版本和反射替換的工作了嗎?

對(duì)于這種情況,我們也找到了更優(yōu)雅的方式,不需要再如此地大費(fèi)周章。

Android資源熱修復(fù)技術(shù)怎么應(yīng)用

明顯,這個(gè)是用來銷毀 AssetManager并釋放資源的函數(shù),我們來看看它具體做了什么吧。

Android資源熱修復(fù)技術(shù)怎么應(yīng)用

可以看到,首先,它析構(gòu)了 native 層的 AssetManager,然后把 java層的 AssetManager 對(duì) native 層的 AssetManager 的引用設(shè)為空。

Android資源熱修復(fù)技術(shù)怎么應(yīng)用

native 層的 AssetManager 析構(gòu)函數(shù)會(huì)析構(gòu)它的所有成員,這樣就會(huì)釋放之前加載了的資源。

而現(xiàn)在,java 層的 AssetManager 已經(jīng)成為了空殼。我們就可以調(diào)用它的 init 方法,對(duì)它重新進(jìn)行初始化了!

Android資源熱修復(fù)技術(shù)怎么應(yīng)用

這同樣是個(gè)native方法,

Android資源熱修復(fù)技術(shù)怎么應(yīng)用

這樣,在執(zhí)行 init 的時(shí)候,會(huì)在 native層創(chuàng)建一個(gè)沒有添加過資源,并且 mResources 沒有初始化的的 AssetManager。然后我們?cè)賹?duì)它進(jìn)行 addAssetPath,之后由于 mResource 沒有初始化過,就可以正常走到解析 mResources的邏輯,加載所有此時(shí)add進(jìn)去的資源了 !

由于我們是直接對(duì)原有的 AssetManager進(jìn)行析構(gòu)和重構(gòu),所有原先對(duì) AssetManager 對(duì)象的引用是沒有發(fā)生改變的,這樣,就不需要像 Instant Run那樣進(jìn)行繁瑣的修改了。

順帶一提,類似 Instant Run 的完整替換資源的方案,在替換 AssetManager這一步,也可以采用我們這種方式進(jìn)行替換,省時(shí)省力又省心。

關(guān)于“Android資源熱修復(fù)技術(shù)怎么應(yīng)用”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí),可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,小編每天都會(huì)為大家更新不同的知識(shí)點(diǎn)。


網(wǎng)頁標(biāo)題:Android資源熱修復(fù)技術(shù)怎么應(yīng)用
分享路徑:http://weahome.cn/article/ijpjhi.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部