Android工程中同名属性冲突的问题和解决方法
最近的一个 Lib 被提了一个属性冲突的 Issue,大意是在和其他 Lib 同时使用时,两个 Lib 定义了相同名称的属性,导致冲突。找来冲突的 Lib 试了一下,并没有复现,根据这个 Issue,Build tools 24.0.1 进行了部分修复,但相同名称的属性仍有发生冲突的可能性,具体情况整理如下。
Contents
1. 建立冲突的情景
创建一个 App,然后通过 File/New/New Module… 创建一个 Android Library,这里命名为 MyLib,Module Name 为 mylib。之后在 app/build.gradle 中加入对 MyLib 的依赖:
dependencies { ... compile project(path: ':mylib') }
然后在 MyLib 的 /res/values/ 下建立 attrs.xml,在其中定义一个属性 foo:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="MyLibAttr1"> <attr name="foo" format="integer"/> </declare-styleable> </resources>
2. 冲突的情况
2.1. App 与 Lib,同名同类型属性
在 App 的 /res/values/ 下建立 attrs.xml,在其中也定义一个属性 foo:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="LocalAttr1"> <attr name="foo" format="integer"/> </declare-styleable> </resources>
编译程序,可以成功编译(buildToolsVersion “25.0.2”,下同)。此时 foo 属性在 App 和 MyLib 中都是可以正常工作的。在 App 的 R.java 中可以找到:
public static final int foo=0x7f0100ac; // ... public static final int LocalAttr1_foo = 0; // ... public static final int MyLibAttr1_foo = 0;
R.java 根据 declare-styleable 的 name 对属性添加了前缀,这里并不会发生冲突。
2.2. App 与 Lib,同名不同类型属性
在 App 的 /res/values/attrs.xml 中,修改 foo 的 format 为 string:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="LocalAttr1"> <attr name="foo" format="string"/> </declare-styleable> </resources>
此时会编译失败,报错为:
Attribute "foo" already defined with incompatible format
出错位置在 /build/intermediates/res/merged/debug/values/values.xml,里面有:
<declare-styleable name="LocalAttr1"><attr format="string" name="foo"/></declare-styleable> <!--...--> <declare-styleable name="MyLibAttr1"><attr format="integer" name="foo"/></declare-styleable>
编译时会将所有 declare-styleable 都合并在一起,虽然 declare-styleable 的 name 不同(LocalAttr1 和 MyLibAttr1),但具有相同 name 和不同 format 的 attr 也会导致冲突。
2.3. App 内部,同名属性
在 App 的 /res/values/attrs.xml 中,声明两组 declare-styleable,在两组 declare-styleable 中都声明一个名为 bar 的属性:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="LocalAttr1"> <attr name="bar" format="integer"/> </declare-styleable> <declare-styleable name="LocalAttr2"> <attr name="bar" format="integer"/> </declare-styleable> </resources>
虽然两个 bar 具有相同的类型,这种写法也会编译失败,报错为:
Error:Execution failed for task ':app:mergeDebugResources'. > ...\app\src\main\res\values\attrs.xml: Error: Found item Attr/bar more than one time
Gradle 的 mergeDebugResources 任务检测到 attrs.xml 里的同名属性,报错并直接终止了编译。
3. 解决方法
3.1. App 与 Lib 的同名属性冲突
我在使用 25.0.2 版本的 build tools 时,如果 App 与 Lib 存在同名、同类型的属性,可以成功编译;如果 App 与 Lib 存在同名、不同类型的属性,会无法编译,此时只能通过修改 App 或 Lib 其中一方的属性名称来解决。Google 在这个 Issue 中给出的建议是手动为属性添加前缀,减小重名的可能性。
3.2. App 内部同名属性的处理
如果只是在 App 内部或者 Lib 内部,是可以使用同名属性的,处理方法为,在 resources 顶部定义公用的属性,在 declare-styleable 内进行引用,如:
<?xml version="1.0" encoding="utf-8"?> <resources> <attr name="common_bar" format="integer"/> <declare-styleable name="LocalAttr1"> <attr name="common_bar"/> </declare-styleable> <declare-styleable name="LocalAttr2"> <attr name="common_bar"/> </declare-styleable> </resources>
注意 LocalAttr1 和 LocalAttr2 的 common_bar 不能带 format,否则会变为属性声明,导致冲突。LocalAttr1 和 LocalAttr2 里面 common_bar 的 format 为外部声明的 integer,不能具有不同的类型。在外部声明 common_bar 时可以一次性声明多种format,如:
<attr name="common_bar" format="integer|string"/>
倒是可以达到【伪】不同类型同名属性的效果。