天天看點

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

繼續閱讀