前言
其实提起Window,作为Android开发都能提上几嘴,比如Window是界面的载体,我们的Activity最终就是要通过WindowManagerService把界面加载到Window展示出来;Window有一个唯一的实现类是PhoneWindow等等等等。但话说回来,如果问自己真的理解Window的整个机制吗,那我肯定支支吾吾,不敢作声。
也源于上一篇View自定义的文章,既然子view都是由父view来布局的,那么最顶层的view呢?当我们打开APP,Activity的画面是怎么展现到我们面前,这中间的过程经历了什么,又是怎么调度的呢?神秘的DecorView和ViewRootImpl在其中又扮演者什么角色呢?
那么就带着这些问题和好奇,我们来好好梳理一下Window的整个机制。
注:以下源码基于Android SDK API 31
相关概念
Window的知识点比较多,先说一下相关的概念,方便后面理解。
首先Window表示的是窗口的概念,但这跟PC端有点差别,比如PC端说的多窗口其实就是指打开多个应用(我是这么理解,不知道对不对)。而Android的窗口并不单单是指代应用层面,比如你打开一个APP,当前确实就是一个Window,但如果在当前页面有个Dialog,那其实这个Dialog它自己也是个Window,只不过是依赖在当前这个界面的Window上。
Window和View
在Android中Window是一个抽象的概念,Android所有的视图都是通过Window来呈现,不论是Activity、Dialog还是Toast,对应的视图其实都是附加在Window上的,也就是说Window是View的载体。
为什么说Window是一个抽象的概念呢,比如一个Activity的View树(整体布局),展现出来我们就可以说是一个Window。但是我们只能看到View(体现出来),却无法看到实际的Window(展现形式),因为它本身并不存在。有个比喻很恰当,就是类比班集体的话,学生就是一个个View,一整个班的学生就是班集体,但班集体是抽象出来的概念,我们看不见摸不着。当然了,还是那句话,概念是帮助我们理解事物的,我们不必去纠结概念本身,能够(通过源码)理解就行。
View树
View树其实就是一系列View(ViewGroup)的组合。在Activity中,对应的整体布局就是一个View树,它有自己最顶层的View(ViewGroup),也就最底层(或者说嵌套最内层)的View(ViewGroup);而对于一个自定义布局的Dialog来说,它也拥有自己独立的整体布局,它并不属于Activity的View树,所以这就是两个不同的View树,对应两个不同的Window。
设计初衷
为什么Android要把一个View树看成是一个Window呢?其实这就是好比班集体的概念一样,每当组织活动的时候,把通知下发给各个班集体就行,然后每个班集体再自己内部消化,这样管理起来就比较容易。
那既然不同的Window管理着自己对应的View树,那不同的Window同时出现怎么办?因为手机的屏幕就那么大,总不能再去分屏操作吧(当然这也行),那么就引出了Window的显示层级(或者说View的显示层级)。比如我们在Activity上
弹出Dialog,然后再弹出一个Toast,那么该如何保证Dialog是显示在Activity之上,而Toast又是显示在Dialog之上的呢?此时如果我们刷新Activity的UI,那Dialog和Toast的UI也需要刷新吗?而当把这些View树分开,通过各自的Window来管理之后,事情就变得简单了,可以比较轻松的去实现不同View树的分层级显示了。
而这样做的带来的另一个好处就是便于点击事件的分发,还是上面的例子,这时我们给屏幕一个点击事件,那么是该由Activity来处理还是Dialog来处理呢?这也由Window来统一分发和实现就行。
总体来说,Window的出现就是为了解耦,虽然实际还是由View来显示,但我们通过把View树当成一个集体,让视图的显示和事件传递都变得非常方便。
Window和WindowManager
通过源码我们可以发现,Window实际上是一个抽象类,而它的一个唯一实现类就是PhoneWindow。
Window.java
/**
* 顶级窗口外观和行为策略的抽象基类。此类的实例应用作添加到窗口管理器的顶级视图。它提供标准的 UI 策略,例如背景、标
* 题区域、默认键处理等。
* 此抽象类的唯一现有实现是 android.view.PhoneWindow,当你需要 Window 时,应实例化该实现。
*/
public abstract class Window {
...
}
PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
private final static String TAG = "PhoneWindow";
...
}
创建Window的话很简单,只需要WindowManager即可完成。
val textView = TextView(this).apply {
text = "通过WindowManager添加一个View(Window)"
}
val layoutParams = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
0,
0,
PixelFormat.TRANSPARENT
)
layoutParams.gravity = Gravity.CENTER
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
windowManager.addView(textView, layoutParams)
上面的代码逻辑就可以在页面上创建出一个Window,而该Window的表现形式(我们直观看到的)就是一个TextView,Window本身我们是看不见摸不着的。可以看到,我们是通过WindowManager类的addView方法来实现的,看下WindowManager:
public interface WindowManager extends ViewManager {
....
}
WindowManager其实是一个接口,继承自ViewManager:
/**
* 用于向 Activity 添加和删除子视图的接口。若要获取此类的实例,请调用 Context.getSystemService()。
*/
public interface ViewManager {
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
ViewManager也是一个接口,很明显我们刚刚使用的addView方法,就是这里继承的。ViewManager接口有三个方法,其实可以看出就是用来规范视图相关操作的。注意看注释,用于向 Activity 添加和删除子视图的接口。若要获取此类的实例,请调用 Context.getSystemService()。我们再回头看下刚刚在界面上创建TextView时的操作,我们的WindowManager实例是哪里来的,是不是就是通过Context的getSystemService()方法拿到的。这里先不展开,大概清楚就行,后面再具体说。
所以我们知道,通过WindowManager的这三个方法,就可以来操作Window了。关于这里具体的添加逻辑和显示逻辑,我们等下说,先来看看Window的属性有哪些。
type属性
type表示的是Window的类型,一共有三种:应用程序Window、子程序Window和系统Window。而类型就决定了Window的显示层序,假如一个Window的类型是系统Window,那么它一定会显示在其他应用程序类型的Window上面。
显示层级是由Z-Order属性来控制的,即Z轴的值(类似3D坐标轴),而type实际上就是给Z-Order提供值的。type值越大,对应的Window就显示在越上面(层级越高),换句话说,就是type值大的Window可以把type值小的盖住。我们来看下不同类型的Window都有哪个type值。
应用程序Window(Application Window)
应用程序Window的type值范围是[1-99],那什么是应用程序Window呢?比如我们最熟悉的Activity(所展示的页面)就是,这也很好理解,因为Activity就是应用最基础的展现形式,那当然属于应用程序Window了。系统封装了一系列的常量,在WindowManager#LayoutParams里:
//表示普通应用程序窗口的窗口类型的开始。
public static final int FIRST_APPLICATION_WINDOW = 1;
//窗口类型:作为整个应用程序的“基本”窗口的应用程序窗口;所有其他应用程序窗口将出现在其顶部。在多用户系统中,仅在拥有
//用户的窗口上显示。
public static final int TYPE_BASE_APPLICATION = 1;
//窗口类型:普通应用程序窗口。token 必须是Activity的token,用于标识窗口所属的用户。在多用户系统中,仅在拥有用户的窗口
//上显示。
public static final int TYPE_APPLICATION = 2;
//窗口类型:应用程序启动时显示的特殊应用程序窗口,不供应用程序本身使用。
public static final int TYPE_APPLICATION_STARTING = 3;
//窗口类型:TYPE_APPLICATION的变体,可确保窗口管理器在显示应用之前等待绘制此窗口。
public static final int TYPE_DRAWN_APPLICATION = 4;
//应用程序窗口类型的结束值
public static final int LAST_APPLICATION_WINDOW = 99;
子Window(Sub Window)
子Window的type值范围是[1000,1999],可以看到值就是在应用程序Window之上,这些子Window会按照Z-Order顺序依附于父Window上,而且他们的坐标是相当于父Window的,所以这也就是为什么叫子Window的原因。比如Dialog和PopupWindow,都属于子Window,对应的type值如下:
WindowManager#LayoutParams
//子窗口类型的开始,这些窗口的token必须设置为它们所附加到的窗口。这些类型的窗口按Z-order保留在其附加窗口旁边,并且它们的
//坐标空间相对于其附加窗口。
public static final int FIRST_SUB_WINDOW = 1000;
//窗口类型:应用程序窗口顶部的面板,这些窗口显示在其附加窗口的顶部。
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
//窗口类型:用于显示媒体(如视频)的窗口,这些窗口显示在其附加窗口的后面。
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
//窗口类型:应用程序窗口顶部的子面板。这些窗口显示在其附加窗口和任何TYPE_APPLICATION_PANEL面板的顶部
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
//窗口类型:与TYPE_APPLICATION_PANEL类似,但窗口的布局是作为顶级窗口的布局发生的,而不是作为其容器的子窗口。
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
//窗口类型:用于在媒体窗口顶部显示叠加层的窗口。这些窗口显示在TYPE_APPLICATION_MEDIA和应用程序窗口之间。它们应该是半透
//明的,才有用。这是一个丑陋的大黑客,所以废弃掉了。
@UnsupportedAppUsage
public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4;
//窗口类型:应用程序窗口顶部的上方子面板及其子面板窗口。这些窗口显示在其附加窗口和任何TYPE_APPLICATION_SUB_PANEL面板
//的顶部。
public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
//子窗口类型的结束值。
public static final int LAST_SUB_WINDOW = 1999;
系统Window(System Window)
系统Window的范围是[2000,2999],常见的系统的Window有Toast、输入法窗口、系统音量条窗口、系统错误窗口等,对应type的值如下:
WindowManager#LayoutParams
//特定于系统的窗口类型的初始值, 这些通常不是由应用程序创建的。
public static final int FIRST_SYSTEM_WINDOW = 2000;
//窗口类型:状态栏。只能有一个状态栏窗口;它被放置在屏幕的顶部,所有其他窗口都向下移动,因此它们位于屏幕下方
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
//窗口类型:搜索栏。只能有一个搜索栏窗口;它位于屏幕顶部。
public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;
//窗口类型:电话。这些是非应用程序窗口,提供用户与电话的交互(特别是来电)。这些窗口通常位于所有应用程序的上方,但位于
//状态栏的后面。已弃用,请改用TYPE_APPLICATION_OVERLAY。
@Deprecated
public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;
//窗口类型:系统窗口,如低电量警报。这些窗口始终位于应用程序窗口的顶部。已弃用,请改用TYPE_APPLICATION_OVERLAY。
@Deprecated
public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;
//窗口类型:键盘锁窗口
public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;
//中间省略...
//系统窗口类型的结束值。
public static final int LAST_SYSTEM_WINDOW = 2999;
有点多,就不全贴出来了,而且有很多都被废弃了,大家可以自己去看。这里要注意的是使用系统的Window需要申请权限,即Manifest.permission.SYSTEM_ALERT_WINDOW权限。
flag属性
通过type属性让我们能够控制Window的显示层级,那如何设置Window的其他显示项和配置项呢,源码中为我们提供了许多Flag值,通过设置不同的Flag值可以得到对应的效果。同样是定义在WindowManager#LayoutParams类中:
//窗口标志:只要此窗口对用户可见,就允许锁屏界面在屏幕打开时激活。这可以单独使用,也可以与
//FLAG_KEEP_SCREEN_ON和/或FLAG_SHOW_WHEN_LOCKED结合使用
public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001;
//窗口标志:此窗口后面的所有内容都将变暗。使用 dimAmount 控制 dim 的量。
public static final int FLAG_DIM_BEHIND = 0x00000002;
//窗口标志:为此窗口启用模糊。
public static final int FLAG_BLUR_BEHIND = 0x00000004;
//窗口标志:此窗口永远不会获得键输入焦点,因此用户无法向其发送键或其他按钮事件。相反,这些将转到它后面的任何
//可聚焦窗口,并会直接启用FLAG_NOT_TOUCH_MODAL。
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
//窗口标志:此窗口永远无法接收触摸事件。此标志的目的是将触摸留给此窗口下方的某个窗口(按Z-order)处理。
public static final int FLAG_NOT_TOUCHABLE = 0x00000010;
//窗口标志:即使此窗口是可聚焦的(未设置其 {@link FLAG_NOT_FOCUSABLE}),也允许将窗口外部的任何指针事件发送到
//其后面的窗口。否则,它将消耗所有指针事件本身,无论它们是否在窗口内。
public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020;
@Deprecated
public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;
//窗口标志:只要此窗口对用户可见,就要保持设备的屏幕打开并保持明亮。
public static final int FLAG_KEEP_SCREEN_ON = 0x00000080;
//附加窗口的窗口标志:将窗口放置在整个屏幕中,忽略来自父窗口的任何约束。
public static final int FLAG_LAYOUT_IN_SCREEN = 0x00000100;
//窗口标志:允许窗口延伸到屏幕之外。
public static final int FLAG_LAYOUT_NO_LIMITS = 0x00000200;
//窗口标志:显示此窗口时隐藏所有屏幕装饰(如状态栏)。这允许窗口自行使用整个显示空间 -- 当设置了此标志的应用窗口位于顶层时,
//状态栏将被隐藏。但已经被废弃了,
//请改用 {@link WindowInsetsController#hide(int)} 和 {@link Type#statusBars()}。
@Deprecated
public static final int FLAG_FULLSCREEN = 0x00000400;
//窗口标志:覆盖 {@link FLAG_FULLSCREEN} 并强制显示屏幕装饰(例如状态栏)
//此值“意外”成为 API,不应由第三方应用程序使用。所以也被废弃了。
@Deprecated
public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800;
//窗口标志:将此窗口合成到屏幕时打开抖动。
@Deprecated
public static final int FLAG_DITHER = 0x00001000;
//窗口标志:将窗口的内容视为安全内容,防止其出现在屏幕截图中或在非安全显示器上查看。
public static final int FLAG_SECURE = 0x00002000;
//窗口标志:一种特殊模式,其中布局参数用于在将曲面合成到屏幕时执行曲面缩放。
public static final int FLAG_SCALED = 0x00004000;
//窗口标志:用于当用户将屏幕靠在脸上时经常使用的窗口,它将积极过滤事件流,以防止在这种情况下出现特定窗口可能不希望的意外
//按下,当检测到此类事件流时,应用程序将收到一个 CANCEL motion 事件来指示这一点,以便应用程序可以通过不对事件执行任何操作
//来相应地处理此问题,直到直到手指松开。
// 其实就是当用户的脸贴近屏幕时(比如打电话),不会去响应此事件
public static final int FLAG_IGNORE_CHEEK_PRESSES = 0x00008000;
@Deprecated
public static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000;
//窗口标志:设置后,反转窗口的输入法可聚焦性。设置此标志的效果取决于是否设置了 {@link FLAG_NOT_FOCUSABLE}:
//如果未设置 {@link FLAG_NOT_FOCUSABLE},即当窗口可聚焦时,则设置此标志可防止此窗口成为输入法的目标。因此,
//它将无法与输入法交互,并且将分层到输入法之上(除非其上方有另一个输入法目标)。
//如果设置了 {@link FLAG_NOT_FOCUSABLE} ,则设置此标志会请求将窗口作为输入法目标,即使窗口不可聚焦也是如此。
//因此,它将在输入法下方分层。注意:无论此标志如何,设置 {@link FLAG_NOT_FOCUSABLE} 的 Windows 都无法与输入法交互。
public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000;
//窗口标志:如果设置了 {@link FLAG_NOT_TOUCH_MODAL},则可以将此标志设置为接收一个特殊的 MotionEvent,其中包含发生在
//窗口外的触摸的操作 {@link MotionEvent#ACTION_OUTSIDE MotionEvent.ACTION_OUTSIDE}。请注意,您不会收到完整的下移手势,
//只会收到第一个下移的位置作为ACTION_OUTSIDE。
public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;
//窗口标志:要求在该窗口后面显示系统墙纸。窗口表面必须是半透明的,才能真正看到它后面的墙纸;此标志仅确保如果此窗口实际上
//具有半透明区域,墙纸表面将在那里。
public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
//窗口标志:设置后,窗口将接受超出其边界的触摸事件,以发送到也支持拆分触摸的其他窗口。如果未设置此标志,则向下的第一个指针
//将确定所有后续触摸所到的窗口,直到所有指针都向上。设置此标志后,每个向下的指针(不一定是第一个指针)将确定该指针的所有
//后续触摸将转到的窗口,直到该指针上升,从而使具有多个指针的触摸能够跨多个窗口拆分。
public static final int FLAG_SPLIT_TOUCH = 0x00800000;
//指示是否应对此窗口进行硬件加速。请求硬件加速并不能保证它会发生。
//此标志只能通过编程方式进行控制,以启用硬件加速。要以编程方式为给定窗口启用硬件加速,请执行以下操作:
//Window w = activity.getWindow(); // in Activity's onCreate() for instance
//w.setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, //WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
//请务必记住,在设置活动或对话框的内容视图(set content view)之前,必须设置此标志。
//也就是说,当我们要启用硬件加速时,需要在代码层面动态设置,且要在视图加载之前(Activity的话就是要在setContentView()方法之前)
public static final int FLAG_HARDWARE_ACCELERATED = 0x01000000;
//处于本地焦点模式的窗口的标志。
public static final int FLAG_LOCAL_FOCUS_MODE = 0x10000000;
//指示此窗口负责绘制系统栏的背景的标志。如果设置,则使用透明背景绘制系统栏,并且此窗口中的相应区域将填充
// {@link Window#getStatusBarColor()} 和 {@link Window#getNavigationBarColor()} 中指定的颜色。
public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000;
flag属性也很多...其中有些废弃的我就省略了。之所以完整的过一遍是因为我觉得,对flag属性熟悉一遍还是挺有必要的,因为在日常开发里还是比较容易碰上的,比如使用Dialog时,会经常用到这些属性。当我们真正知道这些Flag的作用之后,可以让我们更好地去管理View。
SoftInputMode(软键盘)
表示Window软键盘输入区域的显示模式,我们前面说过,键盘是系统Window,它的层级比较高,也就是说它会盖住下面层级的Window。比如在微信聊天界面,当我们点击输入框弹出键盘的时候,如果不做处理,那么就会把下面层级的Window盖住,这当然不是我们希望的。
所以我们对软键盘的一些属性设置,其实设置的就是软键盘所对应Window的属性。同样,系统封装了一系列常量值在WindowManager#LayoutParams里:
//{@link softInputMode} 的可见性状态:未指定任何状态。当窗口获得焦点时,系统可能会显示或隐藏软件键盘以获得更好的用户体验。
public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0;
//{@link softInputMode} 的可见性状态:请不要更改软输入区域的状态。
public static final int SOFT_INPUT_STATE_UNCHANGED = 1;
//{@link softInputMode} 的可见性状态:请在正常情况下隐藏任何软输入区域(当用户向前导航到您的窗口时)。
public static final int SOFT_INPUT_STATE_HIDDEN = 2;
//{@link softInputMode} 的可见性状态:当此窗口获得焦点时,请始终隐藏任何软输入区域。
public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;
//{@link softInputMode} 的可见性状态:请在正常情况下显示软输入区域(当用户向前导航到您的窗口时)。
public static final int SOFT_INPUT_STATE_VISIBLE = 4;
//{@link softInputMode} 的可见性状态:当此窗口接收到输入焦点时,请始终使软输入区域可见。
public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;
//window会调整大小以适应软键盘窗口
public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;
//没有指定状态,系统会选择一个合适的状态或依赖于主题的设置
public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;
//...
具体大家可以参照源码和注释去看,我们还可以在AndroidManifest(activity标签)文件中设置软键盘的弹出规则
<activity
android:name=".MainActivity"
android:windowSoftInputMode="adjustNothing"
android:exported="true" />
也就是说我们可以给不同的Activity(所在的Window)来配置不同的软键盘弹出属性。这里埋个坑,那每开一个Activity都会对应加载出一个Window吗?
其他属性
- x和y:Window的左上角坐标,相对于屏幕的坐标轴系。
- alpha:Window的透明度。
- gravity: Window在屏幕中的位置,使用的是Gravity类的常量。
- format: Window的像素格式,值定义在PixelFormat中。
到这里我们基本也就把Window的属性都过完了,Window作为View的载体,而View是Window的具体表现形式,这就使得我们通过设置Window的各种属性,可以更方便的处理Activity、Dialog等各种不同View的表现形式,这也让我们对Android View的层级有了更清晰的认识。
原理探究
虽然到这里我们对Window有个大概的了解,但其实应该疑惑更多,比如:
- Window是View的载体,而ViewRootImpl作为View的绘制类,那它和Window是什么关系?
- Activity、Dialog和Toast都是通过Window来显示和管理View,他们有什么区别?为什么Dialog必须在Activity中弹出?
- MainActivity(首个Activity)是怎么样展示出来的,这中间经历了哪些过程?
那么,就让我们来通过源码一探究竟。
Window的内部机制
我们前面说了,Window是一个抽象的概念,添加Window是通过WindowManager来完成的,所以我们就通过WindowManager的使用方法作为切入点,来探究一下Window的内部机制。(PS:我记得郭神说过,探究源码最好的切入点就是从用法入手)
Window的添加过程
例子前面说过了,就不再赘述了。
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
windowManager.addView(textView, layoutParams)
我们知道,WindowManager是一个接口,那么addView()的具体逻辑是谁来实现的呢?我们看下getSystemService()方法:
Activity.java
@Override
public Object getSystemService(@ServiceName @NonNull String name) {
if (getBaseContext() == null) {
throw new IllegalStateException(
"System services not available to Activities before onCreate()");
}
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
return super.getSystemService(name);
}
也就是,我们获取到的WindowManager实例就是mWindowManager,看下它是怎么初始化的:
mWindowManager = mWindow.getWindowManager();
在Activity的attach()方法中,mWindowManager被初始化,我们再跟进去看看:
Window.java
public WindowManager getWindowManager() {
return mWindowManager;
}
同样再看下mWindowManager是怎么被初始化的:
Window.java
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
//省略...
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
到这里其实我们也就知道了,其实WindowManager的真正实现类是WindowManagerImpl类.
public class WindowManagerImpl implements WindowManager {
}
以上逻辑的具体调用时机和流程我们先不管,因为我们暂时想知道的只是WindowManager的真正实现类。所以这时候真正的切入点就出现了,我们看下WindowManagerImpl的addView方法:
@Override
public void addView(View arg0, android.view.ViewGroup.LayoutParams arg1) {
// pass
}
大失所望,它是空的呜呜T_T...其实我们通过源码网站WindowManagerImpl.java就可以看到它的实现逻辑:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
也就是说,最终addView是交给mGlobal来做的,而mGlobal是WindowManagerGlobal的一个全局单例:
WindowManagerGlobal.java
@UnsupportedAppUsage
public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}
这里的工作模式就是典型的桥接模式,把所有的操作都委托给WindowManagerGlobal来实现,所以WindowManagerGlobal才是WindowManager的真正逻辑实现类,我们看一下addView的实现:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
//判断参数的合法性
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
//如果parentWindow不为空,就说明当前Window是子Window,需要对参数做额外调整
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
//ViewRootImpl实例(终于出现了)
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
//省略一些无关代码
//创建ViewRootImpl实例,并且设置参数
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//分别记录View树 ViewRootImpl和Window参数
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// 最后执行此操作,因为它会触发消息以开始执行操作
try {
//最后通过ViewRootImpl来添加Window
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
//省略...
}
}
}
代码比较长,删减了一些不相关的。这里提一句,看源码的时候千万不要想去把所有东西都看懂,否则就会陷进去,到最后都不知道自己一开始是干嘛来了,我们只要抓住主线任务就行了。这段代码的主逻辑还是很清晰的,创建出ViewRootImpl实例,然后将当前要处理的窗口视图所对应的信息(View、ViewRootImpl、WindowManager.LayoutParams),通过三个ArrayList保存起来,最后调用
ViewRootImpl的setView()方法,我们来看下它的执行逻辑:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
if (mView == null) {
mView = view;
...
//关键点1
//在将第一个布局添加到WindowManager之前执行一次,以确保我们在从系统接收任何其他事件之前进行重新布局
requestLayout();
//关键点2
//通过WindowSession来完成IPC调用,完成创建Window
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
adjustLayoutParamsForCompatibility(mWindowAttributes);
controlInsetsForCompatibility(mWindowAttributes);
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
mTempControls);
if (mTranslator != null) {
mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
}
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}
...
}
}
这个方法非常长,但我们抓出主线逻辑的话其实主要就是干俩件事,第一件就是调用requestLayout()方法来更新界面。注意这里的Layout和我们自定义View中重写的onLayout()以及layout()不是同一个语义,这里的requestLayout()指的是整个布局流程。我们来看下它的实现:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
熟悉吧,scheduleTraversals,顾名思义,就是准备开始遍历,遍历谁呢,当然就是View树了。相信就算你不清楚它的具体逻辑,你也听过UI的绘制就是从scheduleTraversals开始的。没错,scheduleTraversals()方法就是View绘制的入口,这里就不展开了,简单说下。scheduleTraversals()方法内部会调用到performTraversals()方法,而performTraversals()方法最终会调用performMeasure()方法,来对自身的View树开始执行绘制三部曲。
我们再回头看,ViewRootImpl的setView()方法做的第二件事就是通过mWindowSession的addToDisplayAsUser()方法来完成Window的添加过程,那这个mWindowSession又是什么呢?
ViewRootImpl.java
@UnsupportedAppUsage
final IWindowSession mWindowSession;
public ViewRootImpl(Context context, Display display) {
this(context, display, WindowManagerGlobal.getWindowSession(), false);
}
public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session) {
this(context, display, session, false);
}
mWindowSession是一个IWindowSession对象,通过WindowManagerGlobal的getWindowSession()获取。
@UnsupportedAppUsage
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
// Emulate the legacy behavior. The global instance of InputMethodManager
// was instantiated here.
InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}
而IWindowSession是一个IBinder接口(这点我们从命名上就看得出来),所以mWindowSession只是一个Binder对象,而实现类在WindowManagerService中,这里通过mWindowSession完成了IPC通信。
然后真正添加Window的逻辑就交由WindowManagerService(简称WMS)了,由于WMS比较复杂,这里就不过多深入了。也就是说,系统服务帮我们创建了Window,这也符合系统的统一管控,但这个时候我们并没有发现有对应的Window实例出现,先带着这个问题。
到这里,我们也就清楚了ViewRootImpl类的具体作用了。我们从WindowManager的addView方法入手,再到找出WindowManager的实现类WindowManagerImpl,再到WindowManagerImpl把操作委托给WindowManagerGlobal类,我们发现WindowManagerGlobal类才是WindowManager真正的逻辑实现类。然后再到WindowManagerGlobal内创建出ViewRootImpl的实例,最后通过ViewRootImpl的setView方法来开始布局逻辑(requestLayout)以及IPC完成Window的添加(创建)。
所以ViewRootImpl类扮演的角色就是View和WindowManagerService的桥梁,一边执行View的绘制逻辑,一边又通过IPC通信让WMS来创建Window。
梳理下这几个涉及的类:
ViewRootImpl:每次我们在入口处调用addView()方法,到最后都会创建一个ViewRootImpl实例出来,所以这也就说明了,一个View树会对应着一个ViewRootImpl实例。同时它是Window和View之间的桥梁,一边负责View的绘制,一边负责IPC通信到WMS创建Window。
IWindowSession:全局(APP范围内)单例,是一个Binder,负责和WMS通信。那它为什么要设计成单例模式呢?这是因为WMS是系统服务,那么我们手机内的所有APP都要和它交互,而一个APP又(可能)有多个Window,所以每个Window都要WMS来管理的话,那就太繁琐了,也会浪费资源。而当每个应用内只有一个单例时,WMS只需要和该IWindowSession实例进行通信即可,由它来代表整个APP和WMS通信。
WindowManagerGlobal:WindowManager的真正逻辑实现类,从名字也可以看出来,它也是单例模式。当我们调用WindowManager的addView方法时,最终就是调用它的单例来处理。
所以它们的关系如下:
Window的删除过程
我们再来看下Window的删除过程,和添加操作一样,最终会由WindowManagerGlobal类来处理,具体的话是removeView()方法,我们看下逻辑:
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
//线程同步
synchronized (mLock) {
//找出需要删除的View
int index = findViewLocked(view, true);
//通过index来拿到对应的ViewRootImpl实例,然后拿到对应View
View curView = mRoots.get(index).getView();
//真正的删除操作
removeViewLocked(index, immediate);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
private int findViewLocked(View view, boolean required) {
final int index = mViews.indexOf(view);
if (required && index < 0) {
throw new IllegalArgumentException("View=" + view + " not attached to window manager");
}
return index;
}
逻辑很简单,找到对用的View,然后执行删除操作。在前面添加Window里说过,WindowManagerGlobal类中会有几个数组分别来保存当前视图的View、ViewRootImpl等信息,所以我们这时候就能很容易找到需要删除的View(或者说View树)。但你会发现,虽然我们有一个保存View的数组mViews,但我们只是找到对用的下标,而真正View的获取是通过ViewRootImpl的getView()方法来得到的。其实我们前面也说了,ViewRootImpl就是负责View的绘制的,每个ViewRootImpl实例都有自己的View树,所以从ViewRootImpl里获取实际的View会更加严谨。我们看下removeViewLocked()方法:
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if (root != null) {
root.getImeFocusController().onWindowDismissed();
}
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
可以看到最终还是通过ViewRootImpl来完成删除操作的,先拿到对应的View,然后调用ViewRootImpl的die()方法来处理,最后把View加入mDyingViews数组中,该数组表示待删除的View列表,我们看一下这个die方法:
/**
* @param immediate True, do now if not in traversal. False, put on queue and do later.
* @return True, request has been queued. False, request has been completed.
*/
boolean die(boolean immediate) {
// Make sure we do execute immediately if we are in the middle of a traversal or the damage
// done by dispatchDetachedFromWindow will cause havoc on return.
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
" window=" + this + ", title=" + mWindowAttributes.getTitle());
}
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
当我们设置立即删除或者没有在进行View相关绘图操作时,就直接调用doDie()方法;否则就延后处理,通过Handler把消息发出去。看下doDie()方法:
ViewRootImpl.java
void doDie() {
checkThread();
if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
synchronized (this) {
if (mRemoved) {
return;
}
mRemoved = true;
if (mAdded) {
//View的删除逻辑
dispatchDetachedFromWindow();
}
...
mAdded = false;
}
//移除前面说的保存View视图对应信息的数组里当前ViewRootImpl实例所对应的对象
WindowManagerGlobal.getInstance().doRemoveView(this);
}
删减了下代码,真正删除View的逻辑在dispatchDetachedFormWindow方法中,然后再调用WindowManagerGlobal的doRemoveView方法来刷新保存的列表。我们先看下dispatchDetachedFormWindow方法:
ViewRootImpl.java
void dispatchDetachedFromWindow() {
...
if (mView != null && mView.mAttachInfo != null) {
...
//标记点1
mView.dispatchDetachedFromWindow();
}
//标记点2
mAccessibilityInteractionConnectionManager.ensureNoConnection();
mAccessibilityManager.removeAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager);
mAccessibilityManager.removeHighTextContrastStateChangeListener(
mHighContrastTextManager);
removeSendWindowContentChangedCallback();
destroyHardwareRenderer();
setAccessibilityFocus(null, null);
mInsetsController.cancelExistingAnimations();
mView.assignParent(null);
mView = null;
mAttachInfo.mRootView = null;
destroySurface();
if (mInputQueueCallback != null && mInputQueue != null) {
mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
mInputQueue.dispose();
mInputQueueCallback = null;
mInputQueue = null;
}
try {
//标记点3
mWindowSession.remove(mWindow);
} catch (RemoteException e) {
}
...
//移除同步屏障信号,不再去刷新
unscheduleTraversals();
}
主要做了三件事:
标记点1:调用了View的dispatchDetectedFromWindow方法,在该方法内会调用onDeteachedFromWindow方法,该方法我们在做自定义View时,可以在该方法内做一些资源回收的工作,比如终止动画、停止线程等。以及其他一些Detach的监听回调。
标记点2:垃圾回收相关工作,比如清楚数据和消息,移除回调等。
标记点3:通过IWindowSession的remove方法来删除Window,这里也是一个IPC过程,真正删除的地方还是WMS。
通过删除Window的过程,让我们再次确定了几个类的关系及它们所扮演的角色。还有个Window的更新过程我们就不看了,也比较简单,大家可以自行去看。
到这里我们就把Window的几个方法都过了一遍,也理清了整个流程,但是这都是我们主动的逻辑调用,也就是说对应的Window都是我们在应用跑起来,Activity加载完再去操作的。那么你肯定会好奇:
Activity是怎样加载的,它的加载流程跟我们上面分析的一样吗?那它的WindowManager实例是什么时候创建的?
我们前面通过分析知道,其实最后都会调用WindowManagerGlobal类的APP单例来真正去实现对应的逻辑,那WindowManager的实现类WindowManagerImpl有什么用呢,我们为什么不直接使用WindowManagerGlobal类?
我们都知道,Window有一个唯一的实现类PhoneWindow,它又是在这当中充当了什么角色?
那就让我们来探究一下Android内置几种视图的Window的创建过程,这非常有利于我们理解Android系统。
Activity的Window创建过程
我们知道,当我们点击手机的APP图标之后,那么我们的APP就开始进入启动流程了。虚拟机创建,通过一系列系统调度(这里就不展开启动流程相关内容了),我们会进到主线程(也就是UI线程)的入口ActivityThread类的main方法里来,做一些初始化工作之后,再由系统调用performLaunchActivity方法来完成整个Activity启动过程。看下performLaunchActivity方法:
/**
* Activity启动的核心实现
*/
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
//activity
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
//创建activity实例
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
...
} catch (Exception e) {
...
}
try {
...
if (activity != null) {
...
Window window = null;
...
//关键点1
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback,
r.assistToken, r.shareableActivityToken);
...
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
//关键点2,回调Activity的onCreate()方法
mInstrumentation.callActivityOnCreate(activity, r.state);
}
...
}
...
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
...
}
return activity;
}
先通过类加载器创建Activity的实例对象(这边有个工具类Instrumentation,是专门用来辅助执行Activity相关操作的),然后调用Activity的attach方法为其关联运行过程中所依赖的上下文环境变量,最后再调用Instrumentation类的callActivityOnCreate()方法,来回调Activity的onCreate()方法,相信到onCreate()方法,大家也就熟悉了。我们先看下attach()方法做了什么:
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
final void attach(...) { //省略参数
...
//创建PhoneWindow实例
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(mWindowControllerCallback);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...
//保存当前线程,checkThread的时候会用到
mUiThread = Thread.currentThread();
mMainThread = aThread;
...
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
...
}
在这边创建了PhoneWindow实例,而且设置了一些相关回调,同时Activity自己就实现了接口,所以当Window接收到外界的状态改变时,就会回调到Activity里来。我们主要来看下Callback接口,它是定义在Window类里的:
public interface Callback {
public boolean dispatchKeyEvent(KeyEvent event);
public boolean dispatchTouchEvent(MotionEvent event);
boolean onMenuOpened(int featureId, @NonNull Menu menu);
boolean onMenuItemSelected(int featureId, @NonNull MenuItem item);
public void onContentChanged();
public void onWindowFocusChanged(boolean hasFocus);
public void onAttachedToWindow();
public void onDetachedFromWindow();
...
}
可以看到,有些我们很熟悉的Activity回调方法,就是在这个时候通过Window设置的监听回调。
那到这里,Window(PhoneWindow)也有了,那怎么跟Activity的View联系呢?回头看performLaunchActivity()方法,当我们执行完attach()方法之后,就会去执行mInstrumentation的callActivityOnCreate()方法(关键点2),我们看下callActivityOnCreate做了什么:
Instrumentation.java
/**
* 执行对活动的 Activity.onCreate 方法的调用。
*/
public void callActivityOnCreate(Activity activity, Bundle icicle) {
prePerformCreate(activity);
activity.performCreate(icicle);
postPerformCreate(activity);
}
我们再跟到Activity的performCreate()方法里:
final void performCreate(Bundle icicle) {
performCreate(icicle, null);
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
final void performCreate(Bundle icicle, PersistableBundle persistentState) {
...
if (persistentState != null) {
onCreate(icicle, persistentState);
} else {
//Activity生命周期第一个回调
onCreate(icicle);
}
...
//把事件分发给自己的Fragment,回调Fragment的onActivityCreated()方法
mFragments.dispatchActivityCreated();
...
}
熟悉吧,onCreate方法,也就是这个时候,Activity开始了生命周期回调,然后我们在onCreate里面加载布局,具体来看下它的逻辑:
/**
* 在Activity开始时调用。这是大多数初始化应该进行的地方:调用 {@link setContentView(int)} 来扩充 Activity 的界面,使用 {@link
* findViewById} 以编程方式与界面中的小组件进行交互。
*/
protected void onCreate(@Nullable Bundle savedInstanceState) {
...
//同样,把事件分发给自己的Fragment
mFragments.dispatchCreate();
//获取到所有对当前Activity生命周期监听的监听器,把事件分发出去,
//这也就是为什么ViewModel等组件能有生命周期感知能力的原因
dispatchActivityCreated(savedInstanceState);
...
}
可以看到,逻辑很简单,主要就是事件的分发。那么也就意味着,还有对应的布局逻辑就要在我们重写的时候做,注意看注释,也就是说我们重写时,要去调用setContentView()方法来加载Activity对应的布局。更熟悉了吧,这就是我们从第一次写出第一个界面时,就跟我们见面的方法,来看下在Activity类里的原始实现:
/**
* 从布局资源设置Activity内容。资源将被扩充,将所有顶级视图添加到Activity中。
*/
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
先是调用了getWindow()方法来获取Window实例,这里返回的是mWindow,而mWindow就是上面我们提到的在Activity的attach()方法里实例化出来的,也就是PhoneWindow实例,我们跟进去看下:
PhoneWindow.java
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
//关键点1
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 {
//关键点2
mLayoutInflater.inflate(layoutResID, mContentParent);
}
...
//这个Callback就是上面提到的Window类的Callback,这里回调onContentChanged方法,
//把内容加载完毕的事件通知出去,回调到Activity
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
...
}
其实主逻辑就是当mContentParent为空的时候,调用installDecor()方法(顾名思义,就是来创建DecorView的),然后再通过mLayoutInflater的inflate()方法把我们对应的布局资源加载到mContentParent容器里。很显然,这个mContentParent是一个ViewGroup:
//这是放置窗口(window)内容的视图,它要么是 mDecor 本身,要么是内容所在的 mDecor 的子项。
ViewGroup mContentParent;
我们先分析关键点1,也就是installDecor()方法:
PhoneWindow.java
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
//创建DecorView实例
mDecor = generateDecor(-1);
...
} else {
//将DecorView与当前的PhoneWindow绑定
mDecor.setWindow(this);
}
if (mContentParent == null) {
//获取到content的装载容器
mContentParent = generateLayout(mDecor);
...
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
if (decorContentParent != null) {
mDecorContentParent = decorContentParent;
mDecorContentParent.setWindowCallback(getCallback());
if (mDecorContentParent.getTitle() == null) {
mDecorContentParent.setWindowTitle(mTitle);
}
...
} else {
//标题相关内容就是在这里设置的
mTitleView = findViewById(R.id.title);
if (mTitleView != null) {
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
final View titleContainer = findViewById(R.id.title_container);
if (titleContainer != null) {
titleContainer.setVisibility(View.GONE);
} else {
mTitleView.setVisibility(View.GONE);
}
mContentParent.setForeground(null);
} else {
mTitleView.setText(mTitle);
}
}
}
...
}
}
主要逻辑就是创建了DecorView的实例,然后通过generateLayout()方法生成装载容器。我们再进到generateLayout()方法(本来想一笔带过,但发现这个方法挺重要,经过它流程更清晰):
PhoneWindow.java
protected ViewGroup generateLayout(DecorView decor) {
...
//设置系统UI的可见性,状态栏和导航栏的可见性就是在这里设置的
final int sysUiVis = decor.getSystemUiVisibility();
final int statusLightFlag = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
final int statusFlag = a.getBoolean(R.styleable.Window_windowLightStatusBar, false)
? statusLightFlag : 0;
final int navLightFlag = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
final int navFlag = a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)
? navLightFlag : 0;
decor.setSystemUiVisibility(
(sysUiVis & ~(statusLightFlag | navLightFlag)) | (statusFlag | navFlag));
// Inflate the window decor.
//注意看这里注释,要来填充DecorView了
//下面这么一长串的代码就是根据不同的窗口特性来选择DecorView对应的布局
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
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 {
//默认情况下没有别的装饰就会使用screen_simple布局
layoutResource = R.layout.screen_simple;
}
mDecor.startChanging();
//关键点3
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//关键点4
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
mDecor.finishChanging();
return contentParent;
}
前面的一大串代码就是根据Window的本地特性来选择DecorView的布局文件,默认情况最终会使用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布局,包含一个actionbar和一个id为content的FrameLayout。眼熟吧,最终Activity的布局其实就是方法到这个FrameLayout里面。然后再调用DecorView的onResourcesLoaded()方法把这个布局文件加载到DecorView中,我们跟进来看下:
DecorView.java
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
...
mDecorCaptionView = createDecorCaptionView(inflater);
//加载出screen_simple布局文件
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
...
} else {
// 默认情况会走这边的逻辑,把加载出来的root视图添加到DecorView中
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
...
}
DecorView自身其实是继承自FrameLayout,这样一来它就会包含一个LinearLayout,而这个LinearLayout内部有含有标题栏(actionbar)和一个id为content的FrameLayout容器。
再看下关键点4,获取到id为ID_ANDROID_CONTENT的容器实例,而这个ID_ANDROID_CONTENT指向的就是上面说的id为content的FrameLayout容器:
//XML 布局文件中的主布局应具有的 ID。
ublic static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
最后generateLayout()方法会把这个id为content的容器返回,赋值给PhoneWindow的成员变量mContentParent。这也说明了其实generateLayout方法不仅获取返回装载容器,而且它还做了DecorView的填充逻辑。
那我们再回头看下关键点2,也就是执行完installDecor()方法之后,会调用LayoutInflater的inflate()方法:
LayoutInflater.java
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
...
//通过反射来获取View
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
tryInflatePrecompiled()方法是通过反射来获取到预编译的布局类,然后拿到布局的顶层View,添加到mContentParent(也就是上文说的装载容器)中。注意区分这里LayoutInflater类的作用,它只是负责填充(膨胀 inflate)视图,跟View的具体绘制没关系。所以这里是先通过反射来获取View,如果成功就直接返回,如果不成功再通过XmlResourceParser来解析,就不再展开了。也就是说,对于我们布局文件的加载(把布局文件填充到PhoneWindow的装在容器mContentParent中),其实是在这里完成的。另一方面也表明了,对于Activity布局文件的装载,是PhoneWindow在管控。
到这里你会发现,我们的DecorView已经创建且初始化完毕,而且Activity的文件也成功添加到mContentParent(DecorView的内容装载容器)中,但是并没有看到像前面说的通过WindowManager来添加Window啊?别急,我们接着往下看。
当Activity的生命周期再往下走时,就会经由ActivityThread的handleResumeActivity()方法来最终进入到onResume的生命周期回调,我们看下handleResumeActivity()方法做了什么:
ActivityThread.java
@Override
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
boolean isForward, String reason) {
...
// The window is now visible if it has been added, we are not
// simply finishing, and we are not starting another activity.
if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
...
r.activity.mVisibleFromServer = true;
mNumVisibleActivities++;
//开始对客户端可见
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
}
...
//开始执行空闲事务,一些与启动无关的逻辑可以放到这边来做,优化启动速度
Looper.myQueue().addIdleHandler(new Idler());
}
此时,Activity开始对用户可见,会调用Activity的makeVisible()方法:
Activity.java
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
怎么样,回到熟悉的感觉了吧!这就是我们一开始说的通过WindowManager来addView的方式,只不过这里加的是Activity的顶层View:DecorView。至此,DecorView真正地通过WMS加到Window里来,再经过一系列的绘制过程(scheduleTraversals),整个View树被绘制到屏幕,状态转为可见,Activity才可以正常收到外界输入信息。同时这里也说明了在Activity的生命周期中,为什么在onResume回调中才可以接受到手指的触摸事件。
我们再来简单总结一下上面的几个步骤:
创建DecorView
DecorView翻译过来就是装饰视图,它其实是一个FrameLayout,是Activity中的顶级View。一般来说它会有一个子view:LinearLayout,包含内部标题栏和内容栏,会随着主题变化而改变。但其中的内容栏是一定存在的,它的固定id是content,是Activity布局最终加载的容器。DecorView的创建过程由installDecor方法来完成,而创建完的DecorView还是一个空白的FrameLayout,然后再通过generateLayout方法(内部调用DecorView的onResourcesLoaded方法)来加载出自己的布局文件。将布局文件加载到内容装载容器
紧接着再通过LayoutInflater的inflate()方法把Activity的布局文件填充加载到DecorView的内容装载容器里,也就是mContentParent中。而这里由于mContentParent的id是content,也就侧面说明了为什么在Activity中设置布局的方法名是setContentView而不是setView。其实从这里也可以看出,对于布局文件的加载(以及标题的设置),都是由PhoneWindow类来管控的,只不过具体视图是通过DecorView来展现。回调Activity的onContentChanged方法
由于Activity实现了Window的Callback接口,这里Activity的布局已经被添加到DecorView的mContentParent中了,于是最后回调Activity的onContentChanged方法,将事件传递出去,我们可以在我们的Activity中处理这个回调。ActivityThread的handleResumeActivity()方法得到执行
Activity的生命周期往下走,通过执行handleResumeActivity()方法,最终DecorView通过WindowManager添加到Window中来,而接下去的逻辑就是我们一开始分析的addView逻辑了。至此,Activity的Window创建过程也就完成了。
总结
到这里,我们也就把Activity的Window创建流程梳理完了,我们简单再回顾下。首先是Activity之所以能展现在我们面前(屏幕上),是因为通过WMS系统服务来替我们创建了Window,但是其实这个Window我们是看不见摸不着的(PhoneWindow其实也只是管理类,更多的作用是为DecorView提供UI模板),它只是一个抽象的概念(但我们可以通过Activity的getWindow方法获取),而具体的表现形式则是DecorView,它是实实在在的,我们能在屏幕上看得见的,最终把Activity的布局加载展示出来。
可真是一场酣畅淋漓的战斗呀!说实话,阅读源码的过程真的是又枯燥又繁琐,又让人头大,前前后后花了好多天才理完,头都要爆炸了。但是当我们最后把整个逻辑都理清楚,拨开乌云见月明的时候,就会让人异常兴奋,很有成就感,这就是代码的魅力吧!