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的调用过程。