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的調用過程。