1. 程式人生 > >Android 顏色色值與 alpha 分離解決方案

Android 顏色色值與 alpha 分離解決方案

一、背景

目前 Android 並不支援 xml 檔案中顏色與透明度分開定義,如果想用帶透明度的顏色值,只能在 colors.xml 檔案中定義一個新色值。比如,有一個顏色名字叫 N900,定義如下:

<color name="N900">#1F2329</color>

當我需要一個 50% 透明度 N900 的顏色時,只能自己定義再一個色值:

<color name="N900_alpha_50">#7F1F2329</color>

於是,colors.xml 內就出現很多不規則顏色,就像下面這樣子:

並且還會繼續增加這些不規則顏色。當下次換顏色時,這些帶透明度的顏色每一個都需要更換,維護起來十分麻煩。並且,這些顏色目前所在的module已經打成aar,每次如果需要增加新的顏色,都需要重新打包aar上傳,十分影響開發效率。

為了解決上述問題,開發了ResKitPlugin 外掛,在編譯時期動態替換顏色,支援顏色與透明度分開定義。

二、技術原理

基本思路是在aapt最終打包前,替換資源編譯後生成的檔案,使 aapt 最後打包時使用的資源二進位制檔案內的相應的顏色值已經帶上了透明度。先上一張圖:

下面詳細介紹:

(一).相關背景知識介紹

  1. 目前我們的資源編譯使用的都是aapt2, aapt2 編譯資源分兩步:
  • 編譯:將資原始檔編譯為.flat 檔案
  • 連結:將.flat 檔案連結為最終的二進位制資原始檔.ap_
  1. gradle 在編譯apk 時,是執行一系列Task,並且有些Task 是有嚴格先後順序的。
  2. 我們需要關心的是其中兩個和資源編譯相關的Task :
  • mergeDebugResources : 這個Task 是負責收集所有的資原始檔並使用aapt2編譯成.flat檔案,放在build/intermediates/res/merged/{*flavor*}/{buildType}目錄下。
  • processDebugResources:這個Task是負責 使用aapt2 將.flat檔案 連結為最終的二進位制資原始檔
  • mergeDebugResources 與 processDebugResource有嚴格的先後順序,先執行mergeDebugResources,後執行 processDebugResource
  1. gradle 的API支援 “改變Task的執行順序" 的操作
  2. MergeResources 類的 computeResourceSetList 方法可以獲取編譯要用的全部 res 路徑
  3. mergeDebugResources 執行後,所有res/values目錄下的內容,都會合併到一個檔案內,放在build/intermediates/incremental/merge${variantName}Resources/merged.dir/values/values.xml內,這裡麵包括了定義的顏色資源。

(二).實現步驟

  1. 首先,定義一個自己的Task,叫做handleAlphaColorTask,負責修改.flat檔案
  2. 通過gradle 的 API , 將handleAlphaColorTask 插入 mergeDebugResources 與 processDebugResources之間,這一步執行後,gradle 的編譯Task 的 呼叫順序如下:

  1. 在handleAlphaColorTask內處理mergeDebugResources生成的檔案,使其內部的顏色屬性帶上了透明度,例子如下:

最開始,drawable_a.xml.flat 檔案是由(程式碼-1)編譯生成:

   <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" />

對於硬編碼的顏色,會直接進行如下轉換:

  <solid android:color="#1F2329" android:alpha="0.5" />
              |
             \|/
  <solid android:color="#7F1F2329"/>

通過這樣的處理,我們的顏色在執行時就擁有了透明度。下面介紹具體處理的步驟。

(三). 顏色轉換的具體實現方式

