setContentView源碼理解

Android窗口機制之由setContentView引發(fā)的Window,PhoneWindow支示,DecorView源碼理解

Activity啟動流程源碼分析

簡單分析Binder工作機制

由上一篇文章Activity啟動流程源碼分析,Activity啟動完成最終調(diào)用了ActivityThread.handleLaunchActivity->ActivityThread.performLaunchActivity然后回調(diào)Activity.onCreate鄙才,所以,接著由setContentView引出的Window促绵,PhoneWindow攒庵,DecorView源碼理解,最近也看了好多相關(guān)的文章,記錄自己的見解:

window.png

從上面的類圖看:

  • Window是個抽象類败晴,定義一些頂層窗口的行為策略浓冒,而Window的實現(xiàn)類是PhoneWindow;

  • DecorView其實是整個Activity窗口的裝飾類吧尖坤,繼承FrameLayout稳懒;

  • PhoneWindow會持有一個DecorView和mContentParent,mContentParent是DecorView的直接ViewGroup的contentView慢味,即:用來顯示的主要內(nèi)容场梆,不包括title之類的。

第一次畫圖纯路,不是很規(guī)范或油,將就著看吧,我們從Activity的setContentView方法開始:

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

Activity的setContentView方法驰唬,通過layoutResID設(shè)置Activity的主體內(nèi)容顶岸,不包括標題欄狀態(tài)欄,這個資源將被填充至activity的頂層view也就是DecorView的contentView中叫编,而這個方法會直接調(diào)用Window.setContentView方法辖佣,而Window是個抽象類,所以我們看Window的實現(xiàn)類PhoneWindow的setContentView方法:

public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        //創(chuàng)建DecorView搓逾,并內(nèi)容布局賦值到PhoneWindow的mContentParent上
        //也就是說mContentParent是DecorView的直接子View殿衰,然后通過將layoutResID的布局有填充的mContentParent上
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }
    /**
     * 經(jīng)過上面的步驟我們已經(jīng)獲取了DecorView中contectView控硼,那么接下來就是就要將傳進來的layoutResID填充contentView的布局了
     */
    //是否有過度動畫
    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        //mContentParent是DecorView的中contentView,就是DecorView的孫子税课,將layoutResID填充到mContentParent中
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        //回調(diào)通知表示完成界面加載
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

整個流程就是先判斷mContentParent是否為null,就調(diào)用installDecor方法去初始化DecorView宋渔,mContentParent是DecorView的內(nèi)容ViewGroup,如果mContentParent填充完就把layoutResID調(diào)用inflate方法將layoutResID的布局填充至mContentParent,剛剛說了installDecor初始化DecorView裳食,我們來看看installDecor方法。

 private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
        //調(diào)用該方法創(chuàng)建new一個DecorView
        mDecor = generateDecor(-1);
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    } else {
        //DecorView持有PhoneWindow
        mDecor.setWindow(this);
    }

    //一開始DecorView未加載到mContentParent芙沥,所以此時mContentParent=null
    if (mContentParent == null) {
        //該方法將mDecorView添加到Window上綁定布局
        /**
         * Decorview需要填充自己的直接布局有可能是LinearLayout或者FrameLayout,
         * 然后通過ID_ANDROID_CONTENT獲取contentView诲祸,最后返回,將contentview復(fù)制給phonewindow的mContentParent
         */
        mContentParent = generateLayout(mDecor);



// 省略一萬行代碼................
}

有installDecor方法內(nèi)容較多而昨,省略了一些救氯,在installDecor方法中,如果mDecor 為null的話就會調(diào)用generateDecor方法創(chuàng)建一個DecorView并返回歌憨,上面說到mContentParent 是DecorView的內(nèi)容ViewGroup着憨,所以在如果mContentParent 為null的話將調(diào)用generateLayout方法生成mContentParent ,接下來就重點看看generateLayout方法务嫡,因為setContentView的大部分內(nèi)容都在這里做了甲抖, Decorview需要填充自己的內(nèi)容布局有可能是LinearLayout或者FrameLayout,最后返回,將contentview復(fù)制給phonewindow的mContentParent然后通過ID_ANDROID_CONTENT獲取contentView心铃,代碼有點多准谚。

