原来 Android 的 R 文件里还有这么多道道

news2025/1/11 12:52:44

前言

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 文件构成的,不是也不能解决资源冲突。资源冲突仍然要依赖开发者对于资源的规范定义和使用!

更多Android 知识点归整

Android 性能调优系列https://0a.fit/dNHYY
Android 车载学习指南https://0a.fit/jdVoy
Android Framework核心知识点笔记https://0a.fit/acnLL
Android 音视频学习笔记https://0a.fit/BzPVh
Jetpack全家桶(含Compose)https://0a.fit/GQJSl
Kotlin 入门到精进https://0a.fit/kdfWR
Flutter 基础到进阶实战https://0a.fit/xvcHV
Android 八大知识体系https://0a.fit/mieWJ
Android 中高级面试题锦https://0a.fit/YXwVq

后续如有新知识点,将会持续更新,尽请期待……

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/94791.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Manacher算法

0、概括 Manacher算法用于求解字符串中最长回文子串问题。 Manacher算法的核心&#xff1a; 理解回文半径数组&#xff1b;理解所有中心的回文最右边界 R&#xff0c;和取得 R 时的中心点 C&#xff1b;理解 L...(i)...C...(i)...R 的结构&#xff0c;以及根据 i′ii′ 回文长…

C-RNN-GAN:具有对抗训练的连续循环神经网络2016--生成音乐

C-RNN-GAN: Continuous recurrent neural networks with adversarial training 2016 Abstract 生成对抗网络已被提出作为一种有效训练深度生成神经网络的方法。我们提出了一种生成对抗模型&#xff0c;它适用于连续的序列数据&#xff0c;并通过在古典音乐集合上训练它来应用它…

计算机毕设Python+Vue校园学生体温管理系统(程序+LW+部署)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

相见恨晚,Git这些功能太好用了

程序员宝藏库&#xff1a;https://gitee.com/sharetech_lee/CS-Books-Store 作为一名开发者&#xff0c;想必绝大多数同学都无法绕开Git。 作为一款工具&#xff0c;我认为它和word、powerpoint、Excel这些办公工具一样。 对于一部分同学&#xff0c;会一些基本的用法&#x…

【经验帖】项目经理的核心价值:以目标为导向做正确的事

项目经理小李的年终汇报心路历程&#xff08;心情犹如坐过山车&#xff0c;起起落落最后一蹶不振。&#xff09; 汇报前&#xff1a; 终于到年终汇报的日子了&#xff0c;毕竟我负责的项目任务从来没有延期过&#xff0c;都是按时完成&#xff0c;这次肯定得加薪了&#xff01…

[附源码]Node.js计算机毕业设计公租房管理系统Express

项目运行 环境配置&#xff1a; Node.js最新版 Vscode Mysql5.7 HBuilderXNavicat11Vue。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等等。 环境需要 1.运行环境&#xff1a;最好是Nodejs最新版&#xff0c;我…

module命名空间

为什么要有namespaced命名空间&#xff1f; 默认情况下&#xff0c;模块内部的action、mutation和getter都是在全局命名空间。 假设两个modules内部有同名的action、mutation和getter&#xff0c;则vuex会报错。 namespaced作用&#xff1a;保证模块内部的高封闭性&#xff0c;…

021 | 阴离子诱导的系列双核镝配合物的合成及磁性质 | 大学生创新训练项目申请书 | 极致技术工厂

研究目的 近十几年来&#xff0c;随着科技的飞速发展&#xff0c;单分子磁体材料涉及的应用领域越来越宽广。众所周知&#xff0c;单分子磁体材料作为信息存储的基础对信息产业的发展具有一定的意义。此外&#xff0c;单分子磁体在超高密度储存、自旋电子器件、量子计算机等领域…

旅游住宿网站

开发工具(eclipse/idea/vscode等)&#xff1a; 数据库(sqlite/mysql/sqlserver等)&#xff1a; 功能模块(请用文字描述&#xff0c;至少200字)&#xff1a; 网站前台&#xff1a;网站介绍、帮助信息、旅游资讯。景点信息、酒店信息 管理员功能&#xff1a; 1、管理网站介绍、帮…

day22【代码随想录】在每个树行中找最大值、填充每个节点的下一个右侧节点指针、二叉树的最大深度、二叉树的最小深度

