深入理解 Android 模块化里的资源冲突

news2025/1/20 12:14:11

在这里插入图片描述

翻译自 Understanding resource conflicts in Android

⚽ 前言

作为 Android 开发者,我们常常需要去管理非常多不同的资源文件,编译时这些资源文件会被统一地收集和整合到同一个包下面。根据官方的《Configure your build》文档介绍的构建过程可以总结这个过程:

  1. 编译器会将源码文件转换成包含了二进制字节码、能运行在 Android 设备上的 DEX 文件,而其他文件则被转换成编译后资源。
  2. APK 打包工具则会将 DEX 文件和编译后资源组合成独立的 APK 文件。

但如果资源的命名发生了碰撞、冲突,会对编译产生什么影响?

事实证明这个影响是不确定的,尤其是涉及到构建外部 Library。

本文将探究一些不同的资源冲突案例,并逐个说明怎样才能安全地命名资源

🇦🇷 App module 内资源冲突

先来看个最简单的资源冲突的案例:同一个资源文件中出现两个命名、类型一样的资源定义,比如:

<!--strings.xml-->
<resources>
    <string name="hello_world">Hello World!</string>
    <string name="hello_world">Hello World!</string>
</resources>

试图去编译的话,会导致显而易见的错误提示:

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:mergeDebugResources'.
> /.../strings.xml: Error: Found item String/hello_world more than one time

类似的,另一种常见冲突是在多个文件里定义冲突的资源:

<!--strings.xml-->
<resources>
    <string name="hello_world">Hello World!</string>
</resources>

<!--other_strings.xml-->
<resources>
    <string name="hello_world">Hello World!</string>
</resources>

我们会收到类似的编译错误,而这次的错误将列出所有发生冲突的具体文件位置。

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:mergeDebugResources'.
> [string/hello_world] /.../other_strings.xml
  [string/hello_world] /.../strings.xml: Error: Duplicate resources

Android 平台上资源的运作方式变得愈加清晰。我们需要为 App module 指定在类型、名称、设备配置等限定组合下的唯一资源。也就是说,当 App module 引用 string/hello_world 资源的时候,有且仅有一个值被解析出来。开发者们必须解决发生的资源冲突,可以选择删除那些内容重复的资源、重命名仍然需要的资源、亦或移动到其他限定条件下的资源文件。

更多关于资源和限定的信息可以参考官方的《App resources overview》 文档。

🇩🇪 Library 和 App module 的资源冲突

下面这个案例,我们将研究 Library module 定义了一个和 App module 重复的资源而引发的冲突。

<!--app/../strings.xml-->
<resources>
    <string name="hello">Hello from the App!</string>
</resources>

<!--library/../strings.xml-->
<resources>
    <string name="hello">Hello from the Library!</string>
</resources>

当你编译上面的代码的时候,发现竟然通过了。从我们上个章节的发现来看,我们可以推测 Android 肯定采用了一个规则,去确保在这种场景下仍能够找到一个独有的 string/hello 资源值。

根据官方的《Create an Android library》文档:

编译工具会将来自 Library module 的资源和独立的 App module 资源进行合并。如果双方均具备一个资源 ID 的话,将采用 App 的资源。

这样的话,将会对模块化的 App 开发造成什么影响?比如我们在 Library 中定义了这么一个 TextView 布局:

<!--library/../text_view.xml-->
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/hello"
    xmlns:android="http://schemas.android.com/apk/res/android" />

AS 中该布局的预览是这样的。

Hello from the Library!

现在我们决定将这个 TextView 导入到 App module 的布局中:

<!--app/../activity_main.xml-->
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    tools:context=".MainActivity"
    >

    <include layout="@layout/text_view" />

</LinearLayout>

无论是 AS 中预览还是实际运行,我们可以看到下面的一个显示结果:

Hello from the App!

不仅是通过布局访问 string/hello 的 App module 会拿到 “Hello from the App!”,Library 本身拿到的也是如此。基于这个原因,我们需要警惕不要无意覆盖 Lbrary 中的资源定义。

