Android窗口机制(一)——Window,PhoneWindow,DecorView理解

Window

public abstract class Window {
	public <T extends View> T findViewById(@IdRes int id) {
		return getDecorView().findViewById(id);
   	}

	public abstract void setContentView(@LayoutRes int layoutResID);

	public abstract void onConfigurationChanged(Configuration newConfig);
}

一个顶级窗口查看和行为的一个抽象基类。这个类的实例作为一个顶级View添加到Window Manager。它提供了一套标准的UI方法,比如添加背景,标题等等。当你需要用到Window的时候,你应该使用它的唯一实现类PhoneWindow。可以看到,Window是一个抽象基类,它提供了一系列窗口的方法,比如设置背景,标题等等,而它的唯一实现类则是PhoneWindow。

PhoneWindow

public class PhoneWindow extends Window implements MenuBuilder.Callback {

	//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.
    ViewGroup mContentParent;
}

可以看到,在PhoneWindow里面,出现了成员变量DecorView,而DecorView是继承与FrameLayout。

DecorView

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
	private PhoneWindow mWindow;

	void setWindow(PhoneWindow phoneWindow) {
        mWindow = phoneWindow;
        Context context = getContext();
        if (context instanceof DecorContext) {
            DecorContext decorContext = (DecorContext) context;
            decorContext.setPhoneWindow(mWindow);
        }
    }
}

既然是FrameLayout,就可以加载布局文件,也就是说,我们那些标题栏,内容栏,顶级上看是加载在DecorView上的,而DecorView则是由PhoneWindow负责添加。

从setContentView源码流程分析

从Activity里面的setContentView,就是我们平常把布局内容显示到界面上的一个方法。

public void setContentView(View view) {
	getWindow().setContentView(view);
	initWindowDecorActionBar();
}

//mWindow = new PhoneWindow(),后续将介绍
public Window getWindow() {
	return mWindow;
}

这里的mWindow.setContentView(),实际上调用到的是它的实现类方法PhoneWindow.setContentView()。

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

如果当前内容还未放置到窗口,此时mContentParent==null,也就是第一次调用的时候,调用那个installDecor方法,而后将我们的资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent视图中。

private void installDecor() {
	mForceDecorInstall = false;
        if (mDecor == null) {
        	//调用该方法创建new一个DecorView
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
         //一开始DecorView未加载到mContentParent,所以此时mContentParent=null
        if (mContentParent == null) {
       	    //该方法将mDecorView添加到Window上绑定布局
            mContentParent = generateLayout(mDecor);
}
protected DecorView generateDecor(int featureId) {
	return new DecorView(context, featureId, this, getAttributes());
}

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;
        //@android:id/content添加到contentParent中
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        ...
        return contentParent;
    }

首先generateLayout会根据当前用户设置的主题去设置对应的Feature,接着,根据对应的Feature来选择加载对应的布局文件,(Window.FEATURE_NO_TITLE)接下来通过getLocalFeatures来获取你设置的feature,进而选择加载对应的布局,这也就是为什么我们要在setContentView之前调用requesetFeature的原因。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:fitsSystemWindows="true">
    
    <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:layout_width="match_parent" 
        android:layout_height="?android:attr/windowTitleSize"
        style="?android:attr/windowTitleBackgroundStyle">
        <TextView android:id="@android:id/title" 
            style="?android:attr/windowTitleStyle"
            android:background="@null"
            android:fadingEdge="horizontal"
            android:gravity="center_vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    FrameLayout>
    <FrameLayout android:id="@android:id/content"
        android:layout_width="match_parent" 
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foregroundGravity="fill_horizontal|top"
        android:foreground="?android:attr/windowContentOverlay" />
LinearLayout>

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

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

最后调用Callback来通知界面发生改变。Callback是Window里面的一个接口,里面声明了当界面更改触摸时调用的各种方法。

public interface Window.Callback {
	public boolean dispatchKeyEvent(KeyEvent event);
	public boolean dispatchKeyShortcutEvent(KeyEvent event);
	public boolean dispatchTouchEvent(MotionEvent event);
	public boolean onMenuOpened(int featureId, Menu menu);
	public void onContentChanged();
	public void onAttachedToWindow();
	public void onDetachedFromWindow();
    ...
}
public void setCallback(Callback callback) {
	mCallback = callback;
}

public final Callback getCallback() {
	return mCallback;
}

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

可以看到Activity里面实现了Window.Callback接口而里面onContentChanged则是空的,也就是我们可以通过重写该方法来监听布局内容的改变了。

总结

  • Window是一个抽象类,PhoneWindow则是Window的唯一实现类,提供了各种窗口操作的方法,比如设置背景标题ContentView等等;
  • 每一个Activity上面都有一个Window,可以通过getWindow获取;
  • DecorView,顶级视图,继承与FramentLayout,setContentView则是添加在它里面的@id/content里;
  • setContentView里面创建了DecorView,根据Theme,Feature添加了对应的布局文件,当setContentView设置显示后会回调Activity的onContentChanged方法;

Android窗口机制(一)——Window,PhoneWindow,DecorView理解_第1张图片

你可能感兴趣的:(Android,View专项)