Android?nonTransitiveRClass資源沖突問題淺析
前言
nonTransitiveRClass:非傳遞性 R 類的屬性,在 gradle.properties 文件里使用。
不少開發(fā)者可能聽過它,但了解可能僅限于是對(duì) R 文件做了優(yōu)化,甚至以為它可以解決資源沖突!但它到底做了什么優(yōu)化、能否解決資源沖突,則鮮少有機(jī)會(huì)去了解。
本文通過該屬性使用前后對(duì)比、在資源沖突場(chǎng)景下的表現(xiàn)等角度去充分解讀它。
使用前后對(duì)比
假使我們的 Project 包含兩個(gè)子 Module:Common 和 Recommend。
其中 Common Module 的包名為 com.example.common
,提供共通的資源。比如:
?<!-- common/.../strings.xml --> ?<resources> ? ? ?<string name="common_error">發(fā)生了錯(cuò)誤,請(qǐng)檢查鏈路</string> ?</resources>
而 Recommend Moudle 的包名為 com.example.recommend
,提供其獨(dú)有的資源。比如:
?<!-- recommend/.../strings.xml --> ?<resources> ? ? ?<string name="recommend_error">沒有推薦內(nèi)容,稍后再試</string> ?</resources>
當(dāng) Recommend Moudle 收到錯(cuò)誤的時(shí)候,會(huì)根據(jù)類型展示相應(yīng)的說明。
?package com.example.recommend ?sealed class Error(val tipId: Int) { ? ? ?// 來自 Common 包的資源 ? ? ?class Common ? ? : Error(R.string.common_error) ? ? ?class Recommend ? : Error(R.string.recommend_error) ? ? ?// 來自 AppCompat 包的資源 ? ? ?class BarCollapse : Error(R.string.abc_toolbar_collapse_description) ?}
可以看到即便使用了不同 Module 的資源文件,R 文件的包名也無需進(jìn)行區(qū)分。而這樣的寫法能否通過編譯,其實(shí)跟 AGP 的版本、AS 的版本均有關(guān)系。
- 2020 年 8 月發(fā)布的
AGP
4.1 將前期用于 R 文件優(yōu)化的namespacedRClass
實(shí)驗(yàn)性屬性替換成了nonTransitiveRClass
(默認(rèn) false)。以便其 R 類僅包含庫本身中聲明的資源,而不包含庫的依賴項(xiàng)中的任何資源,從而縮減相應(yīng)庫的 R 類大小。 - 2022 年 01 月 18 日發(fā)布的
Android Studio Bumblebee
則將新項(xiàng)目的該屬性默認(rèn)開啟。
屬性關(guān)閉
假使將 namespacedRClass 或 nonTransitiveRClass 屬性指定為 false,或者沒有使用這倆屬性、且 AS 處于 Bumblebee 之前的版本,上述的寫法都是可以通過編譯的。
原因顯而易見 Recommend Module 的 R 文件包含了被依賴的 Common Module 的資源 ID。
可話雖如此,你真的打開過這種情況下的 R 文件嗎?知道它有多龐大嗎?
我們?cè)陧?xiàng)目根目錄下搜索 R 文件的位置:
ellisonchan@bogon AndroidTDemo % find . -name "R.*"
./recommend/build/intermediates/compile_r_class_jar/debug/R.jar
./recommend/build/intermediates/compile_symbol_list/debug/R.txt
沒有找到 R.java,只有有同名的 txt 和 jar。可以直接打開 txt 或使用 Jar 工具查看。
先看下 R.txt,實(shí)際上它有 *4000+ *行,太過龐大,這里只保留 Recommend Module 自身以及極少量其他被依賴的 Module 的資源定義。
?// R.txt ?// 其他被依賴的 Module 定義的資源 ?int anim abc_fade_in 0x0 ?int anim abc_fade_out 0x0 ?int anim abc_grow_fade_in_from_bottom 0x0 ?int anim abc_popup_enter 0x0 ?int anim abc_popup_exit 0x0 ?... ?// 以下是 Recoomend Module 定義的資源 ?int color black 0x0 ?... ?int color purple_200 0x0 ?int color purple_500 0x0 ?int color purple_700 0x0 ?... ?int color teal_200 0x0 ?int color teal_700 0x0 ?... ?int color white 0x0 ?... ?int drawable ic_launcher_background 0x0 ?int drawable ic_launcher_foreground 0x0 ?... ?int mipmap ic_launcher 0x0 ?int mipmap ic_launcher_round 0x0 ?... ?int style Theme_AndroidTDemo 0x0 ?... ?int string recommend_error 0x0 ?... ?// 以下是被依賴的 Common Module 定義的資源 ?int string common_error 0x0 ?// 其他被依賴的 Module 定義的資源 ?... ?int xml standalone_badge 0x0 ?int xml standalone_badge_gravity_bottom_end 0x0 ?int xml standalone_badge_gravity_bottom_start 0x0 ?int xml standalone_badge_gravity_top_start 0x0 ?int xml standalone_badge_offset 0x0
R.jar 的內(nèi)容更多,足足 *5000+ *行,因其除了 ID 列表,還包含了各種二級(jí)資源類型 class 定義(和上面一樣只列出部分內(nèi)容)。
?// R.jar ?package com.example.recommend; ?public final class R { ? ?public static final class anim { ? ? ?public static int abc_fade_in = 0; ? ? ?public static int abc_fade_out = 0; ? ? ?public static int abc_grow_fade_in_from_bottom = 0; ? ? ?public static int abc_popup_enter = 0; ? ? ?public static int abc_popup_exit = 0; ? ? ... ? } ? ... ? ?public static final class color { ? ? ... ? ? ?public static int black = 0; ? ? ... ? ? ?public static int purple_200 = 0; ? ? ?public static int purple_500 = 0; ? ? ?public static int purple_700 = 0; ? ? ... ? ? ?public static int teal_200 = 0; ? ? ?public static int teal_700 = 0; ? ? ... ? ? ?public static int white = 0; ? } ? ?public static final class dimen { ... } ? ?public static final class drawable { ? ? ... ? ? ?public static int ic_launcher_background = 0; ? ? ?public static int ic_launcher_foreground = 0; ? ? ... ? } ? ?public static final class id { ... } ? ?public static final class integer { ... } ? ?public static final class interpolator { ... } ? ?public static final class layout { ... } ? ?public static final class mipmap { ? ? ?public static int ic_launcher = 0; ? ? ?public static int ic_launcher_round = 0; ? } ? ?public static final class plurals { ... } ? ?public static final class string { ? ? ... ? ? ?public static int common_error = 0; ? ? ... ? ? ?public static int recommend_error = 0; ? ? ... ? } ? ?public static final class style { ? ? ... ? ? ?public static int Theme_AndroidTDemo = 0; ? ? ... ? ? ?public static int Theme_AppCompat = 0; ? ? ... ? ?} ? ?public static final class styleable { ... } ? ?public static final class xml { ? ? ?public static int standalone_badge = 0; ? ? ?public static int standalone_badge_gravity_bottom_end = 0; ? ? ?public static int standalone_badge_gravity_bottom_start = 0; ? ? ?public static int standalone_badge_gravity_top_start = 0; ? ? ?public static int standalone_badge_offset = 0; ? } ?}
可以看到 Recommend Module 只定義了 10 多個(gè)資源,但 R 文件卻從其他 Module 導(dǎo)入了近 *3900+ *個(gè)資源。
這里拎出部分資源,看看是從哪個(gè)包導(dǎo)進(jìn)來的。
abc_fade_in
等 anim 資源:來自于 AppCompat 包。
standalone_badge
等 xml 資源:來自于 Material 包。
這些都來源于 build.gradle 的 dependency。
屬性開啟
事實(shí)上這些資源中的大部分,我們都是不會(huì)使用的。早期的這種不管實(shí)際使用,而一股腦將被依賴的 Module 資源 ID 全部囊括進(jìn)來的作法是不太合適的。
當(dāng)將 android.nonTransitiveRClass
屬性改為 true,就不會(huì)執(zhí)行上述作法,但上述的寫法會(huì)發(fā)生編譯錯(cuò)誤:
Unresolved reference: common_error
Unresolved reference: abc_toolbar_collapse_description
很明顯,我們應(yīng)當(dāng)明確指定 common_error 和 abc_toolbar_collapse_description 資源的 R 文件包名才行。
?sealed class Error(val tipId: Int) { ? ? ?class Common ? ? : Error(com.example.common.R.string.common_error) ? ? ... ? ? ?class BarCollapse : Error(androidx.appcompat.R.string.abc_toolbar_collapse_description) ?}
原因很好理解,依賴包的資源 ID 沒有被囊括進(jìn)自己的 R 文件。新的 R.txt 也顯示其僅包括本 Module 定義的資源。
?// R.txt ?int color black 0x0 ?int color purple_200 0x0 ?int color purple_500 0x0 ?int color purple_700 0x0 ?int color teal_200 0x0 ?int color teal_700 0x0 ?int color white 0x0 ?int drawable ic_launcher_background 0x0 ?int drawable ic_launcher_foreground 0x0 ?int mipmap ic_launcher 0x0 ?int mipmap ic_launcher_round 0x0 ?int string recommend_error 0x0 ?int style Theme_AndroidTDemo 0x0
R.jar 中也是一樣。
?// R.jar ?package com.example.recommend; ?public final class R { ? ?public static final class color { ? ? ?public static int black = 0; ? ? ?public static int purple_200 = 0; ? ? ?public static int purple_500 = 0; ? ? ?public static int purple_700 = 0; ? ? ?public static int teal_200 = 0; ? ? ?public static int teal_700 = 0; ? ? ?public static int white = 0; ? } ? ?public static final class drawable { ? ? ?public static int ic_launcher_background = 0; ? ? ?public static int ic_launcher_foreground = 0; ? } ? ?public static final class mipmap { ? ? ?public static int ic_launcher = 0; ? ? ?public static int ic_launcher_round = 0; ? } ? ?public static final class string { ? ? ?public static int recommend_error = 0; ? } ? ?public static final class style { ? ? ?public static int Theme_AndroidTDemo = 0; ? } ?}
開啟并自動(dòng)遷移
上面的示例使用其他包的資源的邏輯極少,手動(dòng)修改 R 文件不繁瑣。但當(dāng)大型項(xiàng)目開啟了 android.nonTransitiveRClass 屬性,修改各 R 文件名稱的工作量很大、還易錯(cuò)。
這時(shí)候可以采用自 Android Studio Arctic Fox
版本引入的重構(gòu)工具來自動(dòng)完成,避免手動(dòng)啟用之后、自己逐步修改的麻煩。
運(yùn)行 Menu -> Refactor -> Migrate to Non-transitive R Classes
這時(shí)候 AS 會(huì)提醒你將修改如下代碼進(jìn)行遷移。
選擇繼續(xù)之后,可以看到引用的其他包的 R 包被自動(dòng)補(bǔ)全了。
sealed class Error(val tipId: Int) {
class Common : Error(com.example.common.R.string.common_error)
class Recommend : Error(R.string.recommend_error)
class BarCollapse : Error(androidx.appcompat.R.string.abc_toolbar_collapse_description)
}
能否解決資源沖突
現(xiàn)在我們來探討 android.nonTransitiveRClass 屬性能否解決資源沖突的問題。
假使我們?cè)?Recommend Module 中也定義了名為 common_error
的資源。
?<!-- recommend/.../strings.xml --> ?<resources> ? ? ?<string name="recommend_error">沒有推薦內(nèi)容,稍后再試</string> ? ? ?<string name="common_error">發(fā)生了錯(cuò)誤,請(qǐng)檢查推薦鏈路</string> ?</resources>
對(duì)于 Recommend Module 而言,使用 common_error 資源的地方肯定會(huì)覆蓋 Common Module 的重復(fù)定義,無須多言。
而對(duì)于使用這兩者的 App Module 而言,因 Module 引用順序的不同,其可能會(huì)使用 Recommend,也可能使用 Common 的定義。即最終編譯進(jìn)來的只有一份定義。
如下的 App、Common、Recommend 3 個(gè) Module 的 R.java 文件也說明了這點(diǎn),3 個(gè) common_error
的數(shù)值完全相同。
?// R.java in App Module ?package com.example.tiramisu_demo; ?public final class R { ? ... ? ?public static final class string { ? ? ?public static final int common_error = 2131689515; ? ? ... ? } ?} ?// R.java in Common Module ?package com.example.common;? ?public final class R { ? ... ? ?public static final class string { ? ? ?public static final int common_error = 2131689515; ? ? ... ? } ? ... ?} ?// R.java in Recommend Module ?package com.example.recommend; ?public final class R { ? ... ? ?public static final class string { ? ? ?public static final int common_error = 2131689515; ? ? ... ? } ? ... ?}
在 App Module 的 Activity 類里測(cè)試下效果:
?class MainActivity : AppCompatActivity() { ? ? ?override fun onCreate(savedInstanceState: Bundle?) { ? ? ? ? ... ? ? ? ? ?val dynamicTextView: TextView = findViewById(R.id.dynamic_test) ? ? ? ? ?handleError( ? ? ? ? ? ? ?Error.Common(), ? ? ? ? ? ? ?dynamicTextView, ? ? ? ? ? ? ?this@MainActivity ? ? ? ? ) ? ? } ?} ?fun handleError( ? ? ?error: Error, ? ? ?textView: TextView, ? ? ?context: Context ?) { ? ? ?error.tipId.let { id -> ? ? ? ? ?context.getText(id).let { content -> ? ? ? ? ? ? ?textView.text = content ? ? ? ? } ? ? } ?} ?sealed class Error(val tipId: Int) { ? ? ?class Common ? ? : Error(R.string.common_error) ? ? ?class Recommend ? : Error(R.string.recommend_error) ? ? ?class BarCollapse : Error(R.string.abc_toolbar_collapse_description) ?}
運(yùn)行下可以看到展示的是 Recommend Module 定義的資源內(nèi)容:
之后,我們?cè)偈褂们懊嫣峒暗?android.nonTransitiveRClass 自動(dòng)遷移工具嘗試更改下 R 文件的配置問題。
如下的工具遷移提醒可以看到:只能將待遷移資源的 R 遷移到目前使用來源 Module 的 R,即無法識(shí)別多個(gè)來源。
遷移后的代碼:
sealed class Error(val tipId: Int) {
class Common : Error(com.example.recommend.R.string.common_error)
class Recommend : Error(com.example.recommend.R.string.recommend_error)
class BarCollapse : Error(androidx.appcompat.R.string.abc_toolbar_collapse_description)
}
初步可以看到 nonTransitiveRClass 屬性并不能幫你自動(dòng)解決資源沖突,只是強(qiáng)制要求你將各 Module 的資源按其所屬包名區(qū)分開來使用。
當(dāng)沖突發(fā)生的地方,你可以通過包名進(jìn)行區(qū)分。
比如讓 Common Error 展示 Common Module 下的 common_error 資源。
?sealed class Error(val tipId: Int) { ? ? ?class CommonRecommend ? : Error(com.example.recommend.R.string.common_error) ? ? ?class Common ? : Error(com.example.common.R.string.common_error) ? ? ... ?}
但這種寫法真的有用嗎?
再運(yùn)行下,竟發(fā)現(xiàn)沒有任何作用,仍然展示的是 Recommend Module 中的資源。
此刻,你可能已經(jīng)領(lǐng)悟到:為什么用即便用包名區(qū)分了沖突的資源,但仍然沒有任何作用?
這是因?yàn)橘Y源沖突導(dǎo)致 AAPT 仍然只打包了一份資源,nonTransitiveRClass 屬性只是不再將 common_error
等其他被依賴的資源 ID 囊括到 App 的 R 文件中而已。
同一份資源 ID,通過 com.example.common.R 來引用,還是 com.example.recommend.R 來引用,沒有區(qū)別!
結(jié)語
上面的示例可以看到,沒有開啟 nonTransitiveRClass
的話,僅僅定義 10 多個(gè)資源的 Module 的 R 文件會(huì)激增到 4000+ 個(gè) ID。這對(duì)編譯速度、AAR / APK 體積的影響是可以預(yù)見的。
加上模塊化開發(fā)的流行,Module 的龐雜必然引發(fā) ID 的大量重復(fù)定義,進(jìn)而導(dǎo)致 R 文件指數(shù)膨脹。另外 App 構(gòu)建的時(shí)候,會(huì)為項(xiàng)目的每個(gè)依賴 Module 生成一個(gè) R.java
文件,然后將這些 R 文件和應(yīng)用的其他類一起編譯。
這兩個(gè)因素將極大地拖累多模塊的構(gòu)建效率。
而當(dāng)開啟了 nonTransitiveRClass 屬性,可以保證每個(gè) Module 的 R 文件將只會(huì)包含自己聲明的資源,依賴項(xiàng)中的資源會(huì)被排除在外。這樣一來,R 文件大小將會(huì)顯著減少。
另外,AGP 會(huì)直接生成包含應(yīng)用的已編譯 R.jar
,而不會(huì)先編譯其中間的 R.java
文件。這項(xiàng)優(yōu)化可以確保,向運(yùn)行時(shí)依賴 Module 中添加新資源時(shí),可以避免重新編譯下游 Module。
這兩項(xiàng)變化將大大提升模塊化的構(gòu)建速度,并減小 AAR / APK 的體積~
另外,我們必須認(rèn)識(shí)到 nonTransitiveRClass
屬性跟資源沖突沒有關(guān)系,它是用來優(yōu)化 R 文件構(gòu)成的,不是也不能解決資源沖突。資源沖突仍然要依賴開發(fā)者對(duì)于資源的規(guī)范定義和使用!
到此這篇關(guān)于Android nonTransitiveRClass資源沖突問題淺析的文章就介紹到這了,更多相關(guān)Android nonTransitiveRClass內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android學(xué)習(xí)筆記(二)之電話撥號(hào)器
目前手機(jī)市場(chǎng)上android已經(jīng)具有強(qiáng)大的霸主地位,吸引了很多的追棒者,android學(xué)習(xí)越來越火熱,本文給大家介紹android學(xué)習(xí)筆記(二)之電話撥號(hào)器,感興趣的朋友一起學(xué)習(xí)吧2015-11-11Android?Activity通用懸浮可拖拽View封裝的思路詳解
這篇文章主要介紹了Android?Activity通用懸浮可拖拽View封裝,實(shí)現(xiàn)思路是通過封裝通用的基礎(chǔ)懸浮View,繼承通用View,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07Android Toast的幾種使用方式及注意事項(xiàng)
Toast是Android中常用的組件,下面介紹下Toast使用的幾種方式和注意事項(xiàng),本文給大家分享Toast的使用方式,感興趣的朋友一起看看吧2024-02-02Android基于OpenCV實(shí)現(xiàn)圖像脫色
脫色是將彩色圖像轉(zhuǎn)換為灰度圖像的過程。同時(shí),它也是數(shù)字打印,風(fēng)格化的黑白照片渲染以及許多單通道圖像處理應(yīng)用程序中的基本工具。本文講述基于OpenCV實(shí)現(xiàn)圖像脫色的步驟2021-06-06Android實(shí)現(xiàn)簡(jiǎn)單加法計(jì)算器
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)簡(jiǎn)單加法計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-03-03Android編程之線性布局LinearLayout實(shí)例簡(jiǎn)析
這篇文章主要介紹了Android編程之線性布局LinearLayout用法,結(jié)合實(shí)例形式簡(jiǎn)單分析了Android線性布局的使用技巧,需要的朋友可以參考下2016-01-01