跟你們講解Window,PhoneWindow,DecorView他們的理解以及他們之間的聯繫android
咱們來看下源碼裏面的說明app
/** * 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); ... }
一個頂級窗口查看和行爲的一個抽象基類。這個類的實例做爲一個頂級View添加到Window Manager。它提供了一套標準的UI方法,好比添加背景,標題等等。當你須要用到Window的時候,你應該使用它的惟一實現類PhoneWindow。能夠看到,Window是一個抽象基類,它提供了一系列窗口的方法,好比設置背景,標題等等,而它的惟一實現類則是PhoneWindowide
Window的惟一實現類佈局
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裏面,出現了成員變量DecorView的而這裏,DecorView則是PhoneWindow裏面的一個內部類,它是繼承與FrameLayoutpost
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,也就能夠加載佈局文件,也就是說,咱們那些標題欄,內容欄,頂級上看是加載在DecorView上的。而DecorView則是由PhoneWindow負責添加動畫
接下咱們就從一個常見的方法中去認知他們之間的關係,那就是activity裏面的setContentView,就是咱們日常把佈局內容顯示到界面上的一個方法。點擊activity.setContentView時ui
public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }
裏面方法調用了getWindow().setContentView,而這個getWindow方法獲取的就是Activity上的Windowthis
/** * 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; }
能夠看到若是當前mWindow爲null的話,則表示當前Activity不在窗口上,這裏的mWindow.setContentView,實際上調用到的是它的實現類方法phoneWindow.setContentViewspa
@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) { //建立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()) { //回調通知表示完成界面加載 cb.onContentChanged(); } }
若是當前內容還未放置到窗口,此時mContentParent==null,也就是第一次調用的時候,調用那個installDecor方法。FEATURE_CONTENT_TRANSITIONS,則是標記當前內容加載有沒有使用過分動畫,也就是轉場動畫。若是內容已經加載過,而且不須要動畫,則會調用removeAllViews。添加完Content後若有設置了FEATURE_CONTENT_TRANSITIONS則添加Scene來過分啓動。不然mLayoutInflater.inflate(layoutResID, mContentParent);
將咱們的資源文件經過LayoutInflater對象轉換爲View樹,而且添加至mContentParent視圖中。既然是第一次啓動則會調用到installDecor,從字面上看能夠知道該方法用來添加DecorView,看下里面說明.net
private void installDecor() { if (mDecor == null) { //調用該方法建立new一個DecorView mDecor = generateDecor(); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } } //一開始DecorView未加載到mContentParent,因此此時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); ...//添加其餘資源 ...//設置轉場動畫 } }
能夠看到該方法,先經過吊桶generateDecor建立DecorView
protected DecorView generateDecor() { return new DecorView(getContext(), -1); }
建立完後再經過調用generateLayout將setContentView的內容賦值到mContentParent,這個方法有點長,咱們看下
protected ViewGroup generateLayout(DecorView decor) { // Apply data from current theme. //根據當前設置的主題來加載默認佈局 TypedArray a = getWindowStyle(); //若是你在theme中設置了window_windowNoTitle,則這裏會調用到,其餘方法同理, //這裏是根據你在theme中的設置去設置的 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); } //是否有設置全屏 if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) { setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags())); } ...//省略其餘加載資源 // 添加布局到DecorView,前面說到,DecorView是繼承與FrameLayout,它自己也是一個ViewGroup,而咱們前面建立它的時候,只是調用了new DecorView,此時裏面並沒有什麼東西。而下面的步奏則是根據用戶設置的Feature來建立相應的默認佈局主題。舉個例子,若是我在setContentView以前調用了requestWindowFeature(Window.FEATURE_NO_TITLE),這裏則會經過getLocalFeatures來獲取你設置的feature,進而選擇加載對應的佈局,此時則是加載沒有標題欄的主題,對應的就是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(); //選擇對應佈局建立添加到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會根據當前用戶設置的主題去設置對應的Feature,接着,根據對應的Feature來選擇加載對應的佈局文件,(Window.FEATURE_NO_TITLE)接下來經過getLocalFeatures來獲取你設置的feature,進而選擇加載對應的佈局,這也就是爲何咱們要在setContentView以前調用requesetFeature的緣由。此時則是加載沒有標題欄的主題,對應的就是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裏面包含了兩個,由於設置可NoTitle,因此上面只有一個ViewStub,不然還有一個FrameLayout。也證實前面第一篇中說的,「DecorView只有一個子元素爲LinearLayout。表明整個Window界面,包含通知欄,標題欄,內容顯示欄三塊區域。」注意FrameLayout裏面的id,@android:id/content ,咱們setContentView的內容就是添加到這個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) { //建立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()) { //回調通知表示完成界面改變 cb.onContentChanged(); } }
此時已經建立完DecorView而且獲取到mContentParent,接着就是將你setContentView的內容添加到mContentParent中,也就是
mLayoutInflater.inflate(layoutResID, mContentParent); 或者 mContentParent.addView(view, params);
最後調用Callback來通知界面發生改變。Callback是Window裏面的一個接口,裏面聲明瞭當界面更改觸摸時調用的各類方法。這裏的話,咱們看下onContentChanged,在PhoneWindow裏面並無看到onContentChanged的實現類,而咱們又知道Activity自己又是加載在Window上的,咱們看下Activity
public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, ComponentCallbacks2, Window.OnWindowDismissedCallback { ... }
能夠看到Activity裏面實現了Window.Callback接口而裏面onContentChanged則是空的,也就是咱們能夠經過重寫該方法來監聽佈局內容的改變了
public void onContentChanged() { }
再看一下前一篇文章的結構圖,是否是就更好理解了呢。
Paste_Image.png
下一篇文章
Android窗口機制(三)Window和WindowManager的建立與Activity
http://www.jianshu.com/p/6afb0c17df43
做者:Hohohong 連接:https://www.jianshu.com/p/e42b638944ae 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。