对 findViewById 的了解

1,358 阅读2分钟

Activity

public View findViewById(@IdRes int id) {
    return getWindow().findViewById(id);
}

从这个方法我们可以看到Activity中的findViewById方法是通过getWindow()方法返回的Window对象调用了其findViewById方法而得来的。

Window

public View findViewById(@IdRes int id) {
    return getDecorView().findViewById(id);
}

Window类中的findViewById方法返回的是getDecorView()的findViewById,前面说Window是个抽象类,不能具体实现管理ActivityUI组件的方法,那么这个getDecorView()就是返回的是一个具体的View对象,来管理Activity的UI组件。

View

public final View findViewById(@IdRes int id) {
    if (id < 0) {
        return null;
    }
    return findViewTraversal(id);
}
protected View findViewTraversal(@IdRes int id) {
    if (id == mID) {
        return this;
    }
    return null;
}

从这里看,View类的findViewById方法是直接调用findViewTraversal(),用mID跟传入的id对比,如果存在就返回当前view,不存在就返回null。findViewTraversal从字面上理解,这个方法的意思是反复寻找view的ID。但是findViewTraversal方法中似乎并没有发现“反复”或“循环”的意思。凭感觉,查找mID应该是不断+1比较,如果是ViewGroup(容器组件),里面还有View的话,还应该是一个递归的过程,从这里看不出来。所以我们还需要找mID是如何设置的。

public void setId(@IdRes int id) {
    mID = id;
    if (mID == View.NO_ID && mLabelForId != View.NO_ID) {
        mID = generateViewId();
    }
}
private int mLabelForId = View.NO_ID;

先把id给了mID,如果mID为空(就是当前ID没有被加过),而且不是作为标签,那么就给他加一个ID,动态生成控件的ID。

public static int generateViewId() {
    for (;;) {
        final int result = sNextGeneratedId.get();
        // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
        int newValue = result + 1;
        if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
        if (sNextGeneratedId.compareAndSet(result, newValue)) {
            return result;
        }
    }
}

一个固定地址开始,一个一个找ID,设置的ID值似乎连续的。然后在后面的查找中,找递归,找循环。。没有找到类似的代码。

在ViewGroup中,已经对View的findViewTraversal()方法进行重写了!

protected View findViewTraversal(@IdRes 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;
}

这个是重写的方法:可以非常清楚的看到,findViewById的原理,是从头开始找,遇到有子控件的,就递归接着找。先不考虑具体细节,到这里,开始的猜想得到了证实。其中v.findViewById又调用View中的方法,而View的findViewById调用的findViewTraversal()。这样实现了一个递归。

注:在找findViewById的过程中,如果忽略子类ViewGroup对View方法的重写,就会导致在View.class万行代码中陷入死循环,找不到想看到的方法。

现在为止,已经把findViewById的源码分析了一遍,我们再来总结一下吧。组件ID值(句柄)的设定,是通过View类实现的。在查找指定ID的时候,如果是容器组件的话,是通过被View的子类ViewGroup重写的findViewTraversal()方法实现的。查找ID的过程类似于数据结构中讲解的深度优先搜索的概念。Activity通过把ID值传递给Window类,Window类的方法通过返回的View对象,来管理Activity的UI组件。传递的ID值与提前设定好的ID进行比较,并且是按照View结构的DOM树进行深度优先搜索,从而获取指定的组件对象。

给定 2 个 View 视图引用,您将如何查找它们的祖先视图?

采用递归先序遍历的方法,分别从左右子树中找目标节点,如果左子树中找到了二者,则返回左子树,如果右子树找到了二者则返回右子树;左右子树各找到一个,则返回当前节点。

public class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null || root == p || root == q)  return root;
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        if(left != null && right != null)   return root;
        return left != null ? left : right;
    }
}