通過computeResourceSetList去獲取到所有參與編譯的資原始檔,然後修改原始碼,編譯生成新的.flat檔案,並替換原來的.flat檔案。分為以下幾步:

  1. 通過反射呼叫MergeResources 的 computeResourceSetList 方法,獲取參與編譯的全部 res 資料夾路徑,包括aar內的。
  2. build/intermediates/incremental/merge${variantName}Resources/merged.dir/values/values.xml內挑出所有的顏色定義並生成colors.xml,為後續根據id找顏色值提供基礎。
  3. 遍歷所有 res 資料夾下的xml 檔案。
  4. 通過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檔案,參與後續編譯。
  5. 遍歷期間,將需要修改的檔案,則儲存下來,放到一個Map裡,這麼做的目的是當出現同名資源時,提供篩選資源的資料。Map的定義如下:
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]
  1. 遍歷完成後,我們就得到了一個Map和在指定目錄下合併了alpha屬性的資源 原始碼檔案,接下來,需要處理重名資源
  2. 我們需要知道在intermediates/res/merged/{*flavor*}/{buildType}目錄下,對於重名檔案來說,系統究竟使用了哪個檔案去參與編譯的。這裡使用的方法是:
    • 在第5步獲得的map裡,可以知道有幾個重名檔案,全路徑是什麼,對應的新的修改後的檔案是什麼。
    • 編譯每一個重名的原檔案,生成.flat檔案,然後和intermediates/res/merged/{flavor}/{buildType}目錄下的同名檔案做md5比較,比較結果相同的,說明找到了系統編譯使用的檔案
    • 找到了系統編譯是用的檔案,我們就知道了最後我們應該編譯哪個新的修改了屬性的檔案去替換原.flat檔案
  3. 重名檔案處理完成後,就可以編譯新的修改後的檔案,產生新的.flat 檔案
  4. 用新的.flat 檔案 替換老的.flat 檔案。
  5. 如果本次編譯沒有修改資原始檔,即intermediates/res/merged/{flavor}/{buildType}目錄下的檔案的md5和上次一致,則直接使用上次的快取的.flat檔案進行替換

三.對編譯效能的影響

我們快取了上次顏色處理得到的.flat檔案,對於本次顏色處理:

  1. 未命中快取時:
    • 在CI 平臺上測試:替換了78個.flat檔案,用時13991ms,其中,替換資原始檔用時10135ms(未指定資料夾過濾,此時是最壞情況,全量遍歷), 編譯用時352ms。一個檔案的編譯時間4.5ms左右。
    • 本地測試:替換了78個.flat檔案,指定資料夾過濾 ,耗時6192ms,替換顏色3995m,編譯255ms;未指定資料夾過濾,耗時9179ms左右, 替換顏色 7392 ms, 編譯 184ms。
  2. 命中快取時,耗時500ms左右

四.探索的過程

目前的實現並不是最初的方案,測試時,替換7個檔案,耗時從30s ,到20s, 最終優化到現在的5s左右。下面介紹這期間經歷的幾個方案:

  1. 最初方案是在processDebugResources 後插入顏色處理Task:
    1. 獲取到系統連線後的資原始檔包.ap_
    2. 通過ApkTool反編譯.ap_檔案,得到原始碼
    3. 修改原始碼
    4. 重新編譯新修改的原始碼,獲得新的.flat檔案
    5. 將新的.flat檔案與系統編譯產生的.flat檔案一起參與連結,生成最終的二進位制檔案
    6. 用新獲得的由新的原始檔編譯連結而產生的二進位制檔案提替換.ap_檔案內的檔案
    7. 處理完成

這種方式,一次連結耗時7s左右,而且為了連結,還需要做一些壓縮與解壓的檔案操作,壓縮全部.flat檔案需要7秒多,反編譯.ap_又需要7秒多,最終一次顏色處理下來,耗時30s左右。

  1. 第二種方案,在mergeDebugResources 和 processDebugResources之間插入顏色處理Task:
    1. 獲取mergeDebugResources 後生成的.flat檔案,連結這些.flat檔案,生成tmp.ap_包
    2. 反編譯tmp.ap_,得到原始碼
    3. 修改原始碼
    4. 重新編譯修改了原始碼的檔案,得到.flat檔案
    5. 替換mergeDebugResources 生成的同名.flat檔案
    6. 處理完成

這種方式,可以去除對系統生成的.ap_檔案的修改,耗時20s左右。

