Android Activity setContentView流程解析

Activity setContentView流程解析

參考圖解:

自主生碼.jpg

1.當MainActivity直接繼承自Activity時

此時會執(zhí)行Activity類的setContentView方法:

    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

主要的邏輯在getWindow().setContentView(layoutResID)中,下面將以實現類PhoneWindow的setContentView方法進行講解
在該方法的開頭,首先會對mContentParent進行判空檢查,為空時將調用installDecor()進行DecorView的初始化:

    if (mContentParent == null) {
        installDecor();
    } 

進入installDecor方法,首先當decorView為空時,會調用generateDecor方法進行DecorView的創(chuàng)建:

    if (mDecor == null) {
        //核心方法
        mDecor = generateDecor(-1);
        //......
    } else {
        mDecor.setWindow(this);
    }

進入generateDecor方法,該方法比較簡短,在完成context的創(chuàng)建后就創(chuàng)建出一個DecorView并返回:

    protected DecorView generateDecor(int featureId) {
        Context context;
        //根據不同情況對context進行初始化
        //......
        return new DecorView(context, featureId, this, getAttributes());
    }

至此完成了對mDecor變量的賦值。
回到installDecor方法,此時將進入contentParent的創(chuàng)建邏輯,核心方法是generateLayout,該方法將對mContentParent進行賦值(這個contentParent就將作為MainActivity布局的父容器):

    if (mContentParent == null) {
        //核心方法
        mContentParent = generateLayout(mDecor);
        final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                R.id.decor_content_parent);
        if (decorContentParent != null) {
            mDecorContentParent = decorContentParent;
            //......
        } 
        //...... 
    }    

進入generateLayout方法,

    protected ViewGroup generateLayout(DecorView decor) {
        //......
        //上面省略的代碼根據Flags、Feature等數據完成了對layoutResource的賦值
        mDecor.startChanging();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        //ID_ANDROID_CONTENT值為com.android.internal.R.id.content
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        //......
        mDecor.finishChanging();
        return contentParent;    
    }

進入onResourcesLoaded方法,此方法主要的工作就是將layoutResource對應的xml文件解析并添加到decorView中:

    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        //......
        mDecorCaptionView = createDecorCaptionView(inflater);
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }

此時完成了對decorView的xml布局文件的加載。
回到generateLayout方法,此時將通過findViewById獲取到com.android.internal.R.id.content這個ViewGroup,上述decorView加載的xml文件里就有一個控件id與之對應,這個控件就是我們加載并添加MainActivity的布局文件的地方。
generateLayout方法完成了對mContentParent的賦值,也就是說,DecorView中放置MainActivity內容的父容器已經準備完畢。
至此,installDecor方法的核心邏輯介紹完畢,接下來將MainActivity的布局文件加載到decorView的content中,即結束了整個加載流程:

    public void setContentView(int layoutResID) {
        //......
        mLayoutInflater.inflate(layoutResID, mContentParent);
        //......
    }

2.當MainActivity直接繼承自AppCompatActivity時

首先,進入AppCompatActivity的setContentView方法:

    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }

此處的getDelegate方法會創(chuàng)建一個AppCompatDelegate對象,由AppCompatDelegateImpl類實現,
進入AppCompatDelegateImpl的setContentView方法:

    public void setContentView(int resId) {
        ensureSubDecor();
        //......
    }

首先,進入ensureSubDecor方法:

   private void ensureSubDecor() {
        if (!mSubDecorInstalled) {
            //核心方法
            mSubDecor = createSubDecor();
           //......
        }
    }

進入createSubDecor方法:

   private ViewGroup createSubDecor() {
        //ensureWindow方法完成Delegate與Window的綁定,確保mWindow存在
        ensureWindow();
        mWindow.getDecorView();
        //......
    }

首先,進入PhoneWindow的getDecorView方法:

    public final @NonNull View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            installDecor();
        }
        return mDecor;
    }