🇧🇷 Library 之间的资源冲突

再一个案例,我们将讨论下当多个 Library 里定义了冲突的资源,会发生什么。

首先来看下如下的布局,如果这样写的话会产生什么结果?

<!--library1/../strings.xml-->
<resources>
    <string name="hello">Hello from Library 1!</string>
</resources>

<!--library2/../strings.xml-->
<resources>
    <string name="hello">Hello from Library 2!</string>
</resources>

<!--app/../activity_main.xml-->
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/hello" />

string/hello 将会被显示成什么?

事实上这取决于 App build.gradle 文件里依赖这些 Library 的顺序。再次到官方的《Create an Android library》文档里找答案:

如果多个 AAR 库之间发生了冲突,依赖列表里第一个列出(在依赖关系块的顶部)的资源将会被使用。

假使 App module 有这样的依赖列表:

dependencies {
    implementation project(":library1")
    implementation project(":library2")
    ...
}

最后 string/hello 的值将会被编译成 Hello from Library 1!

那么如果这两个 implementation 代码调换顺序,比如 implementation project(":library2") 在前、 implementation project(":library1") 在后,资源值则会被编译成 Hello from Library 2!

从这种微妙的变化可以非常直观地看到,依赖顺序可以轻易地改变 App 的资源展示结果。

🇪🇸 自定义 Attributes 的资源冲突

目前为止讨论的示例都是针对 string 资源的使用,然而需要特别留意的是自定义 attributes 这种有趣的资源类型。

看下如下的 attr 定义:

<!--app/../attrs.xml-->
<resources>
    <declare-styleable name="CustomStyleable">
        <attr name="freeText" format="string"/>
    </declare-styleable>

    <declare-styleable name="CustomStyleable2">
        <attr name="freeText" format="string"/>
    </declare-styleable>
</resources>

大家可能都认为上面的写法能通过编译、不会报错,而事实上这种写法必将导致下面的编译错误:

Execution failed for task ':app:mergeDebugResources'.
> /.../attrs.xml: Error: Found item Attr/freeText more than one time

但如果 2 个 Library 也采用了这样的自定义 attr 写法:

<!--library1/../attrs.xml-->
<resources>
    <declare-styleable name="CustomStyleable">
        <attr name="freeText" format="string"/>
    </declare-styleable>
</resources>

<!--library2/../attrs.xml-->
<resources>
    <declare-styleable name="CustomStyleable2">
        <attr name="freeText" format="string"/>
    </declare-styleable>
</resources>

事实上它却能够通过编译。

然而,如果我们进一步将 Library2 的 attr 做些调整,比如改为 <attr name="freeText" format="boolean"/>。再次编译,它竟然又失败了,而且出现了更多令人费解的错误:

* What went wrong:
Execution failed for task ':app:mergeDebugResources'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade
   > Android resource compilation failed
     /.../library2/build/intermediates/packaged_res/debug/values/values.xml:4:5-6:25: AAPT: error: duplicate value for resource 'attr/freeText' with config ''.
     /.../library2/build/intermediates/packaged_res/debug/values/values.xml:4:5-6:25: AAPT: error: resource previously defined here.
     /.../app/build/intermediates/incremental/mergeDebugResources/merged.dir/values/values.xml: AAPT: error: file failed to compile.

上面错误的一个重点是: mergeDebugResources/merged.dir/values/values.xml: AAPT: error: file failed to compile

到底是怎么回事呢?

事实上 values.xml 的编译指的是为 App module 生成 R 类。编译期间,AAPT 会尝试在 R 类里为每个资源属性生成独一无二的值。而对于 styleable 类型里的每个自定义 attr,都会在 R 类里生成 2 个的属性值。

