findViewById 原理

有人问了 findViewById 到底做了什么事情,有点懵逼,在此深入研究以下

findViewById 流程图

在这里插入图片描述

1. Activity 的 findViewById

首先 Activity 中调用 findViewById(id):

  • 如果继承 app 包下的 Activity
@Nullable public <T extends View> T findViewById(@IdRes int id) {
	//这里直接获取 window对象去调用其findViewById方法了
    return getWindow().findViewById(id); 
}

继承 support 包下的 AppCompatActivity:

public <T extends View> T findViewById(@IdRes int id) {
        return this.getDelegate().findViewById(id);
    }

getDelegate():

    @NonNull
    public AppCompatDelegate getDelegate() {
        if (this.mDelegate == null) {
            this.mDelegate = AppCompatDelegate.create(this, this);
        }
        return this.mDelegate;
    }

可以看出通过调用本地的 getDelegate 方法得到的是 AppCompatDelegate 的一个实现类 AppCompatDelegateImpl,源码如下:

public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
        return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
}

这里传入 AppCompatDelegateImpl的构造方法的第二个参数,activity.getWindow 得到了 Window 的对象传了过去,并且赋值给了AppCompatDelegateImpl 的成员变量 mWindow.

这时候 getDelegate 调用完毕,就会拿这个 AppCompatDelegate 对象去调用findViewById,但真正调用当然是 AppCompatDelegateImpl 实现类里重写的findViewById,下面看源码:

@Nullable 
public <T extends View> T findViewById(@IdRes int id) {
	this.ensureSubDecor();
	return this.mWindow.findViewById(id); 
}

这就调用了 Window 类下的 findViewById, 到了和app包下Activity同样的地方.

点进去看源码进入 Activity.class 类里的方法:

    /**
     * Finds a view that was identified by the id attribute from the XML that
     * was processed in {@link #onCreate}.
     *
     * @return The view if found or null otherwise.
     */
    public View findViewById(int id) {
        return getWindow().findViewById(id);
    }
/**
     * Finds a view that was identified by the id attribute from the XML that
     * was processed in {@link android.app.Activity#onCreate}.  This will
     * implicitly call {@link #getDecorView} for you, with all of the
     * associated side-effects.
     *
     * @return The view if found or null otherwise.
     */
    public View findViewById(int id) {
        return getDecorView().findViewById(id);
    }

可以看到 Activity 通过调用 getWindow() 获取到一个 Window.java 对象,Window.java 是一个抽象类,真正的对象其实是 PhoneWindow.java 对象PhoneWindow.java 管理 ActivityUI 组件,先这么理解,接着看 Window.java 里面的实现

2. Window.class 的 findViewById

    /**
     * Finds a view that was identified by the id attribute from the XML that
     * was processed in {@link android.app.Activity#onCreate}.  This will
     * implicitly call {@link #getDecorView} for you, with all of the
     * associated side-effects.
     *
     * @return The view if found or null otherwise.
     */
    public View findViewById(int id) {
        return getDecorView().findViewById(id);
    }

getDecorView() 就是返回的是一个具体的 View 对象,来管理 ActivityUI 组件

在这里插入图片描述

具体实现在 PhoneWindow.javaDecorViewAndroid应用的最外层的 View,相当于是所有 View的容器,它定义在 PhoneWindow.java

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
}

DecorView 继承于 FrameLayout.java,具体的继承关系如下:

在这里插入图片描述

可以看到, ActivityfindViewById 方法,又通过 getDecorView() 一步一步传入到ViewfindViewById 了。下面是 View 类。

3. View.class 的 findViewById

    /**
     * Look for a child view with the given id.  If this view has the given
     * id, return this view.
     *
     * @param id The id to search for.
     * @return The view that has the given id in the hierarchy or null
     */
    public final View findViewById(int id) {
        if (id < 0) {
            return null;
        }
        return findViewTraversal(id);
    }

    /**
     * {@hide}
     * @param id the id of the view to be found
     * @return the view of the specified id, null if cannot be found
     */
    protected View findViewTraversal(int id) {
        if (id == mID) {
            return this;
        }
        return null;
    }

可以看到,在 id 合法的情况下,一个 View 会直接返回自己的对象,但是对于 ViewGroup来说,由于 ViewGroup 重写了 findViewTraversal 方法,所以会调用到 ViewGroup 的实现当中:

    /**
     * {@hide}
     */
    @Override
    protected View findViewTraversal(int id) {
        // 如果此id是该ViewGroup的id,直接返回该ViewGroup对象,例如一个LinearLayout
        if (id == mID) { 
            return this;
        }
        // mChildren会在调用setContentView时初始化,里面的View都是ViewGroup的子View,例如在一个LinearLayout标签里面定义的View
        final View[] where = mChildren;
        final int len = mChildrenCount;

        // 开始遍历该ViewGroup的子View
        for (int i = 0; i < len; i++) {
            View v = where[i]; // 加入此v是个ViewGroup对象,则又会进入到该ViewGroup当中执行findViewTraversal,如果没有找到该id对应的View,则跳出执行遍历下个where[]的元素

            if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
                v = v.findViewById(id);// 发现到该View

                if (v != null) {
                    return v;
                }
            }
        }

        return null;
    }

上面就是 findViewById 最核心的代码,主要原理就是递归调用,通俗将,就是从最开始的DecorView里面,一层一层地去匹配,看看这个 id 对应的 View 是否在其中一个ViewGroup 当中。

可以比作是在一整条街道上,有各种各样的建筑,其中一部分建筑还有很多的弄堂,你需要找到其中一条柱子,这时你需要进入到这条街道,逐个逐个建筑物地毯式地去寻找要找的那条柱子,直到你找到那条柱子。
  
其实这样的实现是非常耗时的,假如一个非常庞大复杂的布局当中有非常多的 View 需要执行 findViewById,每一次都是一模一样的递归遍历,所耗费的时间可想而知。

由此可以引申到的就是布局的优化,尽量做到布局要扁平化,少包裹

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值