網站首頁 編程語言 正文
前言
nonTransitiveRClass:非傳遞性 R 類的屬性,在 gradle.properties 文件里使用。
不少開發者可能聽過它,但了解可能僅限于是對 R 文件做了優化,甚至以為它可以解決資源沖突!但它到底做了什么優化、能否解決資源沖突,則鮮少有機會去了解。
本文通過該屬性使用前后對比、在資源沖突場景下的表現等角度去充分解讀它。
使用前后對比
假使我們的 Project 包含兩個子 Module:Common 和 Recommend。
其中 Common Module 的包名為 com.example.common
,提供共通的資源。比如:
?<!-- common/.../strings.xml --> ?<resources> ? ? ?<string name="common_error">發生了錯誤,請檢查鏈路</string> ?</resources>
而 Recommend Moudle 的包名為 com.example.recommend
,提供其獨有的資源。比如:
?<!-- recommend/.../strings.xml --> ?<resources> ? ? ?<string name="recommend_error">沒有推薦內容,稍后再試</string> ?</resources>
當 Recommend Moudle 收到錯誤的時候,會根據類型展示相應的說明。
?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 文件的包名也無需進行區分。而這樣的寫法能否通過編譯,其實跟 AGP 的版本、AS 的版本均有關系。
- 2020 年 8 月發布的
AGP
4.1 將前期用于 R 文件優化的namespacedRClass
實驗性屬性替換成了nonTransitiveRClass
(默認 false)。以便其 R 類僅包含庫本身中聲明的資源,而不包含庫的依賴項中的任何資源,從而縮減相應庫的 R 類大小。 - 2022 年 01 月 18 日發布的
Android Studio Bumblebee
則將新項目的該屬性默認開啟。
屬性關閉
假使將 namespacedRClass 或 nonTransitiveRClass 屬性指定為 false,或者沒有使用這倆屬性、且 AS 處于 Bumblebee 之前的版本,上述的寫法都是可以通過編譯的。
原因顯而易見 Recommend Module 的 R 文件包含了被依賴的 Common Module 的資源 ID。
可話雖如此,你真的打開過這種情況下的 R 文件嗎?知道它有多龐大嗎?
我們在項目根目錄下搜索 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,實際上它有 *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 的內容更多,足足 *5000+ *行,因其除了 ID 列表,還包含了各種二級資源類型 class 定義(和上面一樣只列出部分內容)。
?// 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 多個資源,但 R 文件卻從其他 Module 導入了近 *3900+ *個資源。
這里拎出部分資源,看看是從哪個包導進來的。
abc_fade_in
等 anim 資源:來自于 AppCompat 包。
standalone_badge
等 xml 資源:來自于 Material 包。
這些都來源于 build.gradle 的 dependency。
屬性開啟
事實上這些資源中的大部分,我們都是不會使用的。早期的這種不管實際使用,而一股腦將被依賴的 Module 資源 ID 全部囊括進來的作法是不太合適的。
當將 android.nonTransitiveRClass
屬性改為 true,就不會執行上述作法,但上述的寫法會發生編譯錯誤:
Unresolved reference: common_error
Unresolved reference: abc_toolbar_collapse_description
很明顯,我們應當明確指定 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 沒有被囊括進自己的 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; ? } ?}
開啟并自動遷移
上面的示例使用其他包的資源的邏輯極少,手動修改 R 文件不繁瑣。但當大型項目開啟了 android.nonTransitiveRClass 屬性,修改各 R 文件名稱的工作量很大、還易錯。
這時候可以采用自 Android Studio Arctic Fox
版本引入的重構工具來自動完成,避免手動啟用之后、自己逐步修改的麻煩。
運行 Menu -> Refactor -> Migrate to Non-transitive R Classes
這時候 AS 會提醒你將修改如下代碼進行遷移。
選擇繼續之后,可以看到引用的其他包的 R 包被自動補全了。
?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)
?}
能否解決資源沖突
現在我們來探討 android.nonTransitiveRClass 屬性能否解決資源沖突的問題。
假使我們在 Recommend Module 中也定義了名為 common_error
的資源。
?<!-- recommend/.../strings.xml --> ?<resources> ? ? ?<string name="recommend_error">沒有推薦內容,稍后再試</string> ? ? ?<string name="common_error">發生了錯誤,請檢查推薦鏈路</string> ?</resources>
對于 Recommend Module 而言,使用 common_error 資源的地方肯定會覆蓋 Common Module 的重復定義,無須多言。
而對于使用這兩者的 App Module 而言,因 Module 引用順序的不同,其可能會使用 Recommend,也可能使用 Common 的定義。即最終編譯進來的只有一份定義。
如下的 App、Common、Recommend 3 個 Module 的 R.java 文件也說明了這點,3 個 common_error
的數值完全相同。
?// 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 類里測試下效果:
?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) ?}
運行下可以看到展示的是 Recommend Module 定義的資源內容:
之后,我們再使用前面提及的 android.nonTransitiveRClass 自動遷移工具嘗試更改下 R 文件的配置問題。
如下的工具遷移提醒可以看到:只能將待遷移資源的 R 遷移到目前使用來源 Module 的 R,即無法識別多個來源。
遷移后的代碼:
?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 屬性并不能幫你自動解決資源沖突,只是強制要求你將各 Module 的資源按其所屬包名區分開來使用。
當沖突發生的地方,你可以通過包名進行區分。
比如讓 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) ? ? ... ?}
但這種寫法真的有用嗎?
再運行下,竟發現沒有任何作用,仍然展示的是 Recommend Module 中的資源。
此刻,你可能已經領悟到:為什么用即便用包名區分了沖突的資源,但仍然沒有任何作用?
這是因為資源沖突導致 AAPT 仍然只打包了一份資源,nonTransitiveRClass 屬性只是不再將 common_error
等其他被依賴的資源 ID 囊括到 App 的 R 文件中而已。
同一份資源 ID,通過 com.example.common.R 來引用,還是 com.example.recommend.R 來引用,沒有區別!
結語
上面的示例可以看到,沒有開啟 nonTransitiveRClass
的話,僅僅定義 10 多個資源的 Module 的 R 文件會激增到 4000+ 個 ID。這對編譯速度、AAR / APK 體積的影響是可以預見的。
加上模塊化開發的流行,Module 的龐雜必然引發 ID 的大量重復定義,進而導致 R 文件指數膨脹。另外 App 構建的時候,會為項目的每個依賴 Module 生成一個 R.java
文件,然后將這些 R 文件和應用的其他類一起編譯。
這兩個因素將極大地拖累多模塊的構建效率。
而當開啟了 nonTransitiveRClass 屬性,可以保證每個 Module 的 R 文件將只會包含自己聲明的資源,依賴項中的資源會被排除在外。這樣一來,R 文件大小將會顯著減少。
另外,AGP 會直接生成包含應用的已編譯 R.jar
,而不會先編譯其中間的 R.java
文件。這項優化可以確保,向運行時依賴 Module 中添加新資源時,可以避免重新編譯下游 Module。
這兩項變化將大大提升模塊化的構建速度,并減小 AAR / APK 的體積~
另外,我們必須認識到 nonTransitiveRClass
屬性跟資源沖突沒有關系,它是用來優化 R 文件構成的,不是也不能解決資源沖突。資源沖突仍然要依賴開發者對于資源的規范定義和使用!
原文鏈接:https://juejin.cn/post/7176111455236784185
相關推薦
- 2022-07-28 XML基本概念XPath、XSLT與XQuery函數介紹_XML/RSS
- 2023-01-18 fastadmin使用學習中的常見問題匯總_其它CMS
- 2022-05-24 C#多線程TPL模式下使用HttpClient_C#教程
- 2022-12-23 iOS之異常與信號使用場景分析_IOS
- 2022-12-24 Kubernetes?controller?manager運行機制源碼解析_云和虛擬化
- 2022-04-11 解決git push出現error: failed to push some refs to 錯誤
- 2022-08-15 C語言排序算法實現
- 2022-09-04 Go語言指針用法詳解_Golang
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支