第一个是 styleable 命名空间属性值(位于 R.styleable 包下),第二个是全局的 attr 属性值(位于 R.attr 包下)。对于这个探讨的特殊案例,我们则遇到了全局属性值的冲突,并且由于此冲突造成存在 3 个属性值:

  • R.styleable.CustomStyleable_freeText:来自 Library1,用于解析 string 格式的、名称为 freeText 的 attr
  • R.styleable.CustomStyleable2_freeText:来自 Library2,用于解析 boolean 格式的、名称为 freeText 的 attr
  • R.attr.freeText:无法被成功解析,源自我们给它赋予了来自 2 个 Library 的数值,而它们的格式不同,造成了冲突

前面能通过编译的示例是因为 Library 间同名的 R.attr.freeText 格式也相同,最终为 App module 编译到的是独一无二的数值。需要注意:每个 module 具备自己的 R 类,我们不能总是指望属性的数值在 Library 间保持一致。

再次看下官方的《Create an Android library》文档的建议:

当你构建依赖其他 Library 的 App module 时,Library module 们将会被编译成 AAR 文件再添加到 App module 中。所以,每个 Library 都会具备自己的 R 类,用 Library 的包名进行命名。所有包都会创建从 App module 和 Library module 生成的 R 类,包括 App module 的包和 Library moudle 的包。

📝 结语

所以我们能从上面的这些探讨得到什么启发?

是资源编译过程的复杂和微妙吗?

确实是的。但是作为开发者,我们能为自己和团队做的是:解释清楚定义的资源想要做什么,也就是说可以加上名称前缀。我们最喜欢的官方文档《Create an Android library》也提到了这宝贵的一点:

通用的资源 ID 应当避免发生资源冲突,可以考虑使用前缀或其他一致的、对 module 来说独一无二的命名方案(抑或是整个项目都是独一无二的命名)。

根据这个建议,比较好的做法是在我们的项目和团队中建立一个模式:在 module 中的所有资源前加上它的 module 名称,例如library_help_text

这将带来两个好处:

  1. 大大降低了名称冲突的概率。

  2. 明确资源覆盖的意图。

    比如也在 App module 中创建 library_help_text 的话,则表明开发者是有意地覆盖 Library module 中的某些定义。有的时候我们的确会想去覆盖一些其他资源,而这样的编码方式可以明确地告诉自己和团队,在编译的时候会发生预期的覆盖。

抛开内部开发不谈,至少是所有公开的资源都应该加上前缀,尤其是作为一个供应商或者开源项目去发布我们的 library。

可以往的经验来看,Google 自己的 library 也没有对所有的资源进行恰当地前缀命名。这将导致意外的副作用:依赖我们发行的 library 可能会因为命名冲突引发 App 编译失败。

Not a great look!

例如,我们可以看到 Material Design library 会给它们的颜色资源统一地添加 mtrl 的前缀。可是 styleable 下嵌套的 attribute resources 却没有使用 material 之类的前缀。

所以你会看到:假使一个 module 依赖了 Material library,同时依赖的另一个 library 中包含了与 Material library 一样名称的 attribute,那么在为这个 moudle 生成 R 类的时候,会发生冲突的可能。

🙏 鸣谢

本篇文章受到了下面文章或文档的启发和帮助:

  • Configure your build
  • Create an Android library
  • Android Resources collision without warning!
  • Why Android cannot deal with Resources name conflict between Project and Library?

📚 原文

Understanding resource conflicts in Android


文章出自:http://www.whnw.com.cn/news/1712629.html

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

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

相关文章

RFSoC应用笔记 - RF数据转换器 -22- API使用指南之配置DAC相关工作状态和中断相关函数使用

前言 本文完结后&#xff0c;关于RFSoC的配置的API函数部分就全部介绍完毕&#xff0c;后续有空将更新介绍简单的射频收发回环示例工程&#xff0c;不定时更新&#xff0c;敬请期待。 配置DAC相关工作状态 XRFdc_SetInterpolationFactor 函数原型 u32 XRFdc_SetInterpolat…

内存一致性,指令重排序,内存屏障,volatile解析

