Android的窗口结构
窗口结构示意图:
对上图做一个简单的说明:
1、Window表示一个窗口的概念,Android中所有的视图都是通过Window来呈现的,它们的视图实际上都是附加在Window上的,因此Window实际上是View的直接管理者。Activty的设置视图的方法setContentView在底层也是通过Window来完成的。
2、Window是一个抽象类,它的具体实现是PhoneWindow,每个Activity都会创建以恶搞PhoneWindow对象,是Activity和整个View系统交互的接口。
3、DecorView继承FrameLayout,所以是一个ViewGroup,在整个Window最顶层,将要显示的具体内容。最终由PhoneWindowDecorView的内容呈现在屏幕上,也就是说当前Activity在屏幕上显示的东西都在DecorView里。
DecorView
DecorView是一个ViewGroup,是一个View的容器,本身并不会向用户呈现任何东西,DecorView是ViewTree中最顶层的元素,从屏幕的角度来看DecorView是最底层的View,其他的View都呈现在DecorView上面。所以DecorView是当前Activity中所有View的根View。
系统为我们提供了几种默认的布局文件填充到DecorView中,作为其子元素,这些布局文件位于 frameworks/base/core/res/layout/ ,最常用的窗口布局文件有:
Screen-title.xml
<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" />
<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>
将Screen-title.xml填充到DecorView后我们会得到如下的ViewTree:
可以看到在ViewTree中,DecorView是最顶层的元素,它有两个子元素:
(1)LinearLayout:就是将Screen-title.xml填充到DecorView,作为其子元素,LinearLayout又包含了三个子元素一个ViewStub和两个帧布局(FrameLayout),可以看出和Screen-title.xml中的元素一致。其中一个FrameLayout中含有一个id为title的TextView,其实就是标题;另外一个FrameLayout的id为content,我们设置的Activity的布局文件就是被填充到这里的
(2)View:屏幕最上端状态栏的背景
Screen-simple.xml
<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" />
<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>
在开发中,有时候需要将标题栏去掉,可以在AndroidManifest中进行设置:
android:theme="@android:style/Theme.NoTitleBar"`
这时填充到DecorView中的是Screen-simple.xml,得到如下的ViewTree:
DecorView依旧包含两个子元素,我们可以看到LinearLayout中只有一个帧布局id为content,用来填充Activity的布局文件,包含TextView(标题)的帧布局没有了。
那么问题来了,Acitvity的布局文件是如何加载到id为content的帧布局中的呢?
视图加载过程
在开发中通常是定义好XML布局文件后,在Activity的onCreate方法中使用setContentView方法加载布局
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
setContentView方法如下:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
其中getWindow() 方法获取了当前Activity中的Window的对象
public Window getWindow() {
return mWindow;
}
回到setContentView方法,获取Window对象后,调用了Window的setContentView方法,Window是抽象类,它的唯一实现类是PhoneWindow,所以在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) {
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 {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
mContentParent是PhoneWindow的一个成员变量:
// 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;
其实它就是ViewTree中,DecorView的LineLayout子元素中id为content的FrameLayout,回到PhoneWindow的setContentView,如果mContentParent为空则执行installDecor()方法初始化DecorView的布局,加一个基本布局上去,如前面说的Screen-title.xml。
installDecor()方法源码如下:
源码太长,只看需要的部分
private void installDecor() {
...省略
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...省略
}
...省略
}
如果mContentParent为空调用generateLayout方法为mContentParent赋值。
the fucking source code is too long
generateLayout源码:
protected ViewGroup generateLayout(DecorView decor) {
...省略...
int layoutResource;
int features = getLocalFeatures();
//这些布局都是系统提供的,在SDK的目录:/platforms/android-25/data/res/layout
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
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();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);//将布局加载到DecorView中
//ID_ANDROID_CONTENT对应的是@android:id/content的FrameLayout 即DecorView子元素LinearLayout中的id是content的帧布局
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...省略...
return contentParent;
}
概括一下就是,这个方法就是将系统提供的某一种布局加载到DecorView中,然后找到一个id为content的FrameLayout,并将这个FrameLayout返回。
回到installDecor方法,将generateLayout的返回值赋给mContentParent至此我们得到了Activity的布局文件所加载的位置。
再会带PhoneWindow的setContentView方法,将Activity的布局加载到id为content的FrameLayout中。
流程图如下:
到目前为止,通过setContentView方法,创建了DecorView和加载了Activity的布局,但是这是,View还是不可见的,因为仅仅是加载了布局,并没有对View进行任何的测量、布局、绘制。
绘制流程:View的绘制流程