項目地址:https://github.com/hust201010701/CheckResourceConflicthtml
在 Android 的平常開發中,咱們會使用到大量的第三方庫或者本身編寫的組件庫,這些依賴庫中資源加上主工程自己的資源,可能會發生同名衝突,會發生資源相互覆蓋的現象。java
因爲資源覆蓋不會有任何提示,並且只會在 APP 運行到相關代碼時暴露出來,若是測試不細緻的話,很容易把問題帶到線上,形成嚴重後果。在個人業務開發過程當中,就發生過兩起因爲資源覆蓋致使的實際問題:android
咱們有兩款 APP 分別記做 A 和 B,它們同時依賴了一個對話框組件記做 C,C中有個顏色資源名爲 @color/main_color
,B 項目在主工程中也同時聲明瞭一個同名的顏色,可是資源的值與組件 C 中不同,最終致使 B 項目中的對話框顯示效果異常。git
若是你認爲顏色資源被覆蓋最多隻影響顯示效果,並不算嚴重的話,那麼再來看這個佈局資源被覆蓋的問題,它是有可能引發崩潰的。 咱們有個已有的業務,相關程序都在主工程中,因爲一些需求,須要把這個業務相關的代碼遷移到一個獨立的組件庫中,咱們在遷移的過程當中,有些自定義View頁跟隨遷移了。可是因爲粗心緣由,主工程的原有資源並無刪除。當咱們使用佈局資源時,主工程中的 layout 佈局文件會覆蓋組件中的佈局文件,當咱們在 Java 文件中使用 findViewById
去綁定 id 時,佈局中的 View 的包名是老包名,而java 文件中的 View 的包名是新包名,在運行時就會發生類型轉換失敗的崩潰。舉個例子,主工程中有個自定義控件 package.a.view.CircleWithBorderView
,遷移到 A 組件中後包名發生變化,變成了 package.b.view.CircleWithBorderView
,在組件 A 某個佈局 activity_main.xml 中使用到了這個控件,並且對應的java文件中,咱們使用的都是 package.b.view.CircleWithBorderView
包下的自定義控件,沒有任何問題。可是在接入主工程時,因爲主工程的 activity_main.xml
並無刪除,而主工程中 activity_main.xml
使用的 package.a.view.CircleWithBorderView
下的自定義控件,這樣在運行時就會發生崩潰現象。github
<package.a.view.CircleWithBorderView
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
複製代碼
在瞭解同名資源被覆蓋,會產生一些難以及時發現的問題時,咱們須要思考如何避免這種問題的產生。瀏覽器
若是咱們可以在編譯期間,把全部衝突的資源找出來,告訴開發者有哪些資源同名可是內容不一樣,讓開發者在最終發版前將這些同名資源處理掉。處理的方式能夠有兩種:bash
資源按照類型來區分,能夠分爲兩種,區分的規則是文件類型整個文件佔用一個 R.id
,而值類型的資源每一項元素佔用一個 R.id
:app
那程序須要怎麼區分某個文件是不是值類型仍是文件類型呢?maven
咱們能夠根據這個這個文件所在目錄的目錄名是否 values
或者 以values-
開頭進行判斷。佈局
要判斷一個資源存在衝突,咱們須要處理兩件事:一是給資源肯定惟一的id,二是如何判斷資源的值是否發生衝突;
資源肯定惟一的id
對於文件資源,舉個例子, res/drawable-xxhdpi/a.png
與 res/drawable-xxxhdpi/a.png
即便文件不一樣也不會發生衝突,可是 A 組件中的 res/drawable-xxhdpi/a.png
和 B 組件中的 res/drawable-xxhdpi/a.png
會發生衝突,因此對於文件資源,惟一id能夠肯定爲 file@文件所在文件夾名稱/文件名
, 如file@layout/activity_main.xml
。
對於值類型的資源,舉個例子, res/values/colors.xml
與 res/values/values.xml
中聲明的 main_color
會發生衝突,而 res/values/colors.xml
與 res/values-v19/colors.xml
中的 main_color
不會發生衝突,因此對於值類型資源,與所在文件名無關,惟一id能夠肯定爲 alue@資源所在文件的上層文件夾名稱/資源名
, 如value@values/main_color
。
資源值是否衝突
對於文件類型資源,咱們能夠計算文件對於的md5是否相等來判斷是否衝突; 對於值類型資源,咱們能夠直接比較值的內容是否同樣。
咱們的方案已經很清晰了。萬事具有隻欠東風,只須要在編譯期間可以獲取全部的資源文件就能夠了。
Android Gradle Plugin 3.3 版本及其以上,提供了一個 API 能夠獲取編譯全部的資源文件。
variants.forEach { variant ->
variant as BaseVariantImpl
// files 即對應全部的編譯資源
def files = variant.allRawAndroidResources.files
複製代碼
我使用這套 API 開發了一套編譯期間自動查找全部衝突資源的插件,項目地址: CheckResourceConflict github.com/hust2010107…
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.orzangleli:checkresourceconflict:0.0.1'
}
}
複製代碼
在你的項目的 build.gradle 文件中增長:
apply plugin: 'CheckResourcePrefixPlugin'
有兩種方式能夠運行資源衝突檢測任務
check
目錄下生產若干個任務,能夠根據須要執行相關任務,checkResourceConfig {
// 是否開啓插件
enabled false
// 運行完後自動使用默認瀏覽器打開html結果進行預覽
autoPreviewResult true
// 輸出文檔的目錄
outputDir "./out"
// 資源衝突白名單
whiteListFile "../checkResource/whitelist.lxc"
// 郵件相關配置
emailConfig {
// 是否開啓郵件發送功能
needSendEmail true
// 郵箱指定的 email host
host ""
// 發件人郵箱
fromEmail ""
// 收件人郵箱 支持多人
toEmailList ("", "")
// 郵箱帳號
account ""
// 郵箱受權第三方客戶端的受權碼
authorizationCode ""
}
}
複製代碼
# 「#」開頭表示該行爲註釋
# 這個文件能夠聲明檢測資源衝突的白名單資源
# 文件資源的格式爲 file@文件所在文件夾名稱/文件名, 如 file@layout/activity_main.xml
# 值類型資源的格式爲 value@資源所在文件的上層文件夾名稱/資源名, 如 value@values/colorPrimary
value@values/error_color_material_dark
file@layout/notification_action_tombstone.xml
複製代碼
其中 value@values/error_color_material_dark
和 file@layout/notification_action_tombstone.xml
表示的是資源id,能夠從輸出的html文檔中找到資源對應的id。
Tips: