跟你们讲解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 来源:简书 简书著做权归做者全部,任何形式的转载都请联系做者得到受权并注明出处。