​ 前兩種方案耗時,主要是進行連結和反編譯,從而得到原始碼。於是思考,有沒有可能不通過連結和反編譯的方式來得到原始碼,最終有個方案3。

  1. 第三種方案,依然是 在mergeDebugResources 和 processDebugResources之間插入顏色處理Task,區別於第二種方案,是通過反射MergeResource的 computeResourceSetList 得到所有參與編譯的資原始檔(/res):
    1. 通過反射呼叫computeResourceSetList 獲得所有的資源目錄
    2. 遍歷資源目錄,如果需要處理顏色,則拷貝一份新檔案,然後處理並儲存到指定目錄build/coloralpha/res
    3. 處理完成後,編譯build/coloralpha/res生成新的.flat檔案
    4. 用新的.flat檔案替換系統編譯生成的同名.flat檔案
    5. 處理完成

最終耗時5s 左右。

處理完成後,系統的processDebugResources就會使用我們處理過的.flat檔案。

五.特別注意的坑

mergeResource Task 若使用了 gradle 的構建快取(執行該Task 會輸出 FROM_CACHE) ,會缺失這個Task的中間產物,即merged.dir資料夾為空。

針對這種情況,我們每次在MergeResource執行前判斷是否有merged.dir,若沒有,不讓它走 FROM_CACHE。

具體做法是:臨時生成一個資原始檔,導致快取失效,這樣就會觸發mergeResources走一遍,然後 在mergeResource之後刪除我們臨時生成的資原始檔。

六.一些想法

由於我們可以拿到參與編譯的所有資原始檔,也可以修改替換系統編譯產生的檔案。這兩個能力,提供了巨大的想象空間,如:

  1. 可以做全域性資源查重,包括aar內的資源
  2. 可以做資源壓縮,如壓縮圖片
  3. 可以支援更多的類似color,alpha這樣的組合屬性的自定義
  4. 可以根據編譯環境修改string.xml 內容
  5. 編譯過程中自動收集生成面板包

最後

如果你看到了這裡,覺得文章寫得不錯就給個讚唄!歡迎大家評論討論!如果你覺得那裡值得改進的,請給我留言。一定會認真查詢,修正不足,定期免費分享技術乾貨。感興趣的小夥伴可以點一下關注哦。謝謝!

相關推薦

Android 顏色 alpha 分離解決方案

一、背景 目前 Android 並不支援 xml 檔案中顏色與透明度分開定義,如果想用帶透明度的顏色值,只能在 colors.xm

ARGB 顏色透明度對照表

1. ARGB 依次代表透明度(alpha)、紅色(red)、綠色(green)、藍色(blue)。2.透明度分為256階(0-255),計算機上用16進製表示為(00-ff)。透明就是0階,不透明就是

Android開發SDKGradle更新解決方案

1. 導讀 Android開發工具Eclipse與AndroidStudio 伴隨著Android版本提升,SDK與Gradle也得同時更新,但國內幾乎無法更新,購買了能更新工具Nydus,價格比較合理,在公司Windows 7使用穩定性ok,但在家裡M

android設定透明度

在專案中不少會碰到黑色透明度多少多少的,每次都要網上查,故以此作為一個記錄。 color.xml中的色值一般是六位,透明度可以在前面加兩位,例如 #ccf0f0f0,前面兩位表示透明度,後面六位是色值,cc表示透明度為80,那麼怎麼把80換算成cc吶 255*80%=204

Android studio 代碼突然報錯解決方案

cache dst roi image 技術分享 users 方案 file sdn 1.點擊File->Invalidate Caches / Restart... 2.重啟Gradle,清除緩存 3.Clean Project 4.關閉Android Stud

Android studio安裝配置常見問題及其解決方案

fault 末尾 clas 網絡 tools.jar 編輯 art 添加 http 來自:https://jingyan.baidu.com/article/e9fb46e170287a7520f7665c.html  Android studio 是目前

mvc報( 檢測到有潛在危險的 request.form )錯的解決方案

date es2017 als 技術分享 valid images editor alt ida 今天在做項目中遇到了報( 檢測到有潛在危險的 request.form 值 )錯,百度過後解決了該問題,出此問題主要還是因為提交的Form中有HTML字符串,例如你在TextB

網絡安全規範管理解決方案

理解 解決 phone 電影 感染病毒 校園 所有 命令 允許 問題:公司現在有計算機當前在線人數:140移動終端人數:52移動設備受信任情況: 受信任終端數:0 非受信任終端數:0每種終端分布情況: Android人數:32 iphone OS人數:20 。現帶寬共享20

