别搞错了,nonTransitiveRClass 不能解决资源冲突!

news2024/11/25 4:58:22

前言

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.namespacedRClass 属性已重命名为 android.nonTransitiveRClass
  • Android Studio 新特性详解
  • 使用新 Android Gradle 插件加速您的应用构建
  • Speed up your build: Non-transitive R files
  • 主 Module 不能引用子 Module 资源文件
  • Android 性能优化之 R 文件优化详解

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

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

相关文章

【序列召回推荐】(task2)序列召回GRU4Rec模型

学习总结&#xff1a; 一般的RNN模型我们的输入和输出是什么&#xff0c;我们对RNN输入一个序列 X[x1,x2,...,xn]X [x^1,x^2,...,x^n]X[x1,x2,...,xn] &#xff0c;注意我们序列中的每一个节点都是一个向量&#xff0c;那么我们的RNN会给我们的输出也是一个序列 Y[y1,y2,...,…

【python黑帽子】——(一)搭建扫描器入门介绍

作者名&#xff1a;Demo不是emo 主页面链接&#xff1a;主页传送门创作初心&#xff1a;舞台再大&#xff0c;你不上台&#xff0c;永远是观众&#xff0c;没人会关心你努不努力&#xff0c;摔的痛不痛&#xff0c;他们只会看你最后站在什么位置&#xff0c;然后羡慕或鄙夷座右…

Python安装教程

Python安装 1.浏览器打开网址:www.python.org 2.根据电脑系统选择下载 3.确定电脑系统属性&#xff0c;此处我们以win10的64位操作系统为例 4.安装python 3.6.3 双击下载的安装包 python-3.6.3.exe 注意要勾选&#xff1a;Add Python 3.6 to PATH 点击 Customize installat…

4 种经典方法IB 数学证明题分享给大家

学习数学时感觉最有意思的题目就是证明题了&#xff0c;证明题能练习一种能力&#xff1a; 你知道一件事情时对的&#xff0c;怎么说清楚它是对的&#xff1b;你认为一件事情时错的&#xff0c;怎么说清楚它是错的。 这和生活中的辩论有点像&#xff0c;要有理有据地说清楚原因…

[附源码]Node.js计算机毕业设计蛋糕店会员系统Express

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

Servlet的生命周期

servlet 1.servlet是什么 2.servlet生命周期 3. servlet 工作原理 4 .ServletContextListener 什么是Servlet&#xff1f; Servlet是JavaWeb的 三大组件之一 &#xff0c;它属于动态资源。Servlet的作用是处理请求&#xff0c;服务器会把接收到的请求交给Servlet来处理&…

基于RSS和TOA两种方法的无线传感器网络定位测量算法matlab仿真

up目录 一、理论基础 二、核心程序 三、测试结果 一、理论基础 无线传感器网络(Wireless Sensor Networks, WSN)是一种分布式传感网络&#xff0c;它的末梢是可以感知和检查外部世界的传感器。WSN中的传感器通过无线方式通信&#xff0c;因此网络设置灵活&#xff0c;设备位…

去哪儿旅行微服务架构实践,全文带图加详细解析,带你多方面了解

文章目录一、背景介绍二、微服务架构模式的最佳实践三、微服务开发效率提升实践四、服务治理实践五、ServiceMesh 尝试六、总结今天我带来的主题是去哪儿旅行 微服务架构实践。我将从以下几个方面进行介绍&#xff1a;背景介绍微服务架构模式的最佳实践微服务开发效率的提升实践…

前台用户注册_发送邮件配置

在用户注册成功后&#xff0c;要向用户的邮箱发送一封激活邮件&#xff0c;发送邮件需要在系统中配置发件人&#xff0c;同学们使用自己的邮箱作为发件人即可。 配置邮箱第三方登录。 我们在系统中使用邮箱发送邮件属于第三方登录&#xff0c;而市面上的邮箱默认是不能第三方…

自监督学习系列(四):基于蒸馏的图片掩码学习

