網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
前言
nonTransitiveRClass:非傳遞性 R 類的屬性,在 gradle.properties 文件里使用。
不少開(kāi)發(fā)者可能聽(tīng)過(guò)它,但了解可能僅限于是對(duì) R 文件做了優(yōu)化,甚至以為它可以解決資源沖突!但它到底做了什么優(yōu)化、能否解決資源沖突,則鮮少有機(jī)會(huì)去了解。
本文通過(guò)該屬性使用前后對(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">沒(méi)有推薦內(nèi)容,稍后再試</string> ?</resources>
當(dāng) Recommend Moudle 收到錯(cuò)誤的時(shí)候,會(huì)根據(jù)類型展示相應(yīng)的說(shuō)明。
?package com.example.recommend ?sealed class Error(val tipId: Int) { ? ? ?// 來(lái)自 Common 包的資源 ? ? ?class Common ? ? : Error(R.string.common_error) ? ? ?class Recommend ? : Error(R.string.recommend_error) ? ? ?// 來(lái)自 AppCompat 包的資源 ? ? ?class BarCollapse : Error(R.string.abc_toolbar_collapse_description) ?}
可以看到即便使用了不同 Module 的資源文件,R 文件的包名也無(wú)需進(jìn)行區(qū)分。而這樣的寫法能否通過(guò)編譯,其實(shí)跟 AGP 的版本、AS 的版本均有關(guān)系。
- 2020 年 8 月發(fā)布的
AGP
4.1 將前期用于 R 文件優(yōu)化的namespacedRClass
實(shí)驗(yàn)性屬性替換成了nonTransitiveRClass
(默認(rèn) false)。以便其 R 類僅包含庫(kù)本身中聲明的資源,而不包含庫(kù)的依賴項(xiàng)中的任何資源,從而縮減相應(yīng)庫(kù)的 R 類大小。 - 2022 年 01 月 18 日發(fā)布的
Android Studio Bumblebee
則將新項(xiàng)目的該屬性默認(rèn)開(kāi)啟。
屬性關(guān)閉
假使將 namespacedRClass 或 nonTransitiveRClass 屬性指定為 false,或者沒(méi)有使用這倆屬性、且 AS 處于 Bumblebee 之前的版本,上述的寫法都是可以通過(guò)編譯的。
原因顯而易見(jiàn) Recommend Module 的 R 文件包含了被依賴的 Common Module 的資源 ID。
可話雖如此,你真的打開(kāi)過(guò)這種情況下的 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
沒(méi)有找到 R.java,只有有同名的 txt 和 jar。可以直接打開(kāi) txt 或使用 Jar 工具查看。
先看下 R.txt,實(shí)際上它有 *4000+ *行,太過(guò)龐大,這里只保留 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)來(lái)的。
abc_fade_in
等 anim 資源:來(lái)自于 AppCompat 包。
standalone_badge
等 xml 資源:來(lái)自于 Material 包。
這些都來(lái)源于 build.gradle 的 dependency。
屬性開(kāi)啟
事實(shí)上這些資源中的大部分,我們都是不會(huì)使用的。早期的這種不管實(shí)際使用,而一股腦將被依賴的 Module 資源 ID 全部囊括進(jìn)來(lái)的作法是不太合適的。
當(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 沒(méi)有被囊括進(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; ? } ?}
開(kāi)啟并自動(dòng)遷移
上面的示例使用其他包的資源的邏輯極少,手動(dòng)修改 R 文件不繁瑣。但當(dāng)大型項(xiàng)目開(kāi)啟了 android.nonTransitiveRClass 屬性,修改各 R 文件名稱的工作量很大、還易錯(cuò)。
這時(shí)候可以采用自 Android Studio Arctic Fox
版本引入的重構(gòu)工具來(lái)自動(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)在我們來(lái)探討 android.nonTransitiveRClass 屬性能否解決資源沖突的問(wèn)題。
假使我們?cè)?Recommend Module 中也定義了名為 common_error
的資源。
?<!-- recommend/.../strings.xml --> ?<resources> ? ? ?<string name="recommend_error">沒(méi)有推薦內(nèi)容,稍后再試</string> ? ? ?<string name="common_error">發(fā)生了錯(cuò)誤,請(qǐng)檢查推薦鏈路</string> ?</resources>
對(duì)于 Recommend Module 而言,使用 common_error 資源的地方肯定會(huì)覆蓋 Common Module 的重復(fù)定義,無(wú)須多言。
而對(duì)于使用這兩者的 App Module 而言,因 Module 引用順序的不同,其可能會(huì)使用 Recommend,也可能使用 Common 的定義。即最終編譯進(jìn)來(lái)的只有一份定義。
如下的 App、Common、Recommend 3 個(gè) Module 的 R.java 文件也說(shuō)明了這點(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 文件的配置問(wèn)題。
如下的工具遷移提醒可以看到:只能將待遷移資源的 R 遷移到目前使用來(lái)源 Module 的 R,即無(wú)法識(shí)別多個(gè)來(lái)源。
遷移后的代碼:
?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ū)分開(kāi)來(lái)使用。
當(dāng)沖突發(fā)生的地方,你可以通過(guò)包名進(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)沒(méi)有任何作用,仍然展示的是 Recommend Module 中的資源。
此刻,你可能已經(jīng)領(lǐng)悟到:為什么用即便用包名區(qū)分了沖突的資源,但仍然沒(méi)有任何作用?
這是因?yàn)橘Y源沖突導(dǎo)致 AAPT 仍然只打包了一份資源,nonTransitiveRClass 屬性只是不再將 common_error
等其他被依賴的資源 ID 囊括到 App 的 R 文件中而已。
同一份資源 ID,通過(guò) com.example.common.R 來(lái)引用,還是 com.example.recommend.R 來(lái)引用,沒(méi)有區(qū)別!
結(jié)語(yǔ)
上面的示例可以看到,沒(méi)有開(kāi)啟 nonTransitiveRClass
的話,僅僅定義 10 多個(gè)資源的 Module 的 R 文件會(huì)激增到 4000+ 個(gè) ID。這對(duì)編譯速度、AAR / APK 體積的影響是可以預(yù)見(jiàn)的。
加上模塊化開(kāi)發(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)開(kāi)啟了 nonTransitiveRClass 屬性,可以保證每個(gè) Module 的 R 文件將只會(huì)包含自己聲明的資源,依賴項(xiàng)中的資源會(huì)被排除在外。這樣一來(lái),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
屬性跟資源沖突沒(méi)有關(guān)系,它是用來(lái)優(yōu)化 R 文件構(gòu)成的,不是也不能解決資源沖突。資源沖突仍然要依賴開(kāi)發(fā)者對(duì)于資源的規(guī)范定義和使用!
原文鏈接:https://juejin.cn/post/7176111455236784185
相關(guān)推薦
- 2023-12-16 @Configuration(proxyBeanMethods = true)
- 2022-08-23 nginx靜態(tài)資源的服務(wù)器配置方法_nginx
- 2022-10-20 python文件數(shù)據(jù)分析治理提取_python
- 2022-10-22 C#抽象類的用法介紹_實(shí)用技巧
- 2022-05-27 C++回溯算法深度優(yōu)先搜索舉例分析_C 語(yǔ)言
- 2022-11-15 C++構(gòu)造析構(gòu)賦值運(yùn)算函數(shù)應(yīng)用詳解_C 語(yǔ)言
- 2022-08-17 python運(yùn)行腳本文件的三種方法實(shí)例_python
- 2023-05-30 Python嵌套循環(huán)的使用_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支