我喜欢用简单的单词来给xml中的元素加id,比如一个View里面要展示一行文字和一个图标,那么,展示文字的就叫text,展示图标的就叫icon

像这样:

<LinearLayout>
    <TextView
        android:id="@+id/text"/>
    <ImageView
        android:id="@+id/icon"/>
</LinearLayout>

然后我就在想,在同一个布局文件中,是不允许同名(同id)的,不然就会报错,像这样:

<LinearLayout>
    <TextView
        android:id="@+id/text"/>
    <ImageView
        android:id="@+id/text"/> <!-- Error: text作为id已经存在了 -->
</LinearLayout>

但是在有层次嵌套的布局中,允许同名的id,那么findViewById会找到哪一个呢?

为了得到这个问题的答案,我首先去grepcode上搜了一下ViewGroup.findViewById()的源码实现

clipboard.png

可以看到,ViewGroup去findViewById的时候,先把自己的子元素都拿出来,遍历他们,然后调用它们的findViewById,想象一下,如果子元素是个View,那么id只要配对上了,就直接返回了,如果子元素是个ViewGroup,那么就会继续调用子ViewGroup的这个方法。显然,这是一个深度优先的find。

为了验证这个深度优先,我构造了一个场景。先用文字来描述一下:在LinearLayout里面有一个ListView和一个TextView(这个TextView的id是text),然后ListView用数据去填充一些item,item的布局里面都是id为text的TextView。

然后,我在onCreate的时候去findViewById(R.id.text),会获取到哪一个TextView呢?
a.获取到LinearLayout里面的TextView
b.获取到ListView里面第一个item中的TextView

实验结果是:
在onCreate去findView的时候,获取的是LinearLayout里面的TextView。但是我postDelay在去find一遍的时候,获取到的就是ListView里面的TextView了。

clipboard.png

下图所示:fuck是LinearLayout里面的TextView,shit是ListView中item的TextView
clipboard.png

显然,onCreate的时候,Listview尚未被item填充,因此它的children实际上是空的,因此没法进行深度的遍历,找不到任何东西,当ListView已经被填充了,那么findViewById以深度遍历的形式去遍历,那么就会优先找到里面的元素。

因此,在给自定义布局加id的时候,最好还是在前面加上一个命名空间,以防止不知何时的findViewById获取到错误的View导致bug


如果觉得我的文章对你有用,请随意赞赏
边涛bt · 4月28日

有用...但是正常情况应该严格遵守命名规范......这种深度优先的模式应该是谷歌防止崩溃做的一个处理吧

回复

0

对,所以命名规范才是正确的做法

Kross 作者 · 4月28日
LiuZh · 5月15日

这种命名应该遭到唾弃。。。

回复

载入中...