findViewById原理

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


  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值