Android窗口機(jī)制(二)Window,PhoneWindow,DecorView裹匙,setContentView源碼理解

Android窗口機(jī)制系列

Android窗口機(jī)制(一)初識(shí)Android的窗口結(jié)構(gòu)
Android窗口機(jī)制(二)Window瑞凑,PhoneWindow,DecorView概页,setContentView源碼理解
Android窗口機(jī)制(三)Window和WindowManager的創(chuàng)建與Activity
Android窗口機(jī)制(四)ViewRootImpl與View和WindowManager
Android窗口機(jī)制(五)最終章:WindowManager.LayoutParams和Token以及其他窗口Dialog籽御,Toast

前篇文章中出現(xiàn)了PhoneWindow,DecorView這些類绰沥,如果是第一次見過(guò)的話篱蝇,肯定會(huì)覺(jué)得陌生。這篇文章主要跟大家講解Window徽曲,PhoneWindow,DecorView他們的理解以及他們之間的聯(lián)系

Window

我們來(lái)看下源碼里面的說(shuō)明

/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window {
    ...
    @Nullable
    public View findViewById(@IdRes int id) {
        return getDecorView().findViewById(id);
    }

/**
 * Convenience for * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}
 * to set the screen content from a layout resource.  The resource will be * inflated, adding all top-level views to the screen. * * @param layoutResID Resource ID to be inflated.
 * @see #setContentView(View, android.view.ViewGroup.LayoutParams)
 */
     public abstract void setContentView(@LayoutRes int layoutResID);
     ...
}

一個(gè)頂級(jí)窗口查看和行為的一個(gè)抽象基類麸塞。這個(gè)類的實(shí)例作為一個(gè)頂級(jí)View添加到Window Manager秃臣。它提供了一套標(biāo)準(zhǔn)的UI方法,比如添加背景哪工,標(biāo)題等等奥此。當(dāng)你需要用到Window的時(shí)候,你應(yīng)該使用它的唯一實(shí)現(xiàn)類PhoneWindow雁比≈苫ⅲ可以看到,Window是一個(gè)抽象基類偎捎,它提供了一系列窗口的方法蠢终,比如設(shè)置背景,標(biāo)題等等茴她,而它的唯一實(shí)現(xiàn)類則是PhoneWindow

PhoneWindow

Window的唯一實(shí)現(xiàn)類

public class PhoneWindow extends Window implements MenuBuilder.Callback {

    private final static String TAG = "PhoneWindow";

    ...

    // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;

    // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    private ViewGroup mContentParent;

    private ViewGroup mContentRoot;
    ...
}

可以看到寻拂,在PhoneWindow里面,出現(xiàn)了成員變量DecorView的而這里丈牢,DecorView則是PhoneWindow里面的一個(gè)內(nèi)部類祭钉,它是繼承與FrameLayout

 private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {

        /* package */int mDefaultOpacity = PixelFormat.OPAQUE;

        /** The feature ID of the panel, or -1 if this is the application's DecorView */
        private final int mFeatureId;

        private final Rect mDrawingBounds = new Rect();

        private final Rect mBackgroundPadding = new Rect();

        private final Rect mFramePadding = new Rect();

        private final Rect mFrameOffsets = new Rect();
        ....
 }

既然是FrameLayout,也就可以加載布局文件己沛,也就是說(shuō)慌核,我們那些標(biāo)題欄,內(nèi)容欄申尼,頂級(jí)上看是加載在DecorView上的垮卓。而DecorView則是由PhoneWindow負(fù)責(zé)添加

兩者關(guān)系以及setContentView源碼流程

接下我們就從一個(gè)常見的方法中去認(rèn)知他們之間的關(guān)系,那就是activity里面的setContentView晶姊,就是我們平常把布局內(nèi)容顯示到界面上的一個(gè)方法扒接。點(diǎn)擊activity.setContentView時(shí)

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

里面方法調(diào)用了getWindow().setContentView,而這個(gè)getWindow方法獲取的就是Activity上的Window

/**
     * Retrieve the current {@link android.view.Window} for the activity.
     * This can be used to directly access parts of the Window API that
     * are not available through Activity/Screen.
     *
     * @return Window The current window, or null if the activity is not
     *         visual.
     */
    public Window getWindow() {
        return mWindow;
    }

可以看到如果當(dāng)前mWindow為null的話,則表示當(dāng)前Activity不在窗口上钾怔,這里的mWindow.setContentView碱呼,實(shí)際上調(diào)用到的是它的實(shí)現(xiàn)類方法phoneWindow.setContentView

 @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            //創(chuàng)建DecorView,并添加到mContentParent上
            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 {
            //將要加載的資源添加到mContentParent上
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            //回調(diào)通知表示完成界面加載
            cb.onContentChanged();
        }
    }

