有人问了 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
管理 Activity
的 UI
组件,先这么理解,接着看 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
对象,来管理 Activity
的 UI
组件
具体实现在 PhoneWindow.java
,DecorView
是 Android
应用的最外层的 View
,相当于是所有 View的容器
,它定义在 PhoneWindow.java
:
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
}
DecorView
继承于 FrameLayout.java
,具体的继承关系如下:
可以看到, Activity
的 findViewById
方法,又通过 getDecorView()
一步一步传入到View
的 findViewById
了。下面是 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
,每一次都是一模一样的递归遍历,所耗费的时间可想而知。
由此可以引申到的就是布局的优化,尽量做到布局要扁平化,少包裹