目前 Android 並不支持 xml 文件中顏色與透明度分開定義,若是想用帶透明度的顏色值,只能在 colors.xml 文件中定義一個新色值。好比,有一個顏色名字叫 N900,定義以下:android
<color name="N900">#1F2329</color>
複製代碼
當我須要一個 50% 透明度 N900 的顏色時,只能本身定義再一個色值:git
<color name="N900_alpha_50">#7F1F2329</color>
複製代碼
因而,colors.xml 內就出現不少不規則顏色,就像下面這樣子:github
而且還會繼續增長這些不規則顏色。當下次換顏色時,這些帶透明度的顏色每個都須要更換,維護起來十分麻煩。而且,這些顏色目前所在的module已經打成aar,每次若是須要增長新的顏色,都須要從新打包aar上傳,十分影響開發效率。緩存
爲了解決上述問題,開發了ResKitPlugin 插件,在編譯時期動態替換顏色,支持顏色與透明度分開定義。app
基本思路是在aapt最終打包前,替換資源編譯後生成的文件,使 aapt 最後打包時使用的資源二進制文件內的相應的顏色值已經帶上了透明度。先上一張圖:性能
下面詳細介紹:測試
build/intermediates/res/merged/{*flavor*}/{buildType}
目錄下。build/intermediates/incremental/merge${variantName}Resources/merged.dir/values/values.xml
內,這裏麪包括了定義的顏色資源。最開始,drawable_a.xml.flat 文件是由(代碼-1)編譯生成:gradle
<solid android:color="@color/N900" android:alpha="0.5" />
複製代碼
經過咱們的處理,drawable_a.xml.flat 文件 變成了由(代碼-2)編譯生成:優化
<solid android:color="@color/reskit_tmp_color_N900_alpha_0_5" />
複製代碼
對於硬編碼的顏色,會直接進行以下轉換:ui
<solid android:color="#1F2329" android:alpha="0.5" />
|
\|/
<solid android:color="#7F1F2329"/>
複製代碼
經過這樣的處理,咱們的顏色在運行時就擁有了透明度。下面介紹具體處理的步驟。
經過computeResourceSetList去獲取到全部參與編譯的資源文件,而後修改源碼,編譯生成新的.flat文件,並替換原來的.flat文件。分爲如下幾步:
build/intermediates/incremental/merge${variantName}Resources/merged.dir/values/values.xml
內挑出全部的顏色定義並生成colors.xml,爲後續根據id找顏色值提供基礎。android:color ="#1F2329"
,則直接修改值便可。android:color="@color/lkui_N900"
,會生成一個新key,而後將其替換爲新key,並把這個新key與顏色的對應關係存在一個Map裏,待新key 所有生成後,統一將新key 與顏色的對應關係寫入build/intermediates/incremental/merge${variantName}Resources/merged.dir/values/values.xml
文件,參與後續編譯。Map<String, Map<String, String>>
資源文件的父文的名字 + 「/」 + 資源文件的名字 : [ 原始文件全路徑 : 處理了alpha 後的新文件的全路徑 ]
舉例:
drawable/aab.xml : [/Users/guoxiao/ResPluginDemo/app/src/main/res/drawable/aab.xml : /Users/guoxiao/ResPluginDemo/app/build/coloralpha/res/drawable/aab.xml]
複製代碼
intermediates/res/merged/{*flavor*}/{buildType}
目錄下,對於重名文件來講,系統究竟使用了哪一個文件去參與編譯的。這裏使用的方法是:
intermediates/res/merged/{flavor}/{buildType}
目錄下的同名文件作md5比較,比較結果相同的,說明找到了系統編譯使用的文件intermediates/res/merged/{flavor}/{buildType}
目錄下的文件的md5和上次一致,則直接使用上次的緩存的.flat文件進行替換咱們緩存了上次顏色處理獲得的.flat文件,對於本次顏色處理:
目前的實現並非最初的方案,測試時,替換7個文件,耗時從30s ,到20s, 最終優化到如今的5s左右。下面介紹這期間經歷的幾個方案:
這種方式,一次連接耗時7s左右,並且爲了連接,還須要作一些壓縮與解壓的文件操做,壓縮所有.flat文件須要7秒多,反編譯.ap_又須要7秒多,最終一次顏色處理下來,耗時30s左右。
這種方式,能夠去除對系統生成的.ap_文件的修改,耗時20s左右。
前兩種方案耗時,主要是進行連接和反編譯,從而獲得源碼。因而思考,有沒有可能不經過連接和反編譯的方式來獲得源碼,最終有個方案3。
最終耗時5s 左右。
處理完成後,系統的processDebugResources就會使用咱們處理過的.flat文件。
mergeResource Task 若使用了 gradle 的構建緩存(運行該Task 會輸出 FROM_CACHE) ,會缺失這個Task的中間產物,即merged.dir文件夾爲空。
針對這種狀況,咱們每次在MergeResource執行前判斷是否有merged.dir,若沒有,不讓它走 FROM_CACHE。
具體作法是:臨時生成一個資源文件,致使緩存失效,這樣就會觸發mergeResources走一遍,而後 在mergeResource以後刪除咱們臨時生成的資源文件。
因爲咱們能夠拿到參與編譯的全部資源文件,也能夠修改替換系統編譯產生的文件。這兩個能力,提供了巨大的想象空間,如:
文章做者:國霄(EE Lark Android 團隊)
邀請優秀的人一塊兒作有挑戰的事兒!字節跳動效率工程團隊研發職位招聘,想成爲技術大牛的夥伴快點進來看!職位介紹