如果當(dāng)前內(nèi)容還未放置到窗口宗侦,此時(shí)mContentParent==null,也就是第一次調(diào)用的時(shí)候愚臀,調(diào)用那個(gè)installDecor方法。FEATURE_CONTENT_TRANSITIONS矾利,則是標(biāo)記當(dāng)前內(nèi)容加載有沒(méi)有使用過(guò)度動(dòng)畫姑裂,也就是轉(zhuǎn)場(chǎng)動(dòng)畫。如果內(nèi)容已經(jīng)加載過(guò)男旗,并且不需要?jiǎng)赢嫴案瑒t會(huì)調(diào)用removeAllViews。添加完Content后如有設(shè)置了FEATURE_CONTENT_TRANSITIONS則添加Scene來(lái)過(guò)度啟動(dòng)察皇。否則mLayoutInflater.inflate(layoutResID, mContentParent);將我們的資源文件通過(guò)LayoutInflater對(duì)象轉(zhuǎn)換為View樹茴厉,并且添加至mContentParent視圖中。既然是第一次啟動(dòng)則會(huì)調(diào)用到installDecor什荣,從字面上看可以知道該方法用來(lái)添加DecorView矾缓,看下里面說(shuō)明

private void installDecor() {
        if (mDecor == null) {
        //調(diào)用該方法創(chuàng)建new一個(gè)DecorView
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        //一開始DecorView未加載到mContentParent,所以此時(shí)mContentParent=null
        if (mContentParent == null) {
        //該方法將mDecorView添加到Window上綁定布局
            mContentParent = generateLayout(mDecor);

            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();

            final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                    R.id.decor_content_parent);
                
                ...//添加其他資源
                ...//設(shè)置轉(zhuǎn)場(chǎng)動(dòng)畫
        }
    }

可以看到該方法稻爬,先通過(guò)吊桶generateDecor創(chuàng)建DecorView

protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }

創(chuàng)建完后再通過(guò)調(diào)用generateLayout將setContentView的內(nèi)容賦值到mContentParent嗜闻,這個(gè)方法有點(diǎn)長(zhǎng),我們看下

 protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
        //根據(jù)當(dāng)前設(shè)置的主題來(lái)加載默認(rèn)布局
        TypedArray a = getWindowStyle();
        //如果你在theme中設(shè)置了window_windowNoTitle桅锄,則這里會(huì)調(diào)用到琉雳,其他方法同理,
        //這里是根據(jù)你在theme中的設(shè)置去設(shè)置的
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestFeature(FEATURE_ACTION_BAR);
        }
        //是否有設(shè)置全屏
        if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
            setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
        }
        
        ...//省略其他加載資源
        
        // 添加布局到DecorView竞滓,前面說(shuō)到咐吼,DecorView是繼承與FrameLayout,它本身也是一個(gè)ViewGroup商佑,而我們前面創(chuàng)建它的時(shí)候锯茄,只是調(diào)用了new DecorView,此時(shí)里面并無(wú)什么東西茶没。而下面的步奏則是根據(jù)用戶設(shè)置的Feature來(lái)創(chuàng)建相應(yīng)的默認(rèn)布局主題肌幽。舉個(gè)例子,如果我在setContentView之前調(diào)用了requestWindowFeature(Window.FEATURE_NO_TITLE)抓半,這里則會(huì)通過(guò)getLocalFeatures來(lái)獲取你設(shè)置的feature喂急,進(jìn)而選擇加載對(duì)應(yīng)的布局,此時(shí)則是加載沒(méi)有標(biāo)題欄的主題笛求,對(duì)應(yīng)的就是R.layout.screen_simple

        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
        } ... //省略其他判斷方法
        } else {
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }

        mDecor.startChanging();
        //選擇對(duì)應(yīng)布局創(chuàng)建添加到DecorView中
        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        ...
        return contentParent;
    }

首先generateLayout會(huì)根據(jù)當(dāng)前用戶設(shè)置的主題去設(shè)置對(duì)應(yīng)的Feature廊移,接著糕簿,根據(jù)對(duì)應(yīng)的Feature來(lái)選擇加載對(duì)應(yīng)的布局文件,(Window.FEATURE_NO_TITLE)接下來(lái)通過(guò)getLocalFeatures來(lái)獲取你設(shè)置的feature狡孔,進(jìn)而選擇加載對(duì)應(yīng)的布局懂诗,這也就是為什么我們要在setContentView之前調(diào)用requesetFeature的原因。此時(shí)則是加載沒(méi)有標(biāo)題欄的主題苗膝,對(duì)應(yīng)的就是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>

可以看到是LinearLayout里面包含了兩個(gè),因?yàn)樵O(shè)置可NoTitle辱揭,所以上面只有一個(gè)ViewStub离唐,否則還有一個(gè)FrameLayout。也證明前面第一篇中說(shuō)的问窃,“DecorView只有一個(gè)子元素為L(zhǎng)inearLayout亥鬓。代表整個(gè)Window界面,包含通知欄泡躯,標(biāo)題欄贮竟,內(nèi)容顯示欄三塊區(qū)域〗咸辏”注意FrameLayout里面的id,@android:id/content 技健,我們setContentView的內(nèi)容就是添加到這個(gè)FrameLayout中写穴。