protected ViewGroup generateLayout(DecorView decor) {
    //省略............很多代碼就看DecorView的布局選擇填充............

    //填充窗口的裝飾
    // Inflate the window decor.

    int layoutResource;

    //根據(jù)features選擇填充布局
    int features = getLocalFeatures();
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
        layoutResource = R.layout.screen_swipe_dismiss;
        setCloseOnSwipeEnabled(true);
    } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {

        //懸浮FloatButton
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleIconsDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_title_icons;
        }
        // XXX Remove this once action bar supports these features.
        removeFeature(FEATURE_ACTION_BAR);
        // System.out.println("Title Icons!");
    } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
            && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
        // Special case for a window with only a progress bar (and title).
        // XXX Need to have a no-title version of embedded windows.
        layoutResource = R.layout.screen_progress;
        // System.out.println("Progress!");
    } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
        // Special case for a window with a custom title.
        // If the window is floating, we need a dialog layout
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogCustomTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_custom_title;
        }
        // XXX Remove this once action bar supports these features.
        removeFeature(FEATURE_ACTION_BAR);
    } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
        // If no other features and not embedded, only need a title.
        // If the window is floating, we need a dialog layout
        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;
        }
        // System.out.println("Title!");
    } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
        layoutResource = R.layout.screen_simple_overlay_action_mode;
    } else {
        // Embedded, so no decoration is needed.
        layoutResource = R.layout.screen_simple;
        // System.out.println("Simple!");
    }

    mDecor.startChanging();

    //layoutResource  Decorview需要填充自己的內(nèi)容布局有可能是LinearLayout或者FrameLayout,然后將布局addView給自己
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

    //獲取真正的contenview
    ViewGroup contentParent = (ViewGroup) findViewById(ID_ANDROID_CONTENT);
    if (contentParent == null) {
        throw new RuntimeException("Window couldn't find content container view");
    }

    if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
        ProgrsetyessBar progress = getCircularProgressBar(false);
        if (progress != null) {
            progress.setIndeterminate(true);
        }
    }

    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
        registerSwipeCallbacks(contentParent);
    }

    // Remaining setup -- of background and title -- that only applies
    // to top-level windows.
    if (getContainer() == null) {
        final Drawable background;
        if (mBackgroundResource != 0) {
            background = getContext().getDrawable(mBackgroundResource);
        } else {
            background = mBackgroundDrawable;
        }
        mDecor.setWindowBackground(background);

        final Drawable frame;
        if (mFrameResource != 0) {
            frame = getContext().getDrawable(mFrameResource);
        } else {
            frame = null;
        }
        mDecor.setWindowFrame(frame);

        mDecor.setElevation(mElevation);
        mDecor.setClipToOutline(mClipToOutline);

        if (mTitle != null) {
            setTitle(mTitle);
        }

        if (mTitleColor == 0) {
            mTitleColor = mTextColor;
        }
        setTitleColor(mTitleColor);
    }

    mDecor.finishChanging();

    return contentParent;
}

別看代碼多實際上是根據(jù)features選擇填充布局去扣,這就是為什么我們要在setContentCiew之前調(diào)用requestWindowFeature的原因柱衔,先看看DecorView的直接子ViewGroup的布局長什么樣:

<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>

當然根ViewGroup在不僅僅是LinearLayout ,還有FrameLayout和其他的愉棱,這些沒什么好說的唆铐,就這說填充DecorView的布局吧, Decorview需要填充自己的內(nèi)容布局有可能是LinearLayout或者FrameLayout羽氮,然后調(diào)DecorViewon的ResourcesLoaded方法將選擇的布局addView給DecorView自己或链,然后通過findViewById方法找到contentParent,并返回賦值給PhoneWindow的mContentParent档押,接著回到PhoneWindow的setContentView中將我們從Activity的setContentView傳進來的layoutResID澳盐,填充到mContentParent,也就是DecorView的內(nèi)容ViewGroup中令宿,整個流程就完成了布局的填充叼耙。

最后看一下setContentView的時序圖:


activity.setcontentview.png

小結(jié)

  • Window是一個抽象類,提供了各種窗口操作的方法粒没,比如設(shè)置背景標題ContentView等等筛婉;
  • PhoneWindow則是Window的唯一實現(xiàn)類,它里面實現(xiàn)了Window各種各種方法,添加背景主題ContentView等方法爽撒,內(nèi)部通過DecorView來添加頂級視圖
    每一個Activity上面都有一個Window入蛆,可以通過getWindow獲取硕勿;
  • DecorView哨毁,頂級視圖,繼承與FramentLayout源武,setContentView則是添加在它里面的@id/content里
  • setContentView里面創(chuàng)建了DecorView扼褪,根據(jù)Theme,F(xiàn)eature添加了對應(yīng)的布局文件粱栖,當setContentView設(shè)置顯示后會回調(diào)Activity的onContentChanged方法话浇;

最后看看window的結(jié)構(gòu)

view結(jié)構(gòu)圖.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市闹究,隨后出現(xiàn)的幾起案子幔崖,更是在濱河造成了極大的恐慌,老刑警劉巖渣淤,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岖瑰,死亡現(xiàn)場離奇詭異,居然都是意外死亡砂代,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門率挣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刻伊,“玉大人,你說我怎么就攤上這事椒功〈废洌” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵动漾,是天一觀的道長丁屎。 經(jīng)常有香客問我,道長旱眯,這世上最難降的妖魔是什么晨川? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮删豺,結(jié)果婚禮上共虑,老公的妹妹穿的比我還像新娘。我一直安慰自己呀页,他們只是感情好妈拌,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蓬蝶,像睡著了一般尘分。 火紅的嫁衣襯著肌膚如雪猜惋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天培愁,我揣著相機與錄音著摔,去河邊找鬼。 笑死竭钝,一個胖子當著我的面吹牛梨撞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播香罐,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼卧波,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了庇茫?” 一聲冷哼從身側(cè)響起港粱,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎旦签,沒想到半個月后查坪,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡宁炫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年偿曙,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片羔巢。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡望忆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出竿秆,到底是詐尸還是另有隱情启摄,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布幽钢,位于F島的核電站歉备,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏匪燕。R本人自食惡果不足惜蕾羊,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望帽驯。 院中可真熱鬧肚豺,春花似錦、人聲如沸界拦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至截碴,卻和暖如春梳侨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背日丹。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工走哺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哲虾。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓丙躏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親束凑。 傳聞我的和親對象是個殘疾皇子晒旅,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345

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