前文 好久不见&#xff01;自监督系列文章继续更新啦&#xff01;在前几期的文章我们介绍了基于辅助任务&#xff0c;对比学习&#xff0c;和图片掩码学习的自监督学习范式 (对比学习&#xff0c;图片掩码学习其实也可以归属于基于辅助任务的自监督学习&#xff0c;由于这两类…

百度安全怎么查询,怎么彻底解决百度安全弹出的风险提示

当我们在百度搜索自己的网站时&#xff0c;搜索结果中出现各种风险提示&#xff0c;比如安全联盟提醒您&#xff1a;该网站可能存在安全风险&#xff0c;请谨慎访问&#xff01; 别慌&#xff01;今天我们就来解决百度安全弹出的风险提示的问题。 第一步&#xff1a;查询网站…

Python 自动化测试框架unittest与pytest的区别

这篇文章主要讲unittest与pytest的区别&#xff0c;pytest相对unittest而言&#xff0c;代码简洁&#xff0c;使用便捷灵活&#xff0c;并且插件很丰富。 Unittest vs Pytest 主要从用例编写规则、用例的前置和后置、参数化、断言、用例执行、失败重运行和报告这几个方面比较…

传奇GOM引擎微端架设教程

传奇GOM引擎微端架设教程 GOM引擎架设微端需要准备好微端程序&#xff0c;用网站下载在服务器的版本 &#xff08;注&#xff1a;本文章图有打码处因平台GZ原因需打码望读者理解&#xff09; Mirserver文件一般都是自带微端程序的&#xff0c;偶尔也有版本没有微端程序那我们…

基于多目标遗传算法(NSGA-II)和多目标粒子群算法(MOPSO)的分布式仿真系统双目标负载平衡模型【Matlab代码实现】

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清…

Oracle单机部署:GI安装

Oracle单机部署&#xff1a;GI安装存储配置ASM磁盘空间评估GI单机安装配置GI图形化安装流程安装后测试&#x1f42c; 使用grid用户来安装GI。 存储配置 Oracle存储支持Oracle ASM、Oracle ACFS、本地文件系统、网络文件系统&#xff08;NFS/NAS&#xff09;、Oracle Memory S…

RK3588平台开发系列讲解(RTC篇)RTC的使用

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、RTC简介二、HYM8563驱动配置2.1、设备树配置2.1、驱动代码三、RTC的使用3.1、SYSFS接口3.2、PROCFS接口3.3、IOCTL接口沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将介绍RK3588平台RTC驱动及使用方…

玩以太坊链上项目的必备技能(基本类型转换以及推断-Solidity之旅六)

基本类型之间的转换 熟悉过其他编程语言的您&#xff0c;对基本类型之间的转换并不陌生吧&#xff01;当然&#xff0c;这基本类型进行转换可分为隐式转换和显示转换。 隐式转换 Solidity 支持隐式转换&#xff0c;通过编译器自动进行转换&#xff0c;而不要开发人员的干涉&…

信道估计算法误码率仿真,对比不同导频长度,对比不同信道估计算法包括CS-OMP,LS,MMSE

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 所谓信道估计&#xff0c;就是从接收数据中将假定的某个信道模型的模型参数出来的过程。如果信道是线性的话&#xff0c;那么信道估计就是对系统冲激响应进行估计。 CS-OMP 正则正交匹配追踪(Re…

分布式操作系统 - 5.分布式命名管理

文章目录1.基本概念2.非结构化命名管理2.1 简单的实体定位方法&#xff1a;广播和多播方法&#xff08;1&#xff09;广播方法&#xff08;broadcast&#xff09;&#xff08;2&#xff09;多播方法&#xff08;multicast&#xff09;&#xff08;3&#xff09;问题&#xff1a…

图形API学习工程(30):尝试使用panorama来代替Cubemap作为全景图

工程GIT地址&#xff1a;https://gitee.com/yaksue/yaksue-graphics 前言 为了能得到全方位的光照数据&#xff0c;我之前学习了使用CubeMap作为全景图。CubeMap包含六张贴图对应了上下左右前后六个方向的数据。但是最近在下载全景图的资源时&#xff0c;看到很多并非是CubeM…