文章目录为什么会存在“内存可见性”问题重排序与内存可见性的关系as-if-serial语义单线程程序的重排序规则多线程程序的重排序规则happen-before是什么解决方案&#xff1a;内存屏障Volatile关键字解决内存可见性问题的实现原理为什么会存在“内存可见性”问题 下图为x86架构…

redis 的企业实战应用 (二)

前言&#xff1a; 如今redis的常用场景有 短信登录&#xff1a;使用redis共享session来实现 商户查询缓存&#xff1a;会理解缓存击穿&#xff0c;缓存穿透&#xff0c;缓存雪崩等问题&#xff0c;让小伙伴的对于这些概念的理解不仅仅是停留在概念上&#xff0c;更是能在代码…

【数学】仿射变换

∣降维打击NightguardSeries.∣\begin{vmatrix}\Huge{\textsf{ 降 维 打 击 }}\\\texttt{ Nightguard Series. }\end{vmatrix}∣∣∣∣∣​ 降 维 打 击 Nightguard Series. ​∣∣∣∣∣​ 注&#xff1a;本文讨论的仿射变换仅为y轴上的伸缩变换&#xff0c;且难度在高中生理…

H3CNE V7.0 视频教程

构建中小企业网络全套PPT汇总【V7版本】 第1章 计算机网络概述 第2章 OSI参考模型与TCP IP模型 第3章 局域网基本原理 第4章 广域网基本原理 第5章 IP基本原理 第6章 TCP和UDP基本原理 第7章 路由器、交换机及其操作系统介绍 第8章 命令行操作基础 第9章 网络设备文件…

mycat-3-实战篇

1 总结&#xff1a; 1&#xff1a;用的表必须在mycat的配置文件中配置。 2&#xff1a;mycat默认分片策略中&#xff0c;都是针对表的主键&#xff0c;默认是id,如果主键不是id的&#xff0c;请去rule.xml自己复制一份修改 3&#xff1a; 2 注意细讲解 1&#xff1a;schem…

Springboot启动流程分析(四):完成启动流程

目录 一 添加BeanPostProcessors到IOC容器 二 国际化支持 三 初始化监听器的多路播放器 四 刷新容器 五 注册监听器到IOC容器的多播器 六 完成bean的大规模实例化 6.1 大规模实例化bean 6.1.1 连续三层do...while循环作用 6.1.2 FactoryBean是什么&#xff1f;为什么要…

04 YAML kubetnetes世界里的通用语

文章目录1. 前言2. 声明式和命令式是怎么回事&#xff1f;3. 什么是YAML&#xff1f;4. 什么是API对象&#xff1f;4.1 k8s都有哪些资源对象4.2 列出kubectl 命令详细执行过程5. 如何描述 API 对象5.1 命令式5.2 声明式5.2.1 声明式YAML语法详解5.2.1.1 header部分详解5.2.1.2 …

【教学类-19-01】20221127《ABAB式-规律排序-A4竖版2份》(中班)

展示效果&#xff1a; 单人使用样式&#xff1a; 单页打印样式 ​ 背景需求&#xff1a; 中班幼儿需要掌握ABAB规律排序&#xff0c;如下图所示&#xff0c;AB两个元素能外形不同、颜色不同。 ​ ​利用Python Word单元格填色功能&#xff0c;随机生成AB样式&#xff0c;引…

STM32模拟IIC与IIC四种实现数字光强采集模块GY30(标准库与HAL库)

目录 代码实现是的IIC通信&#xff0c;数据采集后在串口显示&#xff0c;方便大家实现二次开发 原件选择 GY-30 数字光强度介绍 BH1750芯片参数 引脚说明 BH1750指令集 接线表设计 通过四种方式实现GY-30数据采集 1.标准库模拟IIC实现GY-30采集并串口1显示 2.标准库IIC…

重构uniapp uni-ui coloerUI项目

重构uniapp uni-ui coloerUI项目这里写自定义目录标题重构uniappuni-uicoloerUI项目起源流程重构uniappuni-uicoloerUI项目 起源 从网上复制了若依移动端的代码,但是对里面的文件夹布局方式和第三方组件库引入方式不甚了解,就想着从头创建一个空白项目&#xff0c;然后一步一…

