配置布局的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。