findViewById的源码解析

作者: 沐沐小风 | 来源:发表于2019-01-11 16:24 被阅读4次

    虽然有了butterknife等依赖注入框架后,对findViewById的使用都不多了,但是前面看butterknife源码的时候,可以看出底层还是通过View中的findViewById方法实现的找View这一工作,所以也就顺便看一下这一部分的源码

    先来个流程图吧

    流程图

    下面细说

    首先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的构造方法的第二个参数,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直接得到window对象去调用findViewById而AppCompatActivity则是得到一个AppCompatDelegate对象去调用findViewById,window对象是在创建其实现类时传入的,为什么绕这么一圈暂时先不去深挖,应是为了兼容而做的一些工作吧

    下面从Window中的findViewById继续看

    @Nullable public <T extends View> T findViewById(@IdRes int id) {

        return getDecorView().findViewById(id); }

    getDecorView()是一个Window下的抽象方法,得到的是一个具体的View对象,因为Window是个抽象类,是不能具体实现管理ActivityUI组件的方法。对DecorView感兴趣可以继续深挖。

    这里得到具体的View后调用View下的findViewById

    @Nullable

    public final <T extends View> T findViewById(@IdRes int id) {

        if (id == NO_ID) { return null; }

        return findViewTraversal(id); }

    那么这个同样是View类下findViewTraversal中是写了啥呢,从名字上看可以看出是变量寻找View

    protected <T extends View> T findViewTraversal(@IdRes int id) {

        if (id == mID) { return (T) this; }

        return null; }

    可以看到这里把传入的id与自己的成员变量mID比较,如果相同说明就是这个View对象,就把自身返回去。

    到这里就有些不明白了,这个mID对于这个View对象来说一次编译应该就会设置一次,那么如果这个mID如果不同是怎么开始下一个View的比较的呢,并没有找到递归或者循环的方法。这个问题下面说

    先看一下这个View对象的mID的设置过程吧。

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

    generateViewId是一个动态生成id的方法,用于当传入的id(注意这里传入的id不是findViewById时传入的那个id)为空的时候,也就是你并没有为这个View设置ID的时候生成一个Id给它。

    下面看上面遗留问题,通过ViewGroup中可以找到对其父类View中的findViewTraversal的重写:可以看到,findViewById的原理,是从头开始找,遇到有子控件的,就递归接着找,找到的View就调用其findViewById去实际比较id与这个view对象的mID

    @Override

    protected <T extends View> T findViewTraversal(@IdRes int id) {

        if (id == mID) { return (T) 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 (T) v; } } }

        return null; }

    因为最上层一开始得到的View实现是Linearlayout等ViewGroup,

    所以在上面getDecorVeiw()后调用的findViewById中的findViewTraversal应该是ViewGroup中重写的这个,然后先比较自身,再遍历子View去比较,直到最后找到对应View返回回去。

    相关文章

      网友评论

        本文标题:findViewById的源码解析

        本文链接:https://www.haomeiwen.com/subject/wuexdqtx.html