配置布局的xml文件时,有时会给View配置id,而在Activity中寻找到该View的方法就是通过该id号来找到该View的。具体调用的方法就是findViewById。

        这个findViewById方法顾名思义,就是根据id来找到View,而Android内部的实现,也同样遵循了这个原则。具体见下面的源代码分析。

        Activity中的findViewById方法执行流程如下:Activity.findViewById() >> Window.findViewById() >> DecorView.findViewById()。在这个流程中,没有干具体的事情,只是一步步调用到了DecorView的findViewById方法。


Activity.java

// findViewById方法
public View findViewById(@IdRes int id) {
    return getWindow().findViewById(id);
}
 
// getWindow方法,获取到activity对应的window对象
public Window getWindow() {
    return mWindow;
}



PhoneWindow.java

public View findViewById(@IdRes int id) {
    return getDecorView().findViewById(id);
}
 
// 获取到root View,而该root View继承自FrameLayout
public final View getDecorView() {
    if (mDecor == null) {
        installDecor();
    }
    return mDecor;
}



        DecorView继承自FrameLayout,是根View,可以理解为整个View树的起始点,联想测量、布局、绘制以及事件分发消费,那么findViewById也是这样的处理思路,概括来说,Activity的findViewById方法就是从根View自身开始,遍历自身子View,尝试查找View树中是否存在mID等于id的View或ViewGroup。说是尝试,因为也有可能找不到。

        从Root View开始,就是要正式进入到寻找mID为id的View或ViewGroup过程了。具体执行流程参考如下:View.findViewById() >> ViewGroup.findViewTraversal() >> View.findViewById() >> ViewGroup.findViewTraversal() >> View.findViewById() >> View.findViewTraversal()。


View.java

// 根据给定的id找指定的View(ViewGroup不能重写)
public final View findViewById(@IdRes int id) {
    if (id < 0) { // id不能为负数
        return null;
    }
    // 调用findViewTraversal方法
    return findViewTraversal(id);
}
 
// 通过遍历找到View
protected View findViewTraversal(@IdRes int id) {
    if (id == mID) {
        // 如果id号与View的mID一致,则返回View自身
        return this;
    }
    // 否则返回null
    return null;
}




ViewGroup.java

// 重写了View的findViewTraversal方法,通过遍历找到View
protected View findViewTraversal(@IdRes int id) {
    // 如果id号恰好与viewgroup的mID一致,就返回ViewGroup自身
    if (id == mID) {
        return this;
    }
 
    final View[] where = mChildren;
    final int len = mChildrenCount;
 
    // 遍历子View,
    for (int i = 0; i < len; i++) {
        View v = where[i];
 
        if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
            // ①如果该子View是ViewGroup,就继续先判断自身的mID是否匹配
            //   如果不匹配,再遍历该ViewGroup的子View,继续判断是否匹配,依次循环下去
            // ②如果该子View是View,就直接判断View的mID是否匹配
            v = v.findViewById(id);
 
            if (v != null) {
                // 如果匹配成功,则返回找到的这个View对象
                return v;
            }
        }
    }
    // 所有子View及其ViewGroup自身的mID都不能匹配上id号,那么就找不到该id号匹配的View了,返回null
    return null;
}




        由于View的findViewById是final,所以ViewGroup不可以重写。而我们可以注意到在View的findViewById中对id是否为负数进行了判断,那么问题来了,为什么不在Activity中判断呢?每次都判断不是浪费时间吗?


        因为不仅Activity会调用findViewById方法,当用LayoutInflater导入一个自定义的布局时,也有需要根据布局中定义的id来找到对应的View,从而进行处理。此时,也要进行判断id是否为负数。



        总结如下,Activity的findViewById最终还是会调用到View的findViewById方法;Activity的findViewById方法就是从根View自身开始,遍历自身子View,尝试查找View树中是否存在mID等于id的View或ViewGroup;View的findViewById方法就是从该View自身开始,遍历自身子View,尝试查找该View的树中是否存在mID等于id的View或ViewGroup。