Android工程中同名属性冲突的问题和解决方法

  最近的一个 Lib 被提了一个属性冲突的 Issue,大意是在和其他 Lib 同时使用时,两个 Lib 定义了相同名称的属性,导致冲突。找来冲突的 Lib 试了一下,并没有复现,根据这个 Issue,Build tools 24.0.1 进行了部分修复,但相同名称的属性仍有发生冲突的可能性,具体情况整理如下。

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"/>

倒是可以达到【伪】不同类型同名属性的效果。