generateLayout的返回是contentParent,而它的獲取則是ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);`

public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

正是id為content的FrameLayout雌贱。之后我們setContentView則是添加在mContentParent上面了啊送。回到前面剛開始的方法

 @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            //創(chuàng)建DecorView欣孤,并添加到mContentParent上
            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 {
            //將要加載的資源添加到mContentParent上
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            //回調(diào)通知表示完成界面改變
            cb.onContentChanged();
        }
    }

此時(shí)已經(jīng)創(chuàng)建完DecorView并且獲取到mContentParent馋没,接著就是將你setContentView的內(nèi)容添加到mContentParent中,也就是

 mLayoutInflater.inflate(layoutResID, mContentParent);
 或者
 mContentParent.addView(view, params);

最后調(diào)用Callback來(lái)通知界面發(fā)生改變降传。Callback是Window里面的一個(gè)接口篷朵,里面聲明了當(dāng)界面更改觸摸時(shí)調(diào)用的各種方法。這里的話婆排,我們看下onContentChanged声旺,在PhoneWindow里面并沒(méi)有看到onContentChanged的實(shí)現(xiàn)類,而我們又知道Activity本身又是加載在Window上的段只,我們看下Activity

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback { ... }

可以看到Activity里面實(shí)現(xiàn)了Window.Callback接口而里面onContentChanged則是空的腮猖,也就是我們可以通過(guò)重寫該方法來(lái)監(jiān)聽布局內(nèi)容的改變了

 public void onContentChanged() {
    }

小結(jié)

  • Window是一個(gè)抽象類,提供了各種窗口操作的方法赞枕,比如設(shè)置背景標(biāo)題ContentView等等
  • PhoneWindow則是Window的唯一實(shí)現(xiàn)類澈缺,它里面實(shí)現(xiàn)了各種添加背景主題ContentView的方法坪创,內(nèi)部通過(guò)DecorView來(lái)添加頂級(jí)視圖
  • 每一個(gè)Activity上面都有一個(gè)Window,可以通過(guò)getWindow獲取
  • DecorView姐赡,頂級(jí)視圖莱预,繼承與FramentLayout,setContentView則是添加在它里面的@id/content里
  • setContentView里面創(chuàng)建了DecorView雏吭,根據(jù)Theme锁施,F(xiàn)eature添加了對(duì)應(yīng)的布局文件
  • 當(dāng)setContentView設(shè)置顯示后會(huì)回調(diào)Activity的onContentChanged方法

回顧

再看一下前一篇文章的結(jié)構(gòu)圖,是不是就更好理解了呢杖们。

Paste_Image.png

下一篇文章
Android窗口機(jī)制(三)Window和WindowManager的創(chuàng)建與Activity
http://www.reibang.com/p/6afb0c17df43

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末悉抵,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子摘完,更是在濱河造成了極大的恐慌姥饰,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件孝治,死亡現(xiàn)場(chǎng)離奇詭異列粪,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)谈飒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門岂座,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人杭措,你說(shuō)我怎么就攤上這事费什。” “怎么了手素?”我有些...
    開封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵鸳址,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我泉懦,道長(zhǎng)稿黍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任崩哩,我火速辦了婚禮巡球,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘琢锋。我一直安慰自己辕漂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開白布吴超。 她就那樣靜靜地躺著钉嘹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鲸阻。 梳的紋絲不亂的頭發(fā)上跋涣,一...
    開封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天缨睡,我揣著相機(jī)與錄音,去河邊找鬼陈辱。 笑死奖年,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沛贪。 我是一名探鬼主播陋守,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼利赋!你這毒婦竟也來(lái)了水评?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤媚送,失蹤者是張志新(化名)和其女友劉穎中燥,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體塘偎,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡疗涉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吟秩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咱扣。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖涵防,靈堂內(nèi)的尸體忽然破棺而出偏窝,到底是詐尸還是另有隱情,我是刑警寧澤武学,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站伦意,受9級(jí)特大地震影響火窒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜驮肉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一熏矿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧离钝,春花似錦票编、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至浪读,卻和暖如春昔榴,著一層夾襖步出監(jiān)牢的瞬間辛藻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工互订, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吱肌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓仰禽,卻偏偏與公主長(zhǎng)得像氮墨,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吐葵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

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

  • 本篇文章所分析源碼基于android 6.0.1 一.從setContentView()說(shuō)起 從我們寫第一個(gè)And...
    Geeks_Liu閱讀 8,444評(píng)論 10 32
  • Android程序員都知道Activity調(diào)用setContentView的方法是將xml布局文件加載到Activ...
    AxeChen閱讀 6,049評(píng)論 4 14
  • Activity調(diào)用setContentView將布局添加到窗口的流程如圖: 在深入了解setContentVie...
    CP9閱讀 889評(píng)論 0 1
  • 本文主要內(nèi)容是講解一個(gè)視圖View或者一個(gè)ViewGroup對(duì)象是如何添加至應(yīng)用程序窗口中的规揪。下文中提到的窗口可泛...
    于闐閱讀 874評(píng)論 0 2
  • 科普貼一張,包治選鉆困難癥折联! 一提到鉆石粒褒,尺寸的問(wèn)題似乎大家第一個(gè)想到的。 關(guān)于大谐狭:如果只是為了blingbli...
    Summerr醬閱讀 1,671評(píng)論 0 0