天天看点

Android findViewById源码分析

Android中常用到findViewById的地方,一是Activity中直接调用findViewById和二个是Fragment中通过View去调用findViewById。

1、先来看下第二种

我们在用Fragment时常有这样一段代码

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    val view = inflater.inflate(R.layout.pw_fragment_main_team_main, container,false)
    teamInvitationTv = view.findViewById(R.id.pw_team_invitation_tv) as TextView
    ......
    ......
}
           

先获取到当前布局的View,然后通过View去调用查询某个具体的View。

接着查看View中部分源码

...
    ...

    @Nullable
    public final <T extends View> T findViewById(@IdRes int id) {
        if (id == NO_ID) {
            return null;
        }
        return findViewTraversal(id);
    }
    
    ...
    ...

    protected <T extends View> T findViewTraversal(@IdRes int id) {
        if (id == mID) {
            return (T) this;
        }
        return null;
    }

    ...
    ...
           

是不是一脸懵逼,就这样怎么查找到某个具体的View。思索一下会发现我们上面在Fragment中创建的View,如果布局中只是一个View(TextView、ImageView、Button等等)那么上面的代码没有任何问题,因为他只是一个单纯的View而没有子布局,那么findViewTraversal中发现两者的ID一样然后直接返回调用的View。那么如果我们的布局是一个容器布局(ViewGroup)呢?显然不可能只有上面面的代码。

接着我们打开ViewGgroup 的部分源码

@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;
    }
           

没错,在ViewGroup中重写findViewTraversal方法。在这里他遍历了儿子辈子布局然后递归调用findViewById方法不断的去递归查找对应ID的View,直到结束,因此findViewById效率是有点低的。

2、我们再来看下第一种在Activity中调用findViewById(其实到最后是一样的,还是调用View和ViewGroup中的方法)

因为用的是AppCompatActivity,所以找到其中部分源码

...

    @SuppressWarnings("TypeParameterUnusedInFormals")
    @Override
    public <T extends View> T findViewById(@IdRes int id) {
        return getDelegate().findViewById(id);
    }

    ...

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

显然发现这里又调用了AppCompatActivity的代理类AppCompatDelegate中的findViewById的方法

接着找到AppCompatDelegate对应的源码

public abstract class AppCompatDelegate {
    
    ...

    @SuppressWarnings("TypeParameterUnusedInFormals")
    @Nullable
    public abstract <T extends View> T findViewById(@IdRes int id);

    ...

    /**
     * Create an {@link androidx.appcompat.app.AppCompatDelegate} to use with {@code activity}.
     *
     * @param callback An optional callback for AppCompat specific events
     */
    public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
        return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
    }

}
           

发现这个代理类是一个抽象类,通过他的create的方法发现他的实现类AppCompatDelegateImpl,接着找AppCompatDelegateImpl

@SuppressWarnings("TypeParameterUnusedInFormals")
    @Nullable
    @Override
    public <T extends View> T findViewById(@IdRes int id) {
        //这个主要保证页面已经构建完成,这里不深入
        ensureSubDecor();
        return (T) mWindow.findViewById(id);
    }
           

发现这里调用的mWindow的findViewById的方法,mWindow在这里创建

final Window mWindow;
...
AppCompatDelegateImpl(Context context, Window window, AppCompatCallback callback) {
        mContext = context;
        mWindow = window;
        mAppCompatCallback = callback;

        mOriginalWindowCallback = mWindow.getCallback();
        if (mOriginalWindowCallback instanceof AppCompatWindowCallback) {
            throw new IllegalStateException(
                    "AppCompat has already installed itself into the Window");
        }
        mAppCompatWindowCallback = new AppCompatWindowCallback(mOriginalWindowCallback);
        // Now install the new callback
        mWindow.setCallback(mAppCompatWindowCallback);

        final TintTypedArray a = TintTypedArray.obtainStyledAttributes(
                context, null, sWindowBackgroundStyleable);
        final Drawable winBg = a.getDrawableIfKnown(0);
        if (winBg != null) {
            mWindow.setBackgroundDrawable(winBg);
        }
        a.recycle();
    }
           

那么又回到了AppCompatDelegate类中的

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

再去找Activity的getWindow方法

public class Activity extends...{
    @UnsupportedAppUsage
    private Window mWindow;
    ...
    @UnsupportedAppUsage
    final void attach(...){
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
    }
    ...
}
           

到这里发现调用的是PhoneWindow的findViewById,再找,发现PhoneWindow中并没有,那就再找他的父类Window类

public abstract class Window {
    ...
    @Nullable
    public <T extends View> T findViewById(@IdRes int id) {
        return getDecorView().findViewById(id);
    }

    public abstract View getDecorView();
    ...
}
           

由于getDecorView是个抽象方法,所以我们再到PhoneWindow中查找其具体实现

@Override
    public final View getDecorView() {
        //我们只要知道mDecor是什么就可以
        if (mDecor == null || mForceDecorInstall) {
            installDecor();
        }
        return mDecor;
    }

    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            //最关键代码,主要在这里去创建一个mDecor
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        省略代码
        ...
        ...
    }

protected DecorView generateDecor(int featureId) {
        // System process doesn't have application context and in that case we need to directly use
        // the context we have. Otherwise we want the application context, so we don't cling to the
        // activity.
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, getContext());
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        //追踪到这里
        return new DecorView(context, featureId, this, getAttributes());
    }
           

那只能在看下DecorView的源码

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    ...
    ...
}
           

发现DecorView继承了FrameLayout,而FrameLayout又继承了ViewGroup,所以最终调用的是ViewGroup中的findViewById,绕了一大圈。

以上就是findViewById的调用过程。

继续阅读