【轉】Android性能優化-過度繪制解決方案

裏的 watermark == ++ 深度 getc 像素 多次 但是 轉載請註明出處:http://blog.csdn.net/a740169405/article/details/53896497 過度繪制: 屏幕上某一像素點在一幀中被重復繪制多次,就是過度繪制。

MYSQL讀寫分離解決方案:MariaDB MaxScale部署實錄

maxscaleMASTER(KING01)[root@king01 ~]# mysql -uroot -pabcd.1234 Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 173 S

MYSQL讀寫分離解決方案:MYCAT部署實錄

mysql mycat 讀寫分離 MASTER (KING01)[root@king01 ~]# mysql -uroot -pabcd.1234 mysql> show master status; +------------------+----------+--------------

【轉】MIUI8以及ViVO X9上在Android Studio運行出錯集及其解決方案

分享圖片 最新版本 ima 運行 測試 mage 安裝 pic com 最近用一臺紅米4高配版(6.0)以及ViVo X9(7.1)來做測試機,它是小米MIUI系統的最新版本MIUI8,我的AS是2.3版本,在網上查看了相關問題,在小米5和紅米note4x等配備了MI

Oracle系列-鎖表解鎖解決方案(操刀版)

ria 目錄 time terminal serial 終極 logo add 系統 【Oracle系列-鎖表與解鎖解決方案(大招版)】 --1查看被鎖的表 select b.owner,b.object_name,a.session_id,a.locked_mode fr

Oracle系列-鎖表解鎖解決方案(基礎版)

使用 因此 logo 才會 解決方案 date操作 order rest table 【Oracle鎖表查詢和解鎖解決方案】 一、了解原因(借鑒整理) 數據庫操作語句的分類 DDL:數據庫模式定義語言,關鍵字:createDML:數據操縱語言,關鍵字:Insert、del

基於Netty實現的Android 訊息推送(即時通訊)的解決方案

根據Netty框架實現訊息推送(即時聊天)功能. Netty框架,TCP長連線,心跳,阻塞訊息佇列,執行緒池處理訊息傳送, 基於Google ProtoBuf自定義的訊息協議, TCP粘包/拆包.... 客戶端通過TCP連線到伺服器,並建立TCP長連線;當伺服器端收到新訊息後通過TCP連線推送給

安卓遇到:Could not find runtime.aar (android.arch.lifecycle:runtime:1.0.3) 解決方案

專案啟動突然遇到: Could not find runtime.aar (android.arch.lifecycle:runtime:1.0.3). Searched in the following locations:     https://jcenter.bint

python--(socket粘包解決方案)

python--(socket與粘包解決方案) 一.socket: Socket 是任何一種計算機網路通訊中最基礎的內容。例如當你在瀏覽器位址列中輸入 http://www.cnblogs.com/ 時,你會開啟一個套接字,然後連線到 http://www.cnblogs.com/ 並讀取響應的頁面然

hosts檔案更新gmail訪問解決方案

大家都知道,在我們當前的網路,是無法訪問Google相關的服務網址的。這就意味著需要藉助《外網訪問軟體》(Fan * qiang),其實目前大部分的《外網訪問軟體》都被和諧了,就在前幾天,我才知道我經常使用的一款軟體也被和諧了(具體是哪款,在此就不提了)。那麼作為開發者的我們又不能不使用Google

發票識別發票查驗解決方案驗真經驗分享

增值稅發票識別、發票查驗 1.1發票識別系統 1、發票識別介面 伺服器端發票識別產品是中安開發的一款基於伺服器平臺的發票OCR識別服務程式,企業可將該識別服務部署在自有伺服器上(雲伺服器或本地伺服器),部署完成後,APP端、PC客戶端、web端、微信H5端等均可傳送識別請求,通過

Android SDK Manager國內無法更新的解決方案(親測有效)

轉自 Android SDK Manager國內無法更新的解決方案(親測有效) 現在由於GWF,google基本和咱們說咱見了,就給現在在做Android 或者想學習Android 的朋友帶來了諸多的不便,最簡單的就是Android SDK Manager 你無法更新了。 現在這裡有一