文章目录前言一、在每个树行中找最大值&#xff08;力扣515&#xff09;二、填充每个节点的下一个右侧节点指针&#xff08;力扣116&#xff09;三、二叉树的最大深度&#xff08;力扣104&#xff09;1、非递归求解2、递归求解四、 二叉树的最小深度&#xff08;力扣111&#x…

【前端开发学习】4.JavaScript

文章目录1 JavaScript1.1 代码位置1.2 存在形式1.3 注释1.4 变量1.5 字符串类型案例&#xff1a;走马灯1.5 数组案例&#xff1a;动态数据1.6 对象&#xff08;字典&#xff09;案例&#xff1a;动态表格1.7 条件语句1.8 函数2 DOM2.1 事件绑定1 JavaScript 一门编程语言&…

第二证券|主力加仓电子、电气设备等行业 北向资金连续2日净流入

沪深两市股市涨跌互现&#xff0c;上证指数早盘震动下探&#xff0c;午后一度回升&#xff0c;尾盘再度回落&#xff1b;深证成指早盘探底回升&#xff0c;午后震动上扬&#xff0c;尾盘有所回落&#xff1b;创业板指早盘窄幅震动&#xff0c;午后震动上扬&#xff1b;科创50指…

D. Say No to Palindromes(构造 + 前缀和)

Problem - 1555D - Codeforces 如果这个字符串不包含一个长度至少为2的子串&#xff0c;我们就称它为美丽的字符串&#xff0c;这是一个回文。回顾一下&#xff0c;回文是指从第一个字符到最后一个字符以及从最后一个字符到第一个字符的读法相同的字符串。例如&#xff0c;字符…

京东物流 × StarRocks : 打造服务分析一体化平台Udata

作者&#xff1a;张栋&#xff0c;京东物流集团数据专家 京东集团 2007 年开始自建物流&#xff0c;2017 年 4 月正式成立京东物流集团&#xff0c;2021 年 5 月&#xff0c;京东物流于香港联交所主板上市。京东物流是中国领先的技术驱动的供应链解决方案及物流服务商&#xff…

Linux内核

内核属于操作系统的核心部分&#xff0c;它具有操作系统基本的功能&#xff0c;主要负责管理系统的内存、进程、设备驱动程序、文件系统和网络接口&#xff0c;因此&#xff0c;操作系统的性能和稳定性由内核决定。 1、内存管理 进程对内存的使用 计算机中所有要执行的程序都必…

大厂10年经验,我对Java高并发问题方案的总结,堪称教科书级

作为一个 Java 开发人员&#xff0c;多线程是一个逃不掉的话题&#xff0c;不管是工作还是面试&#xff0c;但理解起来比较模糊难懂&#xff0c;因为多线程程序在跑起来的时候比较难于观察和跟踪。 搞懂多线程并发知识&#xff0c;可以在面试的时候和周围人拉开差距&#xff0…

流媒体:依托于声网的连麦解决方案

一、背景 近些年&#xff0c;直播连麦这把火在流媒体领域整整燃烧了 6 年。从刚开始的简单探索&#xff0c;到现在的成熟全链路方案&#xff0c;不得不说日益增长的激烈竞争&#xff0c;已将让原本的蓝海领域变成了深海互搏。在这样的大环境下&#xff0c;是否意味着小厂将再也…

分布式系统(P2P Lookup)

文章目录P2P 系统NapsterBitTorrentGnutellaChordConsistent HashingSimple Key LocationScalable Key LocationKademliaRouting TableKademlia’s RPCAdaptabilityDistributed Hash TableP2P 系统 Peer to peer 系统&#xff1a; 每个结点在连接上是互联的&#xff0c;在功能…

Spring MVC: 一种简洁且强大的Web应用框架

⭐️前言⭐️ 这篇文章介绍Spring MVC&#xff0c;Spring MVC是现在基本所有Java程序的主流开发框架&#xff0c;这篇文章主要介绍三部分内容&#xff1a; 实现用户和程序的映射(在浏览器输入URL地址之后&#xff0c;能够在程序中匹配到相应方法)。服务器端得到用户的请求参数…

c语言笔记1 输入和输出注意事项,常量 变量 static

输出&#xff1a;printf c的编译器不会检测格式串中转换说明的数量和数据类型是否和后面的变量一致。转换说明的数据类型与实际数据类型不一致时&#xff0c;产生无意义的值。 int 类型的变量length&#xff0c;值为9&#xff0c;输出时将类型写为float或double&#xff0c;输…