注:本文解析的源码基于 API 25,部分内容来自于《Android开发艺术探索》。
Header
今天我们来讲讲 Window ,Window 代表着一个窗口。
比如在 Activity 中,我们可以设置自定义的视图 View ,其实 View 并不是直接附着在 Activity 上,而是 View 附着在 Window 上,Activity 又持有一个 Window 对象。可见,Window 是一个重要的角色,主要用来负责管理 View 的。而 Window 和 View 又是通过 ViewRootImpl 来建立联系的,这在之前的《View的工作原理》中介绍过。
所以一个 Window 就对应着一个 View 和一个 ViewRootImpl 。
同理,Dialog 和 Toast 等的视图也都是附着在 Window 上。
除此之外,相信看过《Android开发艺术探索》的同学都知道。Window 有三种类型,分别对应着:
- 应用 Window ,即 Activity 的 Window 。对应的 type 为1~99;
- 子 Window ,比如 Dialog 的 Window ,子 Window 并不能单独存在,需要有父 Window 的支持。对应的 type 为1000~1999;
- 系统 Window ,需要权限声明才可以创建,比如常用的 Toast 和状态栏等都是系统级别的 Window。对应的 type 为2000~2999;
这三种 Window 的区分方法就是依靠 WindowManager.LayoutParams 中的 type 来决定的。type 越大,Window 就越显示在层级顶部。
粗看有这么多知识点,所以我们确实有必要对 Window 好好深入了解一下。在这,我们先详细介绍一下 Window 和 Activity 的那些“纠葛”,然后再深入 Window 的内部机制。
初见Window
Activity
attach(Context context, ActivityThread aThread, …)
Window 第一次出现在 Activity 的视野中,是在 Activity 的 attach
方法中,具体代码如下:
1 | final void attach(Context context, ActivityThread aThread, |
在方法中创建了一个 PhoneWindow 对象,而 PhoneWindow 其实就是 Window 的具体实现类,Window 只是一个接口而已。之后设置了回调,这样当 Window 接收到触摸或者按键等事件后,会回调给 Activity 。
另外还给 Window 对象设置了窗口管理器,也就是我们经常用到的 WindowManager 。
WindowManager 是外界接触 Window 的入口,也就是说,想要对 Window 进行一些操作需要用过 WindowManager 来完成。
与DecorView的那些事
在开头中说到,Window 是用来负责管理 View 的。
现在 Window 已经创建完毕了,那么到底什么时候与 View 发生了交集了呢?
我们需要深入到 onCreate()
中一个熟悉的方法: setContentView(R.layout.activity_main)
。
Activity
setContentView(@LayoutRes int layoutResID)
1 | public void setContentView(@LayoutRes int layoutResID) { |
发现它调用的是 Window 中的同名方法。
接着到 PhoneWindow 中跟进,查看具体实现的逻辑。
PhoneWindow
setContentView(int layoutResID)
1 | @Override |
在 setContentView(int layoutResID)
中,一开始判断了 mContentParent 。mContentParent 其实就是我们设置的 contentView 的父视图。
关于 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.
意思就是说,当我们不需要 titlebar 的时候,mContentParent 其实就和 DecorView 一样了;有 titlebar 的时候,DecorView 的内容就分为了 titlebar 和 mContentParent 。
所以如果 mContentParent 为空,那么可以说明还没有创建过 DecorView 。
我们总结一下,在 setContentView(int layoutResID)
中主要就是这三件事:
- 创建 DecorView 视图对象;
- 将自定义的视图 layout_main.xml 进行解析并添加到 mContentParent 中;
- 去通知 activity 窗口视图已经改变了,进行相关操作;
我们去 installDecor()
中看看究竟怎么创建 DecorView 的。
installDecor()
1 | private void installDecor() { |
在 installDecor()
中,调用了 generateDecor()
方法来创建 DecorView;
之后又调用 generateLayout(mDecor)
来创建 mContentParent 。
generateDecor(int featureId)
1 | protected DecorView generateDecor(int featureId) { |
generateDecor(int featureId)
方法比较简单,之前初始化了一下 context ,然后直接 new 了一个 DecorView 完事!
generateLayout(DecorView decor)
1 | protected ViewGroup generateLayout(DecorView decor) { |
这个方法中大致的逻辑就是,根据主题的设置情况来选择 DecorView 子 View 的 layoutResource 。在这,我们就看看最常用的一种布局 R.layout.screen_title (位于 /frameworks/base/core/res/res/layout/screen_title.xml ):
1 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
我们可以看到,DecorView 的子 View 其实是一个 LinearLayout ,而 LinearLayout 中有分为 titlebar 和 id 为 android:id/content 的 FrameLayout(其实就是 mContentParent)。
之后将这个视图创建出来并添加到 DecorView 中。
具体的代码可以深入 DecorView 的 onResourcesLoaded(LayoutInflater inflater, int layoutResource)
中去看:
1 | void onResourcesLoaded(LayoutInflater inflater, int layoutResource) { |
看到这,我们可以画一张图出来了,把 PhoneWindow 、DecorView 和 mContentParent 都理清楚:
然后进行标题设置之类的工作。最后得到并返回 mContentParent 。
到了这里,基本上把 Window 、DecorView 和 Activity 三者之间的关系整理清楚了,但是事情并没有结束。这时候的 DecorView 并没有真正添加到 Window 上去,只是创建出对象了并解析了视图而已。DecorView 还没有被 WindowManager 识别,Window 也还无法接受外界的输入信息。
那么,到底 DecorView 是什么时候附着到 Window 上去的?
这个答案需要我们到 ActivityThread 的 handleResumeActivity()
中找找了。回调 Activity 的 onResume()
生命周期后,又调用了 Activity 的 makeVisible()
方法。
Activity
makeVisible()
1 | void makeVisible() { |
走完这步,DecorView 才完成添加和显示出来,Activity 的视图才能被用户看到。
整个 Window 创建的流程也结束了。
Footer
Window 和 Decor 的“爱恨情仇”到这里就告一段落了,但是 Window 的内部机制我们还可以好好叙一叙。
注意到上面 WindowManager 的 addView
方法了吧?
Window 是怎么添加上去的,究竟在这里面发生了什么事呢?
只能留到下一篇再详细讲讲了。
bye bye !