虽然有了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返回回去。
网友评论