setContentView都干了些什么

簡單的梳理一下setContentView()方法都干了什么吱抚。啟動Activity之后百宇,我們寫的布局文件怎么就展示在了該Activity的頁面之上。我們知道的window秘豹,windowmanager携御,decorview,viewrootImpl它們具體的職責是什么,并且它們之間又存在著什么關系既绕。帶著這些問題啄刹,通過閱讀源碼大致的捋一下思路,對Android布局的繪制過程有一個大概的輪廓框架凄贩。
先進入setContentView方法會看到誓军,會來到Activity的setContentView方法中


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

看到調(diào)用到了getWindow()的setContentView方法,這個window類是一個抽象類怎炊,它有一個唯一的實現(xiàn)類谭企,就是PhoneWindow。所以直接查看PhoneWindow的setContentView方法评肆。

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

mContentParent此時為空债查,會進入到installDecor()方法中。

private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
        ......
        ......
    }

首先mDecor為null瓜挽,進入if塊中的generateDecor()方法盹廷,為mDeocr賦值。當mDecor不為null時久橙,就直接調(diào)用mDecor.setWindow(this);方法俄占,將該phonewindow對象與該decorview相關連,decorview中有成員變量mWindow來指向所關聯(lián)的phonewindow對象淆衷。在decorview的構造函數(shù)中也為mWindow賦值了缸榄。
此方法new了一個DecorView對象return了回來。接著調(diào)用generateLayout方法為mContentParent變量賦值祝拯。此方法就是設置DecorView的具體細節(jié)甚带,

        int layoutResource;
        int features = getLocalFeatures();
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_title_icons;
            }
            removeFeature(FEATURE_ACTION_BAR);
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
            layoutResource = R.layout.screen_progress;
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogCustomTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_custom_title;
            }
            removeFeature(FEATURE_ACTION_BAR);
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                layoutResource = a.getResourceId(
                        R.styleable.Window_windowActionBarFullscreenDecorLayout,
                        R.layout.screen_action_bar);
            } else {
                layoutResource = R.layout.screen_title;
            }
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            layoutResource = R.layout.screen_simple;
        }

        mDecor.startChanging();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

以上代碼的第二行通過getLocalFeatures()方法獲取我們設置想要的樣式,這也是為什么調(diào)用requestWindowFeature()方法要在setContentView()之前調(diào)用的原因佳头,如果在之后調(diào)用鹰贵,就獲取不到我們想要設置的值,也就不會得到我們想要的效果康嘉。方法中一大段if的判斷就是根據(jù)不同的feature來選擇要在decorview下添加什么樣的布局樣式碉输。如果什么都沒有設置,會默認選擇R.layout.screen_simple這個布局亭珍,然后會進入mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);這個方法中

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();
    }

此段代碼可以很清晰的看出是將layoutResource的布局inflater為一個view對象敷钾,并添加到了decorview下枝哄,并將decorview中的mContentRoot變量設置為了這個view對象。返回上一段代碼闰非,findViewById調(diào)用的就是Decorview的findviewById,而ID_ANDROID_CONTENT 的值為com.android.internal.R.id.content膘格,看下R.layout.screen_simple布局到底是什么樣子

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

可以很明顯的看出,這個findviewById返回的這個ViewGroup就是此布局中的FrameLayout财松。
接著回到PhoneWindow的setContentView方法中執(zhí)行mLayoutInflater.inflate(layoutResID, mContentParent);此方法會調(diào)用下面方法瘪贱,resource為我們在activity中setContentView()中傳入的布局ID,root為phonewindow的mContentParent辆毡,也就是DecorView中為id為content的framelayout

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

首先獲取XmlResourceParser布局解析器菜秦,調(diào)用inflate(parser, root, attachToRoot); parser為解析器,root為mContentParent舶掖,attachToRoot為true.

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;
            try {
                final String name = parser.getName();

                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            temp.setLayoutParams(params);
                        }
                    }
                    rInflateChildren(parser, temp, attrs, true)球昨;

                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }
            } 
            return result;
        }
    }