Linux中设置开机启动执行命令和普通用户配置环境变量开机启动生效

记录&#xff1a;343 场景&#xff1a;在CentOS 7.9操作系统上&#xff0c;开机启动就执行自定义的命令&#xff0c;配置rc.local文件达到需求&#xff1b;在普通用户中配置环境变量开机启动生效&#xff0c;使用profile实现。 版本&#xff1a; 操作系统&#xff1a;CentOS…

01、Docker入门

目录 1、Docker是什么 2、Docker与虚拟化 3、Docker虚拟化的好处 好处一&#xff1a;应用部署方便 好处二&#xff1a;服务器同等配置&#xff0c;性能更优&#xff0c;利用率更高 4、核心概念 5、CentOS7 安装docker(在线方式) 6、镜像 7、Docker容器 8、查看Docker容…

typescript 八叉树的简单实现

查了一些文章&#xff0c;准备自己用typescript写一个简单的八叉树场景管理。 所谓的简单&#xff0c;就是所有元素都是边长为1的立方体。 元素类和树节点类 //元素类&#xff0c;因为都是边长为1的立方体&#xff0c;所以就用cube命名 export class CubeData {public reado…

由于没有远程桌面授权服务器可以提供许可证,远程会话连接已断开

一、问题描述 在使用Windows的远程桌面工具连接WindowsServer2016服务器时&#xff0c;无法连接到服务器&#xff0c;并且提示【由于没有远程桌面授权服务器可以提供许可证&#xff0c;远程回来连接已经断开。请跟服务器管理员联系】。 二、解决办法 2.0、前提 Windows Serv…

黑胶歌曲没权限,看我python大展神通,一分钟一个歌单

前言 大家早好、午好、晚好吖 ❤ ~ 人之初&#xff0c;喜白嫖。 大家都喜欢白嫖&#xff0c;我也喜欢&#xff0c;那么今天就来试试怎么白嫖抑云~ 一、需要的准备 1、环境 Python3.6以上 pycharm2019以上 2、模块 requests # 发送请求模块 第三方模块 exec js # 调用JS的…

CocosCreater 教程(下)

1.物理系统 1.1 2D刚体 刚体是组成物理世界的基本对象。 1.2 2D 碰撞组件 目前引擎支持三种不同的碰撞组件&#xff1a; 盒碰撞组件&#xff08;BoxCollider2D&#xff09;、圆形碰撞组件&#xff08;CircleCollider2D&#xff09; 和 多边形碰撞组件&#xff08;PolygonCo…

Java中的抽象类和接口

java中的抽象类和接口抽象类什么是抽象类&#xff1f;抽象的使用场景抽象类的案例抽象类的特征、注意事项小结抽象类的应用知识&#xff1a;模版方法模式接口接口概述、特点接口的基本使用&#xff1a;被实现接口与接口的关系&#xff1a;多继承JDK8开始接口新增方法接口的注意…

AtCoder Beginner Contest 277 F. Sorting a Matrix(拓扑排序+虚点)

题目 n*m(2<n,m<1e6,n*m<1e6)的矩阵&#xff0c; 第i行第j列元素a[i][j](0<a[i][j]<n*m) 对于值为0的元素&#xff0c;你可以将其赋值为任意正整数&#xff0c; 不同位置的0元素&#xff0c;可以被赋值成不同的正整数 然后&#xff0c;你可以执行以下操作若…

firefly3399 移植linux5.15.80 - 2022-11-27

需要注意的是&#xff0c;虚拟机需要足够的硬盘空间&#xff0c;不小于15GB&#xff01;&#xff01; 一、内核源码下载 国内镜像地址 git clone https://kernel.source.codeaurora.cn/pub/scm/linux/kernel/git/stable/linux.git/ 基本达到了带宽的最大值。 国外地址&#…