findViewById原理
前言
从表面上来看,findViewById就是根据R文件中的id值查询到相应的View,然后返回。
那么问题来了,这些View是在find的时候才被实例化还是父View实例化好的时候就已经实例化好了,findViewById只是从数据源中取出来。
答案是在父类被实例化好的时候就已经完成了子类的实例化,并且形成了一个DOM树。这个DOM数其实就是一个View数组
所以为了理解findViewById的原理还要从一个Activity的实例化开始讲解。
如何初始化所有的View对象
1.在Activity的onCreate方法中有一句
setContentView(R.layout.activity_main);
这一句代码将布局文件的id作为参数传入
2.然后在PhoneWindow类中的public void setContentView(int layoutResID)开始了实例化。在这个方法中核心代码是
private LayoutInflater mLayoutInflater;
……
mLayoutInflater.inflate(layoutResID, mContentParent);
3.最后在LayoutInflater 中的
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context)mConstructorArgs[0];
mConstructorArgs[0] = mContext;
View result = root;
……
//按标签创建子View
final View temp = createViewFromTag(root, name, attrs, false);
……
// Inflate all children under temp
//实例化它所有的子View
rInflate(parser, temp, attrs, true, true);
……
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
//如果View不为空就放入根节点
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}
……
return result;
}
}
这个方法会讲XML解析生成一个View对象然后addView到根节点里
4.而在addView 最终会调用ViewGroup中是
private void addInArray(View child, int index) {
View[] children = mChildren;
final int count = mChildrenCount;
final int size = children.length;
if (index == count) {
if (size == count) {
//扩充数组的大小
mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
System.arraycopy(children, 0, mChildren, 0, size);
children = mChildren;
}
children[mChildrenCount++] = child;
} else if (index < count) {
if (size == count) {
mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
System.arraycopy(children, 0, mChildren, 0, index);
System.arraycopy(children, index, mChildren, index + 1, count - index);
children = mChildren;
} else {
System.arraycopy(children, index, children, index + 1, count - index);
}
children[index] = child;
mChildrenCount++;
if (mLastTouchDownIndex >= index) {
mLastTouchDownIndex++;
}
} else {
throw new IndexOutOfBoundsException("index=" + index + " count=" + count);
}
}
简单是说就是生成了一个View数组
findViewById的工作原理
那么findViewById是怎么工作的了? 我们沿着继承树向上找.
1.Activity中
public View findViewById(int id) {
return getWindow().findViewById(id);
}
2.getWindow:
public Window getWindow() {
return mWindow;
}
mWindow 是 private Window mWindow; 是一个Window类型的变量
3.进入Window类,在Window中查找findViewById:
public View findViewById(int id) {
return getDecorView().findViewById(id);
}
4.发现getDecorView()是Window中的一个抽象方法。而Window唯一的子类是PhoneWindow.
5.在PhoneWindow找到getDecorView()方法
public final View getDecorView() {
if (mDecor == null) {
installDecor();
}
return mDecor;
}
而mDecor是Phone的一个内部类DecorView
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker
它继承了FrameLayout ,而FrameLayout 是ViewGroup的子类
6.最终我们在ViewGroup中找到了执行findViewById真正的主体方法。
@Overrideprotected View findViewTraversal(int id) {
if (id == mID) {
return this;
}
final View[] where = mChildren;
final int len = mChildrenCount;
for (int i = 0; i < len; i++) {
View v = where[i];
if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
v = v.findViewById(id);
if (v != null) {
return v;
}
}
}
return null;
}
该方法意思就是遍历我们前面已经加载好的View数组对每个view执行findViewById
,这个findViewById是在View中被实现的。它会调用findViewTraversal来辨别寻找的id与找到的id是否相等
protected View findViewTraversal(int id) {
if (id == mID) {
return this;
}
return null;
}
而这个mID的值是在View被初始化时就已经被赋值了。通过遍历如果id存在,就能成功找到View
总结
当一个View被inflate时,它会将xml解析成一个View数组作为自己的成员变量。同时将id赋给有id的View中一个成员变量mID.
当findViewById时,最终会调用ViewGroup中的findViewTraversal,这个方法会遍历所有的子View,形成一个递归查询,找到最末端。如果找到就会返回停止查询,如果没找到就会返回为null