AttributeSet attrs = Xml.asAttributeSet(parser);解析布局封裝到attrs中。View result = root;定義一個名為result的view指向mContentParent眨攘,這個result最終會被此方法當做返回值返回主慰。然后獲取到根節(jié)點的name,進入if判斷,如果根節(jié)點是marge標簽鲫售,而且如果mContentParent為空共螺,或者attachToRoot為false,會拋出異常情竹。接著會調(diào)用rInflate方法藐不,此方法會在下面描述。如果跟布局不是marge標簽秦效,會進入else雏蛮,一般情況下,都會走這里的代碼阱州。首先調(diào)用createViewFromTag方法返回一個跟布局標簽的實例化對象temp挑秉。接著判斷root如果不為空,調(diào)用root.generateLayoutParams方法返回該ViewGroup在xml布局中對應的LayoutParams苔货。在此情景中衷模,會返回給我們Framelayout.LayoutParams類型的layoutparams,此時判斷attachToRoot為false蒲赂,會走temp.setLayoutParams(params);但是此時attachToRoot為true,所以不會走此方法刁憋。來到了最重要的一句代碼rInflateChildren(parser, temp, attrs, true)滥嘴;進入此方法就是調(diào)用的上面沒展開的rInflate方法。

void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;

        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            final String name = parser.getName();
            
            if (TAG_REQUEST_FOCUS.equals(name)) {
                parseRequestFocus(parser, parent);
            } else if (TAG_TAG.equals(name)) {
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException("<merge /> must be the root element");
            } else {
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true);
                viewGroup.addView(view, params);
            }
        }

        if (finishInflate) {
            parent.onFinishInflate();
        }
    }

此方法獲取到布局的深度之后至耻,while循環(huán)里開始執(zhí)行以下邏輯:獲取跟布局若皱,進入if判斷镊叁,如果不是requestFocus、tag走触、include晦譬、merge標簽,就會進入else互广,此時的情景也是進去else敛腌,繼續(xù)分析else的代碼,跟上一個方法很類似惫皱,也是根據(jù)標簽名創(chuàng)建對象像樊,然后獲取layoutparams,將此view添加到他的parent中旅敷,重復調(diào)用rInflateChildren方法生棍。這個深度遍歷過程結束后,DecorView中的framelayout已經(jīng)被添加上了我們需要添加的布局媳谁。但是這僅僅是devorview有了視圖涂滴,它又是怎樣展示到了我們的activity上的呢?它們之間怎么關聯(lián)在一起的呢晴音?那就去activity的啟動過程尋找看看了柔纵。

布局與Activity的關聯(lián)

來到ActivityThread類中handleLaunchActivity方法中,Activity a =performLaunchActivity(r, customIntent);進入performLaunchActivity方法通過反射創(chuàng)建了該Activity實例段多,之后調(diào)用了該實例的attach方法,此方法中執(zhí)行了一些與window有關的代碼

        mWindow = new PhoneWindow(this, window);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);

該Activity對象實例化了一個PhoneWindow的成員變量首量,并設置了該windos對象的callback就是此Activity。然后執(zhí)行了此代碼进苍,調(diào)用了activity的onCreate函數(shù)加缘,mInstrumentation.callActivityOnCreate(activity, r.state);而onCreate中調(diào)用了我們熟悉的setContentView(R.layout.activity_main);完成了我們上面DecorView初始化,并把布局添加到DecorView中的framelayout中觉啊。

回到handleLaunchActivity方法中拣宏,初始化完Activity

if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            reportSizeConfigurations(r);
            Bundle oldState = r.state;
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
}

此時a!=null成立,進入if執(zhí)行了handleResumeActivity方法杠人,重點看此方法中的這一段代碼

                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (r.mPreserveWindow) {
                    a.mWindowAdded = true;
                    r.mPreserveWindow = false;
                    ViewRootImpl impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                }
                if (a.mVisibleFromClient && !a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                }