可以看到,此處也會與第一種情況一樣調到installDecor方法,該方法會將到PhoneWindow內部的DecorView的xml文件的加載解析為止的操作全部完成
回到createSubDecor方法:

   private ViewGroup createSubDecor() {
        //......
        final LayoutInflater inflater = LayoutInflater.from(mContext);
        ViewGroup subDecor = null;
        //......
        //完成subDecor的布局解析與加載

        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                R.id.action_bar_activity_content);

        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
        if (windowContentView != null) {
            while (windowContentView.getChildCount() > 0) {
                final View child = windowContentView.getChildAt(0);
                windowContentView.removeViewAt(0);
                contentView.addView(child);
            }

            windowContentView.setId(View.NO_ID);
            contentView.setId(android.R.id.content);
        }
        mWindow.setContentView(subDecor);
        return subDecor;
    }

windowContentView為android.R.id.content對應的View,contentView為R.id.action_bar_activity_content對應的View,與第一種情況不同的是:它會先將android.R.id.content的子View全部遷移到R.id.action_bar_activity_content上,之后將R.id.action_bar_activity_content對應的View的id替換為android.R.id.content,之后將subDecor添加到PhoneWindow上,進入PhoneWindow的getDecorView方法(此處直接列出了最終會執(zhí)行到的方法):

   public void setContentView(View view, ViewGroup.LayoutParams params) {
        if (mContentParent == null) {
            installDecor();
        } 
        //......
        mContentParent.addView(view, params);
        //......
    }

由于之前已經完成了mContentParent的創(chuàng)建,所以不會再執(zhí)行installDecor,將直接進行addView。
至此,將decorView添加到了PhoneWindow上,回到最初的AppCompatDelegateImpl的setContentView方法完成對MainActivity xml布局文件的加載:

    public void setContentView(int resId) {
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        //......
    }

3.流程總結

1.Activity
核心就是PhoneWindow的setContentView方法,其主要干了兩件事:
1.完成DecorView的創(chuàng)建與加載
2.將MainActivity的布局加載到DecorView內的一個ViewGroup中

創(chuàng)建DecorView,即installDecor方法,其內部用到了兩個核心的方法:
1.generateDecor方法創(chuàng)建出DecorView對象
2.generateLayout方法完成這個DecorView對象的布局加載,并完成了MainActivity的父容器的賦值(即contentParent變量)

2.AppCompatActivity
核心就是AppCompatDelegateImpl的setContentView方法,它主要干了兩件事:
1.準備好subDecor
2.將MainActivity的布局加載到subDecor內的一個ViewGroup中

準備subDecor,即ensureSubDecor方法,用于確保subDecor已經完成創(chuàng)建,內部的核心邏輯在createSubDecor方法中

createSubDecor主要干了幾件事:
1.確保Delegate獲取到了MainActivity的PhoneWindow實例(ensureWindow方法)
2.完成PhoneWindow內部的DecorView的創(chuàng)建與準備(也就是第一種情況的installDecor方法)
3.創(chuàng)建subDecorView,并讓subDecor接管R.id.content(原先屬于PhoneWindow內部的DecorView)
4.清空PhoneWindow內部的DecorView的內容,并將subDecor添加到該DecorView中
5.把MaiActivity的布局加載到subDecor中(即R.id.content)

4.差異梳理

第一種情況直接走了Activity的setContentView方法,加載用戶布局也是直接用的Activity的PhoneWindow里的DecorView
第二種情況走了AppCompatDelegateImpl的setContentView方法,其PhoneWindow是從MainActivity獲取的。使用了一個中介的subDecorView,PhoneWindow自身也有一個DecorView,并且完成了至DecorView的xml文件加載為止的所有操作,之后,subDecorView將被添加到該DecorView中,而原先往R.id.content的布局內容添加將全部由subDecorView進行接管,至此,方完成第二種情況正式加載MainActivity資源操作前的全部工作,而第一種情況則在PhoneWindow的DecorView xml文件加載工作完成時就已告結束。
綜上所述,兩者最顯著的區(qū)別就是第二種情況多了一層subDecorView的添加與替換接管的操作。

————————————————
版權聲明:本文為CSDN博主「浮生一落英」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_42700685/article/details/128617358

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容