layout中引用后定义的控件id不报错?

1、问题

最近写layout时遇到一个奇怪的问题,layout布局中有两个控件属性都引用了在其后定义的控件id,编译时其中一个报id找不到,但是另一个却没有报错,而且布局显示都正常。布局layout大致如下:

<TextView
    android:id="@+id/title"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerVertical="true"
    android:layout_toLeftOf="@id/btn"
    android:textStyle="bold" />

<Button
    android:id="@+id/btn"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerVertical="true"
    android:layout_toLeftOf="@id/arrow" />

<ImageView
    android:id="@+id/arrow"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentRight="true"
    android:layout_centerVertical="true"
    android:src="@drawable/arrow" />

如上layout所示,Button控件引用了id arrow,TextView控件又引用了id btn。最终报的是找不到btn id,将Button控件挪到TextView前面后发现,编译正常,没有报错,程序运行后布局也没有错乱现象,完全没有影响。

2、分析

这不科学,按照常规来说,被引用的id应该必须先于引用它的控件声明。这里大概可以分为三种情况:

  • 可以先添加被引用的控件,声明id属性,然后再添加引用它的控件
  • 也可以后添加被引用的控件,但是在添加引用它的控件时需要使用@+id先添加被引用控件的id,例如android:layout_toLeftOf=”@+id/arrow”
<Button
    android:id="@+id/btn"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerVertical="true"
    android:layout_toLeftOf="@+id/arrow" />
<ImageView
    android:id="@id/arrow"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentRight="true"
    android:layout_centerVertical="true"
    android:src="@drawable/arrow" />
  • 使用android sdk自带的id,例如@android:id/text1

以上三种情况,不管哪种,都是保证在引用控件id时,该id已经存在R文件中。可是我们这里这三种情况均不符合,为什么也是可以的呢?
难道是arrow的某些属性比较特殊,例如layout_alignParentRight属性,会导致控件优先被解析?去掉该属性测试下也没有报错,这应该不是问题根源所在。因为Android对于xml解析是从上往下递归进行的;另外,layout中控件的位置确定应该在是运行时加载这个布局文件才会去measure、layout的。
换个思路,既然没有报错,那么应该是在解析这个layout时,已经存在了该arrow id。我们知道在编译打包阶段,aapt会为所有的资源生成唯一id(R文件)。在解析layout时,aapt会去R文件中检查id的正确性。那么也就是说项目中其他地方也定义了相同的id,并且先于该layout被解析了。
这样出现的问题也就可以解释了,该layout所在的项目main依赖了一个底层的base项目,base项目中已经定义了arrow id。打包时,aapt会先将base打包成aar,因此R文件中已经存在了arrow id,导致我们的layout没有报找不到id的错误。另外,事实上btn id在main项目中也存在多处重复定义,但是却报了找不到id的错误,原因应该是另一处的double_user所在的layout并没有先于被解析导致的。

3、总结

虽然引用后定义的控件id可能不会报错,但是还是应该遵循先定义,后引用的规则。否则,不知道什么时候底层依赖的aar改了id,上层就会报错,修改起来将很麻烦。

4、脚本开发

为了彻底解决这样的潜在bug,特地学习了下python,写了个小脚本,用于检查项目中存在问题的layout文件。脚本主要针对ReleativeLayout中的相对位置属性进行扫描检查,代码如下:

#!/usr/bin/env python

import xml.etree.ElementTree as XmlTree
import os

dir = '../Demo/build/intermediates/res/merged/common/debug/layout/'

attrib_id = '{http://schemas.android.com/apk/res/android}id'
attrib_toleft = '{http://schemas.android.com/apk/res/android}layout_toLeftOf'
attrib_toright = '{http://schemas.android.com/apk/res/android}layout_toRightOf'
attrib_above = '{http://schemas.android.com/apk/res/android}layout_above'
attrib_below = '{http://schemas.android.com/apk/res/android}layout_below'
attrib_tostart = '{http://schemas.android.com/apk/res/android}layout_toStartOf'
attrib_end = '{http://schemas.android.com/apk/res/android}layout_toEndOf'

attrs = [attrib_toleft, attrib_toright, attrib_above, attrib_below, attrib_tostart, attrib_end]

#parse each layout to find error
def checklayout(layoutname):
    xmltree = XmlTree.parse(dir + layoutname)
    root = xmltree.getroot()
    ids = []
    for element in root.iter():
        for key in element.attrib.keys():
            value = element.attrib.get(key)
            if key == attrib_id:
                id = value.split('/')[1]
                ids.append(id)
            if key in attrs:
                target = element.attrib.get(key).split('/')
                if target[0] != '@+id' and target[0] != '@android:id' and target[1] not in ids:
                    return layoutname
    return ''

#main function to start this script
def main():
    print '******loading******'
    layouts = [filename for filename in os.listdir(dir) if os.path.isfile(dir + filename)
               and os.path.splitext(filename)[1] == '.xml']
    result = []
    for layout in layouts:
        error_layout = checklayout(layout)
        if error_layout != '':
            result.append(error_layout)
    print result

main()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值