主要看此段代碼的最后一行勋乾,獲取到Activity的windowmanger對象,執(zhí)行wm.addView(decor, l);WindowManager是個接口嗡善,他的實現(xiàn)類是WindowManagerImpl,所以看它的addView方法辑莫,他的addView方法又調(diào)用了WindowManagerGlobal的addView。所以罩引,直接查看WindowManagerGlobal的addView方法

 public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
          //......
          ViewRootImpl root;
          View panelParentView = null;
          //......  

            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }

        // do this last because it fires off messages to start doing things
        try {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }

創(chuàng)建了一個ViewRootImpl類的實例各吨,把decorview, viewRootImpl, wparams添加到相應的集合中,接著執(zhí)行了root.setView方法袁铐,此方法重點有三部分代碼

  1. requestLayout();:會執(zhí)行scheduleTraversals方法揭蜒,此方法又會執(zhí)行一個TraversalRunnable的runnable對象横浑,此對象run方法的實現(xiàn)是一個doTraversal();方法,它又會執(zhí)行performTraversals();而此方法會依次執(zhí)行performMeasure屉更、performLayout徙融、performDraw來進行繪制。
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);

通過跨進程方式通知WindowManagerService來添加此window.

  1. view.assignParent(this);將此ViewRootImpl的實例設置為該decorview的mParent瑰谜。所以每個view調(diào)用requestLayout()方法進行重繪的時候欺冀,都會調(diào)用到父類的requestLayout,會一直調(diào)用到ViewRootImpl的requestLayout似舵,而去執(zhí)行scheduleTraversals()方法進行重繪脚猾。

總結

可以看到,

  • Windowmanager是單例的砚哗,它對所有的Window進行管理的類龙助。它內(nèi)部管理著ViewRootImpl,View,WindowManager.LayoutParams
  • window是描述窗口的抽象類,它是一個抽象的概念蛛芥,沒有具體的體現(xiàn)提鸟。所有的視圖都依附它而存在。他的實現(xiàn)類PhoneWindow提供了操作窗口的實現(xiàn)仅淑。它包含一個decorview實例称勋。
  • Decorview是所有布局的根view,它承載著對我們要顯示布局的修飾。
  • ViewRootImpl是視圖樹的頂級view涯竟,是繪制的起點赡鲜。它是window與view之間的橋梁
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市庐船,隨后出現(xiàn)的幾起案子银酬,更是在濱河造成了極大的恐慌,老刑警劉巖筐钟,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件揩瞪,死亡現(xiàn)場離奇詭異,居然都是意外死亡篓冲,警方通過查閱死者的電腦和手機李破,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來壹将,“玉大人嗤攻,你說我怎么就攤上這事》谈” “怎么了屯曹?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我恶耽,道長,這世上最難降的妖魔是什么颜启? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任偷俭,我火速辦了婚禮,結果婚禮上缰盏,老公的妹妹穿的比我還像新娘涌萤。我一直安慰自己,他們只是感情好口猜,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布负溪。 她就那樣靜靜地躺著,像睡著了一般济炎。 火紅的嫁衣襯著肌膚如雪川抡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天须尚,我揣著相機與錄音崖堤,去河邊找鬼。 笑死耐床,一個胖子當著我的面吹牛密幔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播撩轰,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼胯甩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了堪嫂?” 一聲冷哼從身側響起偎箫,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎溉苛,沒想到半個月后镜廉,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡愚战,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年娇唯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寂玲。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡塔插,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拓哟,到底是詐尸還是另有隱情想许,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站流纹,受9級特大地震影響糜烹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜漱凝,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一疮蹦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧茸炒,春花似錦愕乎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至紊册,卻和暖如春比肄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背湿硝。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工薪前, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人关斜。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓示括,卻偏偏與公主長得像,于是被迫代替她去往敵國和親痢畜。 傳聞我的和親對象是個殘疾皇子垛膝,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容