Android framework层离不开AMS、WMS、PMS等,其中WMS中Window这个概念比较抽象,且很多博客作者写了很多关于Window的文章,我个人感觉还缺点什么,本文尽可能通过通俗易懂的方式来描述(android11和13源码)。

0快问快答

1)Window,WMS,Activity之间的联系(什么时候Window和Activity和WMS产生联系)

2)Window和View区别和联系(什么时候Window和View产生联系)

3)Window的类别和层次关系(为什么PopWindow会遮住App的显示,锁屏为什么看不到Launcher)

4)activity与 PhoneWindow与DecorView关系 (如果我添加一个View,会添加到系统层面去吗)

1介绍

Window 是一个窗口的概念,是所有视图的载体,不管是 Activity,Dialog,还是 Toast,他们的视图都是附加在 Window 上面的。例如在桌面显示一个悬浮窗,就需要用到 Window 来实现。

2源码跟踪分析问题

2.1Window,WMS,Activity之间的联系

  • Window: 在Android视图体系中Window就是一个窗口的概念。 Android中所有的视图都是依赖于Window显示的。
  • WindowManager: 对Window的管理, 包括新增、更新和删除等。
  • WMS(WindowManagerService): 窗口的最终管理者, 它负责窗口的启动、添加和删除, 另外窗口的大小和层级也是由WMS进行管理

具体关于三者之间的联系,请翻看2.5.1

2.2Window和View区别和联系

WindowManagerImpl实际控制对应的PhoneWindow

ViewRootImpl与View树进行关联,这样ViewRootImpl就可以指挥View树的具体工作

Activity#onCreate()中调用setContentView()方法,这个方法内部创建一个DecorView实例作为PhoneWindow的内容。WindowManagerImpl决定管理DecorView, 并创建一个ViewRootImpl实例

具体关于Window和View的联系,请翻看2.5.2和2.5.3

2.3Window的类别和层次关系

通常我们开发应用的时候会用到各种View、布局,但是这和Window并不是一回事。打开一个应用,出现界面,我们可以理解出现了一个窗口。实际上View和Window是光年和年的关系。

一个 Activity 可以理解 对应一个 Window,ViewRootImpl 是对应一个 Window。

查看window方式,笔者这里使用手机打开浏览器的设置操作

 1$ dumpsys window windows
 2# 输入法
 3Window #0 Window{6c306bc u0 InputMethod}:
 4mBaseLayer=141000 mSubLayer=0    mToken=WindowToken{25f6be3 android.os.Binder@5319812}
 5isVisible=false
 6# 圆角
 7Window #1 Window{6474d57 u0 RoundCorner}:
 8mBaseLayer=311000 mSubLayer=0    mToken=WindowToken{7706d6 android.os.BinderProxy@359caf1}
 9isVisible=true
10# 圆角
11Window #2 Window{98435fe u0 RoundCorner}:
12mBaseLayer=311000 mSubLayer=0    mToken=WindowToken{a6ec0b2 android.os.BinderProxy@1af12bd}
13isVisible=true
14# 导航栏
15Window #3 Window{987738d u0 NavigationBar}:
16mBaseLayer=231000 mSubLayer=0    mToken=WindowToken{cdfac24 android.os.BinderProxy@1bf9cb7}
17isVisible=true
18# 状态栏
19Window #4 Window{85d4239 u0 StatusBar}:
20mBaseLayer=181000 mSubLayer=0    mToken=WindowToken{e3e1a00 android.os.BinderProxy@bf7a332}
21isVisible=true
22# 圆角
23Window #5 Window{5c8904f u0 RoundCorner}:
24mBaseLayer=171000 mSubLayer=0    mToken=WindowToken{c281aae android.os.BinderProxy@485e729}
25isVisible=false
26# SYSTEM_ALERT_WINDOW
27Window #6 Window{e12503c u0 Aspect}:
28mBaseLayer=111000 mSubLayer=0    mToken=WindowToken{cb4622f android.os.BinderProxy@bba880e}
29isVisible=false
30# 协助预览盘
31Window #7 Window{31fbd71 u0 AssistPreviewPanel}:
32mBaseLayer=41000 mSubLayer=0    mToken=WindowToken{f8d1f18 android.os.BinderProxy@c09ec8a}
33isVisible=false
34# 分屏窗口
35Window #8 Window{67faf21 u0 DockedStackDivider}:
36mBaseLayer=21000 mSubLayer=0    mToken=WindowToken{f740988 android.os.BinderProxy@d47412b}
37isVisible=false
38# 浏览器设置
39Window #9 Window{297fd5f u0 PopupWindow:9fecaec}:
40mBaseLayer=21000 mSubLayer=2    mToken=AppWindowToken{380207c token=Token{d724b6f ActivityRecord{b054e4e u0 com.android.chrome/org.chromium.chrome.browser.ChromeTabbedActivity t220}}}
41isVisible=true
42# 浏览器
43Window #10 Window{7a87747 u0 com.android.chrome/org.chromium.chrome.browser.ChromeTabbedActivity}:
44mBaseLayer=21000 mSubLayer=0    mToken=AppWindowToken{380207c token=Token{d724b6f ActivityRecord{b054e4e u0 com.android.chrome/org.chromium.chrome.browser.ChromeTabbedActivity t220}}}
45isVisible=true
46# Launcher悬浮窗口
47Window #11 Window{8c10cde u0 LauncherOverlayWindow:com.miui.personalassistant}:
48mBaseLayer=21000 mSubLayer=0    mToken=AppWindowToken{e921607 token=Token{361c446 ActivityRecord{6d5b688 u0 com.miui.home/.launcher.Launcher t1}}}
49isVisible=false
50# Launcher
51Window #12 Window{6163c5a u0 com.miui.home/com.miui.home.launcher.Launcher}:
52mBaseLayer=21000 mSubLayer=0    mToken=AppWindowToken{e921607 token=Token{361c446 ActivityRecord{6d5b688 u0 com.miui.home/.launcher.Launcher t1}}}
53isVisible=false
54# 壁纸,类似透明板(动态壁纸)
55Window #13 Window{f63516 u0 com.android.keyguard.wallpaper.service.MiuiKeyguardLiveWallpaper}:
56mBaseLayer=11000 mSubLayer=0    mToken=WallpaperWindowToken{f661030 token=android.os.BinderProxy@3481973}
57isVisible=false
58# 壁纸
59Window #14 Window{cf10cf2 u0 com.android.systemui.ImageWallpaper}:
60mBaseLayer=11000 mSubLayer=0    mToken=WallpaperWindowToken{5182c91 token=android.os.Binder@2b356b8}
61isVisible=false

这里的Window数字越小,那么显示距离屏幕越近。

2.3.1Window类别

  1. Application Window: Activity就是一个典型的应用程序窗口

  2. Sub Window: 子窗口, 顾名思义, 它不能独立存在, 需要附着在其他窗口才可以, PopupWindow就属于子窗口

  3. System Window: 输入法窗口、 系统音量条窗口、系统错误窗口都属于系统窗口

Application Window【type 取值范围 [0,999]】

应用程序窗口,主要是应用中的出现的Window,也就是对应我们说的Activity。

1//frameworks/base/core/java/android/view/WindowManager.java
2public static final int FIRST_APPLICATION_WINDOW = 1;
3public static final int TYPE_BASE_APPLICATION   = 1;
4public static final int TYPE_APPLICATION        = 2;
5public static final int TYPE_APPLICATION_STARTING = 3;
6public static final int TYPE_DRAWN_APPLICATION = 4;
7public static final int LAST_APPLICATION_WINDOW = 99;

Sub window【type 取值范围 [1000,1999]】

不能独立存在,需依附其他窗口的Window,也就是Subwindow可以依赖于Application Window或System Window,也可以依赖于Subwindow,可以存在嵌套的子窗口。实际上学习完Window类别和层次关系,可以知道其实是两个Window属于同一个WindowToken容器,距离谷歌浏览器中打开右上角「设置」,「设置」属于子窗口。

1//frameworks/base/core/java/android/view/WindowManager.java
2public static final int FIRST_SUB_WINDOW = 1000;
3public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
4public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
5public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
6public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
7public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW + 4;
8public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
9public static final int LAST_SUB_WINDOW = 1999;

System Window【type 取值范围 [2000,2999]】

如 Toast,ANR 窗口,输入法,StatusBar,NavigationBar 等。

1//frameworks/base/core/java/android/view/WindowManager.java
2public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;
3public static final int TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1;
4public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;
5...
6public static final int TYPE_STATUS_BAR_ADDITIONAL = FIRST_SYSTEM_WINDOW + 41;
7public static final int LAST_SYSTEM_WINDOW      = 2999;

type 值越大,层级越高, 这个观点是不完全正确的

相对而言,确实type值越大会越接近屏幕,但是相同type或者是存在SubWindow情况,用Type是无法满足的。

因此,引入Window层级,层级越高,最终在屏幕上显示时就越靠近用户

2.3.2Window层次关系

首先,先介绍一下Window容器中的WindowToken和WindowState

WindowToken

WindowToken理解成是一个显示令牌,无论是系统窗口还是应用窗口,添加新的窗口的时候必须使用这个令牌向WMS表明自己的身份,添加窗口的时候会创建WindowToken,销毁窗口的时候移除WindowToken(removeWindowToken方法)。

WMS使用WindowToken将同一个应用组件(Activity,InputMethod,Wallpaper,Dream)的窗口组织在一起,换句话说,每一个窗口都会对应一个WindowToken,并且这个窗口中的所有子窗口将会对应同一个WindowToken,这些窗口的WindowToken都是一样的。

WindowState

WindowState用于窗口管理,这个是WMS中事实的窗口,包含了一个窗口的所有的属性,WindowState对象都存放在mWindowMap里面,mWindowMap是所有窗口的一个全集,如果梳理WindowState的一些增加、移动和删除等操作,会更加理解这个类。举一个例子,对于Activity而言,我们看到的App的中的Activity就是一个个Window;对于系统WMS服务而言,这些Activity一个个对应的Window实际上是WindowState。

为了更好地说清楚层次关系,需要引入两个变量

1//frameworks/base/services/core/java/com/android/server/wm/WindowState.java
2class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState,
3InsetsControlTarget, InputTarget {
4    final int mBaseLayer;//主要层级
5    final int mSubLayer;//子层级
6}

2.3.2.1层级顺序

先看主要层级的顺序,范围是[1, 36]

  1//frameworks/base/services/core/java/com/android/server/policy/WindowManagerPolicy.java
  2default int getWindowLayerFromTypeLw(int type) {
  3    if (isSystemAlertWindowType(type)) {
  4        throw new IllegalArgumentException("Use getWindowLayerFromTypeLw() or"
  5                                           + " getWindowLayerLw() for alert window types");
  6    }
  7    return getWindowLayerFromTypeLw(type, false /* canAddInternalSystemWindow */);
  8}
  9//这里数字越大,层级越大,最小的是1,最大是36
 10default int getWindowLayerFromTypeLw(int type, boolean canAddInternalSystemWindow,
 11                                     boolean roundedCornerOverlay) {
 12    // Always put the rounded corner layer to the top most.
 13    if (roundedCornerOverlay && canAddInternalSystemWindow) {
 14        return getMaxWindowLayer();//这里是36,android版本不一样大小不一样,层级顺序也不一样
 15    }
 16    //这里先判断type是否是APPLICATION Window中的,如果是返回2
 17    if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
 18        return APPLICATION_LAYER;
 19    }
 20
 21    switch (type) {
 22        //壁纸层级最低
 23        case TYPE_WALLPAPER:
 24            return  1;
 25        case TYPE_PRESENTATION:
 26        case TYPE_PRIVATE_PRESENTATION:
 27        case TYPE_DOCK_DIVIDER:
 28        case TYPE_QS_DIALOG:
 29        case TYPE_PHONE:
 30            return  3;
 31        case TYPE_SEARCH_BAR:
 32            return  4;
 33        case TYPE_INPUT_CONSUMER:
 34            return  5;
 35        case TYPE_SYSTEM_DIALOG:
 36            return  6;
 37        case TYPE_TOAST:
 38            // toasts and the plugged-in battery thing
 39            return  7;
 40        case TYPE_PRIORITY_PHONE:
 41            // SIM errors and unlock.  Not sure if this really should be in a high layer.
 42            return  8;
 43        case TYPE_SYSTEM_ALERT:
 44            // like the ANR / app crashed dialogs
 45            // Type is deprecated for non-system apps. For system apps, this type should be
 46            // in a higher layer than TYPE_APPLICATION_OVERLAY.
 47            return  canAddInternalSystemWindow ? 12 : 9;
 48        case TYPE_APPLICATION_OVERLAY:
 49            return  11;
 50        case TYPE_INPUT_METHOD:
 51            // on-screen keyboards and other such input method user interfaces go here.
 52            return  13;
 53        case TYPE_INPUT_METHOD_DIALOG:
 54            // on-screen keyboards and other such input method user interfaces go here.
 55            return  14;
 56        case TYPE_STATUS_BAR:
 57            return  15;
 58        case TYPE_STATUS_BAR_ADDITIONAL:
 59            return  16;
 60        case TYPE_NOTIFICATION_SHADE:
 61            return  17;
 62        case TYPE_STATUS_BAR_SUB_PANEL:
 63            return  18;
 64        case TYPE_KEYGUARD_DIALOG:
 65            return  19;
 66        case TYPE_VOICE_INTERACTION_STARTING:
 67            return  20;
 68        case TYPE_VOICE_INTERACTION:
 69            // voice interaction layer should show above the lock screen.
 70            return  21;
 71        case TYPE_VOLUME_OVERLAY:
 72            // the on-screen volume indicator and controller shown when the user
 73            // changes the device volume
 74            return  22;
 75        case TYPE_SYSTEM_OVERLAY:
 76            // the on-screen volume indicator and controller shown when the user
 77            // changes the device volume
 78            return  canAddInternalSystemWindow ? 23 : 10;
 79        case TYPE_NAVIGATION_BAR:
 80            // the navigation bar, if available, shows atop most things
 81            return  24;
 82        case TYPE_NAVIGATION_BAR_PANEL:
 83            // some panels (e.g. search) need to show on top of the navigation bar
 84            return  25;
 85        case TYPE_SCREENSHOT:
 86            // screenshot selection layer shouldn't go above system error, but it should cover
 87            // navigation bars at the very least.
 88            return  26;
 89        case TYPE_SYSTEM_ERROR:
 90            // system-level error dialogs
 91            return  canAddInternalSystemWindow ? 27 : 9;
 92        case TYPE_MAGNIFICATION_OVERLAY:
 93            // used to highlight the magnified portion of a display
 94            return  28;
 95        case TYPE_DISPLAY_OVERLAY:
 96            // used to simulate secondary display devices
 97            return  29;
 98        case TYPE_DRAG:
 99            // the drag layer: input for drag-and-drop is associated with this window,
100            // which sits above all other focusable windows
101            return  30;
102        case TYPE_ACCESSIBILITY_OVERLAY:
103            // overlay put by accessibility services to intercept user interaction
104            return  31;
105        case TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY:
106            return 32;
107        case TYPE_SECURE_SYSTEM_OVERLAY:
108            return  33;
109        case TYPE_BOOT_PROGRESS:
110            return  34;
111        case TYPE_POINTER:
112            // the (mouse) pointer layer
113            return  35;
114        default:
115            Slog.e("WindowManager", "Unknown window type: " + type);
116            return 3;
117    }
118}

接着看先看次层级的顺序,范围是[-2, 3]

 1//frameworks/base/services/core/java/com/android/server/policy/WindowManagerPolicy.java
 2default int getSubWindowLayerFromTypeLw(int type) {
 3    switch (type) {
 4        case TYPE_APPLICATION_PANEL:
 5        case TYPE_APPLICATION_ATTACHED_DIALOG:
 6            return APPLICATION_PANEL_SUBLAYER;// 1
 7        case TYPE_APPLICATION_MEDIA:
 8            return APPLICATION_MEDIA_SUBLAYER;// -2
 9        case TYPE_APPLICATION_MEDIA_OVERLAY:
10            return APPLICATION_MEDIA_OVERLAY_SUBLAYER;// -1
11        case TYPE_APPLICATION_SUB_PANEL:
12            return APPLICATION_SUB_PANEL_SUBLAYER;// 2
13        case TYPE_APPLICATION_ABOVE_SUB_PANEL:
14            return APPLICATION_ABOVE_SUB_PANEL_SUBLAYER;// 3
15    }
16    Slog.e("WindowManager", "Unknown sub-window type: " + type);
17    return 0;
18}

2.3.2.2Window层级计算

在WindowState的构造函数中会对Window层级计算

 1//frameworks/base/services/core/java/com/android/server/wm/WindowState.java
 2//将阈值扩大 10000 倍,系统中可能存在相同类型的窗口有很多
 3int TYPE_LAYER_MULTIPLIER = 10000;
 4int TYPE_LAYER_OFFSET = 1000;
 5
 6WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
 7            WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
 8            int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow,
 9            PowerManagerWrapper powerManagerWrapper) {
10    ...
11    //如果是子窗口走这里
12    if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW) {
13        mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)
14            * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
15        mSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);
16        mIsChildWindow = true;
17
18        mLayoutAttached = mAttrs.type !=
19            WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
20        mIsImWindow = parentWindow.mAttrs.type == TYPE_INPUT_METHOD
21            || parentWindow.mAttrs.type == TYPE_INPUT_METHOD_DIALOG;
22        mIsWallpaper = parentWindow.mAttrs.type == TYPE_WALLPAPER;
23    } else {
24        //如果是非子窗口走这里
25        mBaseLayer = mPolicy.getWindowLayerLw(this)
26            * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
27        mSubLayer = 0;
28        mIsChildWindow = false;
29        mLayoutAttached = false;
30        mIsImWindow = mAttrs.type == TYPE_INPUT_METHOD
31            || mAttrs.type == TYPE_INPUT_METHOD_DIALOG;
32        mIsWallpaper = mAttrs.type == TYPE_WALLPAPER;
33    }
34    ...
35}

如 Activity,type 是 TYPE_BASE_APPLICATION ,getWindowLayerLw 计算返回 APPLICATION_LAYER(2),mBaseLayer = 2 * 10000 + 1000 = 21000

2.3.2.3Window 排序

主Window排序

 1//frameworks/base/services/core/java/com/android/server/wm/WindowToken.java
 2private final Comparator<WindowState> mWindowComparator =
 3    (WindowState newWindow, WindowState existingWindow) -> {
 4    final WindowToken token = WindowToken.this;
 5    if (newWindow.mToken != token) {
 6        throw new IllegalArgumentException("newWindow=" + newWindow
 7                                           + " is not a child of token=" + token);
 8    }
 9
10    if (existingWindow.mToken != token) {
11        throw new IllegalArgumentException("existingWindow=" + existingWindow
12                                           + " is not a child of token=" + token);
13    }
14    //判断新的Window和原来的Window关系,符合要求新Window会放在原来Window上面
15    return isFirstChildWindowGreaterThanSecond(newWindow, existingWindow) ? 1 : -1;
16};
17
18protected boolean isFirstChildWindowGreaterThanSecond(WindowState newWindow,
19            WindowState existingWindow) {
20        // New window is considered greater if it has a higher or equal base layer.
21        return newWindow.mBaseLayer >= existingWindow.mBaseLayer;
22}

按照 mBaseLayer 大小排序,如果是新插入的,且相等,放在上方

子Window排序

 1//frameworks/base/services/core/java/com/android/server/wm/WindowState.java
 2private static final Comparator<WindowState> sWindowSubLayerComparator =
 3    new Comparator<WindowState>() {
 4    @Override
 5    public int compare(WindowState w1, WindowState w2) {
 6        final int layer1 = w1.mSubLayer;
 7        final int layer2 = w2.mSubLayer;
 8        if (layer1 < layer2 || (layer1 == layer2 && layer2 < 0 )) {
 9            //w1是原来的sublayer,w2是新的子Window
10            //如果新的sublayer大,那么放在原来子Window上面
11            //如果新的sublayer相等,且sublayer小于0,放到原来子Window下面
12            //如果新的sublayer相等且为正值,放在原来子Window上面
13            return -1;
14        }
15        return 1;
16    };
17};

以上面的例子来具体说明

下图红色部分才是展示出来的Window叠加起来真正出现的手机画面

用demo来进一步理解运算逻辑

2.4WindowContainer

Android窗口是根据显示屏幕来管理,每个显示屏幕的窗口层级分为36层,1-36层。每层可以放置多个窗口,上层窗口覆盖下面的。要理解窗口的结构,需要学习下WindowContainer、RootWindowContainer、DisplayContent、TaskDisplayArea、Task、ActivityRecord、WindowToken、WindowState、WindowContainer等类。

2.4.1Window容器架构和继承关系图

2.4.1.1Window容器架构

android11和android13两个版本架构差异比较大,所以为了更好地理解,将分别展示出两个版本的架构

android11 Window容器架构图

android13 Window容器架构图

2.4.1.2Window容器继承关系

android11 Window容器继承关系图

android13 Window容器继承关系图

2.4.2源码解析Window容器

2.4.2.1WindowContainer

Window容器的源头,为直接包含窗口或者通过孩子层级形式包含窗口的类,定义了普遍功能。它作为基类被继承,像RootWindowContainer、DisplayContent、TaskDisplayArea、Task、ActivityRecord、WindowToken、WindowState都是直接或间接的继承该类。

1//frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java
2class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E>
3    implements Comparable<WindowContainer>, Animatable, SurfaceFreezer.Freezable,
4InsetsControlTarget {
5    //mParent是当前WindowContainer容器的父容器
6    private WindowContainer mParent = null;
7    //mChildren是一个继承WindowContainer的集合对象,说明当前WindowContainer可能会有好几个子容器
8    protected final WindowList<E> mChildren = new WindowList<E>();
9};

对于WindowContainer需要了解两个成员变量,mParent和mChildren,一个是容器本身的父容器,另一个是容器的子容器(类似上有老下有小的概念,老人只能有一个,小孩可以是多个)

2.4.2.2窗口结构层级相关类

从Android12开始引入Window的特色模式,更好地管理Window的各个层级相关类

  • RootWindowContainer:根窗口容器,树的根是它。通过它遍历寻找,可以找到窗口树上的窗口。它的孩子是DisplayContent。

  • DisplayContent:该类是对应着显示屏幕的,Android是支持多屏幕的,所以可能存在多个DisplayContent对象。上图只画了一个对象的结构,其他对象的结构也是和画的对象的结构是相似的。

  • DisplayArea:该类是对应着显示屏幕下面的,代表一组窗口合集,具有多个子类,如Tokens,TaskDisplayArea等

  • TaskDisplayArea:它为DisplayContent的孩子,对应着窗口层次的第2层。第2层作为应用层,看它的定义:int APPLICATION_LAYER = 2,应用层的窗口是处于第2层。TaskDisplayArea的孩子是Task类,其实它的孩子类型也可以是TaskDisplayArea。而Task的孩子则可以是ActivityRecord,也可以是Task。

  • Tokens:代表专门包含WindowTokens的容器,它的孩子是WindowToken,而WindowToken的孩子则为WindowState对象。

  • ImeContainer:它是输入法窗口的容器,它的孩子是WindowToken类型。WindowToken的孩子为WindowState类型,而WindowState类型则对应着输入法窗口。

  • Task:任务,它的孩子可以是Task,也可以是ActivityRecord类型。

  • ActivityRecord:是对应着应用进程中的Activity的。ActivityRecord是继承WindowToken的,它的孩子类型为WindowState。

  • WindowState:WindowState是对应着一个窗口的。

上面的文字介绍可能会懵,没关系,先了解一下大概,下面会一一介绍内容。

2.4.2.3RootWindowContainer

1)RootWindowContainer构造

RootWindowContainer的构造是在WindowManagerService中,其中RootWindowContainer对象作为WindowManagerService的成员变量mRoot 存在。

 1//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
 2public class WindowManagerService extends IWindowManager.Stub
 3    implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
 4    RootWindowContainer mRoot;
 5    private WindowManagerService(Context context, InputManagerService inputManager,
 6                                 boolean showBootMsgs, boolean onlyCore, WindowManagerPolicy policy,
 7                                 ActivityTaskManagerService atm, DisplayWindowSettingsProvider
 8                                 displayWindowSettingsProvider, Supplier<SurfaceControl.Transaction>                                          transactionFactory,Supplier<Surface> surfaceFactory,
 9                                 Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory) {
10        ...
11        mRoot = new RootWindowContainer(this);
12        mDisplayAreaPolicyProvider = DisplayAreaPolicy.Provider.fromResources(
13                mContext.getResources());
14        ...
15    }
16}

2)setWindowManager

在ActivityTaskManagerService类中,会调用setWindowManager(WindowManagerService wm)方法

 1//frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
 2public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
 3    RootWindowContainer mRootWindowContainer;
 4    public void setWindowManager(WindowManagerService wm) {
 5        synchronized (mGlobalLock) {
 6            mWindowManager = wm;
 7            mRootWindowContainer = wm.mRoot;
 8            mRootWindowContainer.setWindowManager(wm);
 9            ...
10        }
11    }
12}

ActivityTaskManagerService的成员变量mRootWindowContainer 也赋值为RootWindowContainer根对象。最后会调用RootWindowContainer的setWindowManager(wm)方法

 1//frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
 2class RootWindowContainer extends WindowContainer<DisplayContent>
 3    implements DisplayManager.DisplayListener {
 4    void setWindowManager(WindowManagerService wm) {
 5        //这里的mWindowManager指的是WindowManagerservice的实例
 6        mWindowManager = wm;
 7        //这里的mDisplayManager对应DisplayManagerService服务
 8        mDisplayManager = mService.mContext.getSystemService(DisplayManager.class);
 9        mDisplayManager.registerDisplayListener(this, mService.mUiHandler);
10        mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
11        //这里的Display是屏幕,包括虚拟屏幕,默认只有一个屏幕
12        final Display[] displays = mDisplayManager.getDisplays();//1
13        for (int displayNdx = 0; displayNdx < displays.length; ++displayNdx) {
14            final Display display = displays[displayNdx];
15            final DisplayContent displayContent = new DisplayContent(display, this);
16            addChild(displayContent, POSITION_BOTTOM);//2
17            if (displayContent.mDisplayId == DEFAULT_DISPLAY) {
18                mDefaultDisplay = displayContent;//3
19            }
20        }
21        //添加Home Task,本文不作讨论
22        final TaskDisplayArea defaultTaskDisplayArea = getDefaultTaskDisplayArea();
23        defaultTaskDisplayArea.getOrCreateRootHomeTask(ON_TOP);//4
24        positionChildAt(POSITION_TOP, defaultTaskDisplayArea.mDisplayContent,
25                        false /* includingParents */);//5
26    }
27}

这里只介绍Window层级关系,setWindowManager后边的添加Home Task不在本文讨论,详情可以看这里

  1. 通过屏幕管理对象mDisplayManager得到所有的显示屏幕,然后构造DisplayContent对象
  2. 通过addChild方法将DisplayContent对象添加到RootWindowContainer根对象的树状结构中
  3. 默认显示屏幕的mDisplayId 是DEFAULT_DISPLAY,mDisplayId 为0作为基本显示屏幕
  4. 获取默认屏幕的TaskDisplayArea,创建一个根Home任务
  5. 最后把默认屏幕放在RootWindowContainer根对象的孩子的最上面

2.4.2.4DisplayContent

源码的内容比较多,这里只挑选部分Window容器源码部分

 1//frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
 2class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.DisplayContentInfo {
 3    final ActivityTaskManagerService mAtmService;
 4    DisplayContent(Display display, RootWindowContainer root) {
 5        //DisplayContent继承于RootDisplayArea
 6        super(root.mWindowManager, "DisplayContent", FEATURE_ROOT);
 7        ...
 8        mRootWindowContainer = root;
 9        //mAtmService是ActivityTaskManagerService实例
10        mAtmService = mWmService.mAtmService;
11        mDisplay = display;
12        mDisplayPolicy = new DisplayPolicy(mWmService, this);
13        final Transaction pendingTransaction = getPendingTransaction();
14        configureSurfaces(pendingTransaction);
15        pendingTransaction.apply();
16        // Sets the display content for the children.
17        setWindowingMode(WINDOWING_MODE_FULLSCREEN);
18        mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
19        ...
20    }
21}

其中configureSurfaces是窗口构建的部分

 1//frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
 2//这个是输入法Window容器
 3private final ImeContainer mImeWindowsContainer = new ImeContainer(mWmService);
 4private void configureSurfaces(Transaction transaction) {
 5    if (mDisplayAreaPolicy == null) {
 6        //设置策略并构建显示区域层次结构,只有在创建Surface之后才能构建层次结构,这样才能正确地重新生成
 7        //这里的mWmService是wms
 8        mDisplayAreaPolicy = mWmService.getDisplayAreaPolicyProvider().instantiate(
 9            mWmService, this /* content */, this /* root */,
10            mImeWindowsContainer);
11    }
12    ...
13}

2.4.2.5DisplayAreaPolicy

1//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
2DisplayAreaPolicy.Provider getDisplayAreaPolicyProvider() {
3    return mDisplayAreaPolicyProvider;
4}

这里的mDisplayAreaPolicyProvider实际上是上面2.4.2.3RootWindowContainer构造的时候生成的,DisplayAreaPolicy.Provider.fromResources

 1//frameworks/base/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
 2public abstract class DisplayAreaPolicy {
 3    protected final WindowManagerService mWmService;
 4    //这个mRoot会在后续的内容中用到
 5    protected final RootDisplayArea mRoot;
 6    public interface Provider {
 7        DisplayAreaPolicy instantiate(WindowManagerService wmService, DisplayContent content,
 8                                      RootDisplayArea root, DisplayArea.Tokens imeContainer);
 9        static Provider fromResources(Resources res) {
10            //这个name在/frameworks/base/core/res/res/values/config.xml中定义,里面为空
11            String name = res.getString(
12                com.android.internal.R.string.config_deviceSpecificDisplayAreaPolicyProvider);
13            if (TextUtils.isEmpty(name)) {
14                return new DisplayAreaPolicy.DefaultProvider();
15            }
16            //上面的name定义了的话用反射方式来实例化一个Provider
17            try {
18                return (Provider) Class.forName(name).newInstance();
19            } catch (ReflectiveOperationException | ClassCastException e) {
20                throw new IllegalStateException("Couldn't instantiate class " + name
21                                                + " for config_deviceSpecificDisplayAreaPolicyProvider:"
22                                                + " make sure it has a public zero-argument constructor"
23                                                + " and implements DisplayAreaPolicy.Provider", e);
24            }
25        }
26    }
27}

可以看到mDisplayAreaPolicyProvider 的具体类型是可以在系统资源文件中配置的,资源字符串属性为config_deviceSpecificDisplayAreaPolicyProvider,如果为空,类型则为DefaultProvider类型。目前查看,资源文件里没有配置字符值。所以mDisplayAreaPolicyProvider类型则为DefaultProvider类型。

1<!-- /frameworks/base/core/res/res/values/config.xml -->
2<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
3    ...
4    <string translatable="false" name="config_deviceSpecificDisplayAreaPolicyProvider"></string>
5    ...
6</resources>

上面的mDisplayAreaPolicy实际上就是new DisplayAreaPolicy.DefaultProvider().instantiate

 1//frameworks/base/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
 2public abstract class DisplayAreaPolicy {
 3    static final class DefaultProvider implements DisplayAreaPolicy.Provider {
 4        @Override
 5        public DisplayAreaPolicy instantiate(WindowManagerService wmService,
 6                                             DisplayContent content, RootDisplayArea root,
 7                                             DisplayArea.Tokens imeContainer) {
 8            final TaskDisplayArea defaultTaskDisplayArea = new TaskDisplayArea(content, wmService,
 9                                                                               "DefaultTaskDisplayArea", FEATURE_DEFAULT_TASK_CONTAINER);//1
10            final List<TaskDisplayArea> tdaList = new ArrayList<>();
11            tdaList.add(defaultTaskDisplayArea);
12            //在整个逻辑显示的根下定义将被支持的特性。该策略将基于此构建DisplayArea层次结构
13            //这个特性就是Android12引入的特性,这里的root是上面传入的当前的DisplayContent的this指针
14            final HierarchyBuilder rootHierarchy = new HierarchyBuilder(root);//2
15            // 设置基本容器(即使显示器不支持输入法)。
16            rootHierarchy.setImeContainer(imeContainer).setTaskDisplayAreas(tdaList);//2
17            if (content.isTrusted()) {
18                //只有信任的显示可以有特色模式建造
19                configureTrustedHierarchyBuilder(rootHierarchy, wmService, content);//3
20            }
21            // 使用上面定义的层次结构实例化策略,这将创建并附加所有必要的显示区域到根目录
22            return new DisplayAreaPolicyBuilder().setRootHierarchy(rootHierarchy).build(wmService);//4
23        }
24    }
25}
  1. 新建一个TaskDisplayArea 实例defaultTaskDisplayArea
  2. 接着创建HierarchyBuilder 实例, 将输入法窗口容器和defaultTaskDisplayArea 都设置到它的成员变量里面
  3. 接着判断显示屏是否是可信任的,主要是添加一些特色模式
  4. 采用建造者模式新建DisplayAreaPolicyBuilder对象,返回的是一个DisplayAreaPolicy实例,即Result对象

1)构造TaskDisplayArea

 1//frameworks/base/services/core/java/com/android/server/wm/TaskDisplayArea.java
 2//这里传入的name为DefaultTaskDisplayArea
 3final class TaskDisplayArea extends DisplayArea<WindowContainer> {
 4    TaskDisplayArea(DisplayContent displayContent, WindowManagerService service, String name,
 5                    int displayAreaFeature) {
 6        this(displayContent, service, name, displayAreaFeature, false /* createdByOrganizer */,
 7             true /* canHostHomeTask */);
 8    }
 9
10    TaskDisplayArea(DisplayContent displayContent, WindowManagerService service, String name,
11                    int displayAreaFeature, boolean createdByOrganizer,
12                    boolean canHostHomeTask) {
13        super(service, Type.ANY, name, displayAreaFeature);
14        mDisplayContent = displayContent;
15        mRootWindowContainer = service.mRoot;
16        mAtmService = service.mAtmService;
17        mCreatedByOrganizer = createdByOrganizer;
18        mCanHostHomeTask = canHostHomeTask;
19    }
20}

2)构造HierarchyBuilder

Android12引入的特色模式

 1//frameworks/base/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
 2class DisplayAreaPolicyBuilder {
 3    static class HierarchyBuilder {
 4        //这里只有三种Token类型,TASK,IME和TOKENS
 5        private static final int LEAF_TYPE_TASK_CONTAINERS = 1;
 6        private static final int LEAF_TYPE_IME_CONTAINERS = 2;
 7        private static final int LEAF_TYPE_TOKENS = 0;
 8        //这个是根Root容器
 9        private final RootDisplayArea mRoot;
10        private final ArrayList<DisplayAreaPolicyBuilder.Feature> mFeatures = new ArrayList<>();
11        private final ArrayList<TaskDisplayArea> mTaskDisplayAreas = new ArrayList<>();
12        @Nullable
13        private DisplayArea.Tokens mImeContainer;
14        //这里构造传入的root实际上是DisplayContent的实例对象
15        HierarchyBuilder(RootDisplayArea root) {
16            mRoot = root;
17        }
18
19        HierarchyBuilder addFeature(DisplayAreaPolicyBuilder.Feature feature) {
20            mFeatures.add(feature);
21            return this;
22        }
23
24        HierarchyBuilder setTaskDisplayAreas(List<TaskDisplayArea> taskDisplayAreas) {
25            mTaskDisplayAreas.clear();
26            mTaskDisplayAreas.addAll(taskDisplayAreas);
27            return this;
28        }
29
30        HierarchyBuilder setImeContainer(DisplayArea.Tokens imeContainer) {
31            mImeContainer = imeContainer;
32            return this;
33        }
34
35        private void build() {
36            build(null /* displayAreaGroupHierarchyBuilders */);
37        }
38        ...
39    }
40}

3)特色模式建造

可以看到这里实际上还是特色模式的构建,其中里面传入的参数是Feature.Builder,显然又用到了建造者的设计模式

 1//frameworks/base/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
 2private void configureTrustedHierarchyBuilder(HierarchyBuilder rootHierarchy,
 3                                              WindowManagerService wmService, DisplayContent content) {
 4    rootHierarchy.addFeature(new Feature.Builder(wmService.mPolicy, "WindowedMagnification",
 5                                                 FEATURE_WINDOWED_MAGNIFICATION)
 6                             .upTo(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY)
 7                             .except(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY)
 8                             // Make the DA dimmable so that the magnify window also mirrors the dim layer.
 9                             .setNewDisplayAreaSupplier(DisplayArea.Dimmable::new)
10                             .build());
11    if (content.isDefaultDisplay) {
12        rootHierarchy.addFeature(new Feature.Builder(wmService.mPolicy, "HideDisplayCutout",
13                                                     FEATURE_HIDE_DISPLAY_CUTOUT)
14                                 .all()
15                                 .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL, TYPE_STATUS_BAR,
16                                         TYPE_NOTIFICATION_SHADE)
17                                 .build())
18            .addFeature(new Feature.Builder(wmService.mPolicy, "OneHanded",
19                                            FEATURE_ONE_HANDED)
20                        .all()
21                        .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL,
22                                TYPE_SECURE_SYSTEM_OVERLAY)
23                        .build());
24    }
25    rootHierarchy
26        .addFeature(new Feature.Builder(wmService.mPolicy, "FullscreenMagnification",
27                                        FEATURE_FULLSCREEN_MAGNIFICATION)
28                    .all()
29                    .except(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, TYPE_INPUT_METHOD,
30                            TYPE_INPUT_METHOD_DIALOG, TYPE_MAGNIFICATION_OVERLAY,
31                            TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL)
32                    .build())
33        .addFeature(new Feature.Builder(wmService.mPolicy, "ImePlaceholder",
34                                        FEATURE_IME_PLACEHOLDER)
35                    .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG)
36                    .build());
37}

关于Feature.Builder,内部持有Builder,用于建造Feature类

Feature分类有5种,分别为"WindowedMagnification"、"HideDisplayCutout"、"OneHanded"、"FullscreenMagnification"、"ImePlaceholder"

4)根据上面5种Feature,具体显示区域到根目录

 1//frameworks/base/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
 2Result build(WindowManagerService wmService) {
 3    //让Window容器的特色模式生效
 4    validate();
 5    //在向组层次结构添加窗口之前,将DA组根附加到屏幕层次结构。
 6    mRootHierarchyBuilder.build(mDisplayAreaGroupHierarchyBuilders);//1
 7    List<RootDisplayArea> displayAreaGroupRoots = new ArrayList<>(
 8        mDisplayAreaGroupHierarchyBuilders.size());
 9    //这个size大小根据上文所知是5,然后构建
10    for (int i = 0; i < mDisplayAreaGroupHierarchyBuilders.size(); i++) {
11        HierarchyBuilder hierarchyBuilder = mDisplayAreaGroupHierarchyBuilders.get(i);
12        hierarchyBuilder.build();//2
13        displayAreaGroupRoots.add(hierarchyBuilder.mRoot);//2
14    }
15    // Use the default function if it is not specified otherwise.
16    if (mSelectRootForWindowFunc == null) {
17        mSelectRootForWindowFunc = new DefaultSelectRootForWindowFunction(
18            mRootHierarchyBuilder.mRoot, displayAreaGroupRoots);//3
19    }
20    return new Result(wmService, mRootHierarchyBuilder.mRoot, displayAreaGroupRoots,
21                      mSelectRootForWindowFunc, mSelectTaskDisplayAreaFunc);//4
22}
  1. 调用mRootHierarchyBuilder.build构造层级
  2. 这里没有使用到displayAreaGroupRoots,所以跳过2
  3. 如果mSelectRootForWindowFunc 没有设置值的话,设置一个默认的对象
  4. 新生成一个Result对象返回,即Result为DisplayAreaPolicy的实例类

2.4.2.6HierarchyBuilder.build

这个内容比较多,分成上下两段来说明

1)上半段

 1//frameworks/base/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
 2private void build(@Nullable List<HierarchyBuilder> displayAreaGroupHierarchyBuilders) {
 3    final WindowManagerPolicy policy = mRoot.mWmService.mPolicy;
 4    final int maxWindowLayerCount = policy.getMaxWindowLayer() + 1;
 5    final DisplayArea.Tokens[] displayAreaForLayer =
 6        new DisplayArea.Tokens[maxWindowLayerCount];
 7    final Map<Feature, List<DisplayArea<WindowContainer>>> featureAreas =
 8        new ArrayMap<>(mFeatures.size());
 9    for (int i = 0; i < mFeatures.size(); i++) {
10        featureAreas.put(mFeatures.get(i), new ArrayList<>());
11    }
12
13    PendingArea[] areaForLayer = new PendingArea[maxWindowLayerCount];
14    final PendingArea root = new PendingArea(null, 0, null);
15    Arrays.fill(areaForLayer, root);
16
17    // 创建显示区域以覆盖所有已定义的功能
18    final int size = mFeatures.size();
19    for (int i = 0; i < size; i++) {
20        final Feature feature = mFeatures.get(i);
21        PendingArea featureArea = null;
22        for (int layer = 0; layer < maxWindowLayerCount; layer++) {
23            if (feature.mWindowLayers[layer]) {
24                // 该特性将应用于该窗口层。
25                //我们需要为它找到一个显示区域:
26                //我们可以重用现有的一个,如果它是为前一层的这个特性创建的
27                //并且应用到前一层的最后一个特性与应用到当前层的特性相同(所以它们可以共享相同的父DisplayArea)
28                if (featureArea == null || featureArea.mParent != areaForLayer[layer]) {
29                    // 没有合适的显示区域:
30                    //在之前的区域下创建一个新的图层(作为父层)。
31                    featureArea = new PendingArea(feature, layer, areaForLayer[layer]);
32                    areaForLayer[layer].mChildren.add(featureArea);
33                }
34                areaForLayer[layer] = featureArea;
35            } else {
36                featureArea = null;
37            }
38        }
39    }
40    ...
41}

根据上面的Feature类,创建对应的联系

对应的PendingArea结构

第一个循环

第二个循环

为了更加方便的理解,简化上述过程,用PA代表PendingArea的实例对象,最终上半段的结构如下所示

2)下半段

 1//frameworks/base/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
 2private void build(@Nullable List<HierarchyBuilder> displayAreaGroupHierarchyBuilders) {
 3    ...
 4    // Create Tokens as leaf for every layer.
 5    PendingArea leafArea = null;
 6    int leafType = LEAF_TYPE_TOKENS;
 7    for (int layer = 0; layer < maxWindowLayerCount; layer++) {
 8        int type = typeOfLayer(policy, layer);
 9        // Check whether we can reuse the same Tokens with the previous layer. This happens
10        // if the previous layer is the same type as the current layer AND there is no
11        // feature that applies to only one of them.
12        if (leafArea == null || leafArea.mParent != areaForLayer[layer]
13            || type != leafType) {
14            // Create a new Tokens for this layer.
15            leafArea = new PendingArea(null /* feature */, layer, areaForLayer[layer]);
16            areaForLayer[layer].mChildren.add(leafArea);
17            leafType = type;
18            if (leafType == LEAF_TYPE_TASK_CONTAINERS) {
19                // We use the passed in TaskDisplayAreas for task container type of layer.
20                // Skip creating Tokens even if there is no TDA.
21                addTaskDisplayAreasToApplicationLayer(areaForLayer[layer]);
22                addDisplayAreaGroupsToApplicationLayer(areaForLayer[layer],
23                                                       displayAreaGroupHierarchyBuilders);
24                leafArea.mSkipTokens = true;
25            } else if (leafType == LEAF_TYPE_IME_CONTAINERS) {
26                // We use the passed in ImeContainer for ime container type of layer.
27                // Skip creating Tokens even if there is no ime container.
28                leafArea.mExisting = mImeContainer;
29                leafArea.mSkipTokens = true;
30            }
31        }
32        leafArea.mMaxLayer = layer;
33    }
34    root.computeMaxLayer();
35
36    // 构建了一个PendingAreas树来表示层次结构,现在创建并将真正的DisplayAreas附加到根节点上
37    //这个root是PendingArea的实例,
38    root.instantiateChildren(mRoot, displayAreaForLayer, 0, featureAreas);
39
40    // 通知根节点我们已经完成了所有displayarea的附加。缓存所有与特性相关的集合,以便快速访问
41    // 这个mRoot是RootDisplayArea类型,传入的mRoot是上文传入的DisplayContent的this指针
42    mRoot.onHierarchyBuilt(mFeatures, displayAreaForLayer, featureAreas);
43}

下半段添加了一些叶子结点

再次简化如下

子节点的PendingArea对象树形结构构建完成,接着root.instantiateChildren构建窗口层级图

 1//frameworks/base/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
 2class DisplayAreaPolicyBuilder { 
 3    ...
 4    static class PendingArea {
 5        ...
 6        void instantiateChildren(DisplayArea<DisplayArea> parent, DisplayArea.Tokens[] areaForLayer,
 7                                 int level, Map<Feature, List<DisplayArea<WindowContainer>>> areas) {
 8            mChildren.sort(Comparator.comparingInt(pendingArea -> pendingArea.mMinLayer));
 9            for (int i = 0; i < mChildren.size(); i++) {
10                final PendingArea child = mChildren.get(i);
11                final DisplayArea area = child.createArea(parent, areaForLayer);
12                if (area == null) {
13                    // TaskDisplayArea and ImeContainer can be set at different hierarchy, so it can
14                    // be null.
15                    continue;
16                }
17                parent.addChild(area, WindowContainer.POSITION_TOP);
18                if (child.mFeature != null) {
19                    areas.get(child.mFeature).add(area);
20                }
21                child.instantiateChildren(area, areaForLayer, level + 1, areas);
22            }
23        }
24        
25        @Nullable
26        private DisplayArea createArea(DisplayArea<DisplayArea> parent,
27                DisplayArea.Tokens[] areaForLayer) {
28            if (mExisting != null) {
29                if (mExisting.asTokens() != null) {
30                    // Store the WindowToken container for layers
31                    fillAreaForLayers(mExisting.asTokens(), areaForLayer);
32                }
33                return mExisting;
34            }
35            if (mSkipTokens) {
36                return null;
37            }
38            DisplayArea.Type type;
39            if (mMinLayer > APPLICATION_LAYER) {
40                type = DisplayArea.Type.ABOVE_TASKS;
41            } else if (mMaxLayer < APPLICATION_LAYER) {
42                type = DisplayArea.Type.BELOW_TASKS;
43            } else {
44                type = DisplayArea.Type.ANY;
45            }
46            if (mFeature == null) {
47                final DisplayArea.Tokens leaf = new DisplayArea.Tokens(parent.mWmService, type,
48                        "Leaf:" + mMinLayer + ":" + mMaxLayer);
49                fillAreaForLayers(leaf, areaForLayer);
50                return leaf;
51            } else {
52                return mFeature.mNewDisplayAreaSupplier.create(parent.mWmService, type,
53                        mFeature.mName + ":" + mMinLayer + ":" + mMaxLayer, mFeature.mId);
54            }
55        }
56
57        private void fillAreaForLayers(DisplayArea.Tokens leaf, DisplayArea.Tokens[] areaForLayer) {
58            for (int i = mMinLayer; i <= mMaxLayer; i++) {
59                areaForLayer[i] = leaf;
60            }
61        }
62    }
63}

这里能看到PendingArea类的mExisting 和mSkipTokens属性的使用。

  • mExisting 存在直接就返回它,不会重新生成新的

    如果mExisting 不存在,并且mSkipTokens= true,这个时候,会返回null

    如果mExisting 不存在,并且mSkipTokens= false,则会新生成DisplayArea对象

  • 新生成的DisplayArea对象,如果mFeature 为null,则为DisplayArea.Tokens对象

    不然,则会调用生成具体的对象

  • 前面添加特色模式时,mNewDisplayAreaSupplier设置为DisplayArea.Dimmable::new

    如果不设置,它默认为DisplayArea::new

  • 我们还看到,新生成的DisplayArea对象的type,是根据PendingArea类的mMinLayer 和mMaxLayer 来决定

    如果新生成的DisplayArea对象是Tokens 类型,会填充参数areaForLayer数组,将对应层级的Tokens对象放入其中

2.4.2.7窗口层级分析WindowContainer相关子类关联

这里补充上面提到但没有讲述的Window容器,WindowState的容器,直接持有WindowState容器,WindowToken的容器,ActivityRecord的容器,Task的容器,DisplayArea相关容器,RootWindowContainer容器。

2.4.2.7.1WindowState的容器

直接持有WindowState的类,根据上面的android13 Window容器架构图可以初步得知,WindowToken、ActivityRecord和WallpaperWindowToken

2.4.2.7.2 WindowToken
1//frameworks/base/services/core/java/com/android/server/wm/WindowToken.java
2class WindowToken extends WindowContainer<WindowState> {}

WMS中存放一组相关联的窗口的容器。通常是ActivityRecord,它是Activity在WMS的表示,就如WindowState是Window在WMS的表示,用来显示窗口。

比如StatusBar:

1$ dumpsys activity containers
2#0 WindowToken{ef15750 android.os.BinderProxy@4562c02} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
3#0 d3cbd49 StatusBar type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
2.4.2.7.3 ActivityRecord
1//frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
2final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {}

ActivityRecord是WindowToken的子类,如上面所说,在WMS中一个ActivityRecord对象就代表一个Activity对象

这里有几点需要说明:

  1. 可以根据窗口的添加方式将窗口分为App窗口和非App窗口,或者换个说法,Activity窗口和非Activity窗口,我个人习惯叫App窗口和非App窗口。

    App窗口(Activity或者Dialog)父容器为ActivityRecord

    这类窗口由系统自动创建,不需要App主动去调用ViewManager.addView去添加一个窗口,比如我写一个Activity或者Dialog,系统就会在合适的时机为Activity或者Dialog调用ViewManager.addView去向WindowManager添加一个窗口。这类窗口在创建的时候,其父容器为ActivityRecord。

    非App窗口(NavigationBar)父容器为WindowToken

    这类窗口需要App主动去调用ViewManager.addView来添加一个窗口,比如NavigationBar窗口的添加,需要SystemUI主动去调用ViewManager.addView来为NavigationBar创建一个新的窗口。这类窗口在创建的时候,其父容器为WindowToken。

  2. WindowToken既然是WindowState的直接父容器,那么每次添加窗口的时候,就需要创建一个WindowToken,或者一个ActivityRecord。

  3. 上述情况也有例外,即存在WindowToken的复用,因为一个WindowToken中可能不只一个WindowState对象,说明第二个WindowState创建的时候,复用了第一个WindowState创建的时候生成的WindowToken。如果两个WindowState能被放进一个WindowToken中,那么这两个WindowState之间就必须有联系。

    在WMS.addWindow方法的部分片段:

 1//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
 2public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
 3                     int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
 4                     InputChannel outInputChannel, InsetsState outInsetsState,
 5                     InsetsSourceControl[] outActiveControls) {
 6    ...
 7    WindowToken token = displayContent.getWindowToken(
 8        hasParent ? parentWindow.mAttrs.token : attrs.token);
 9    ...
10}

可知,如果两个窗口的WindowManager.LayoutParams.token指向同一个对象,那么这两个WindowState就会被放入同一个WindowToken中。但是通常来说,只有由同一个Activity的两个窗口才有可能被放入到一个WindowToken中,比如典型的Launcher

1$ dumpsys activity containers
2#0 ActivityRecord{31ce9e6 u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity t191}
3#1 220afda com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity
4#0 4cf47c3 com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity

此外分别来自两个不同的Activity的两个窗口是不会被放入同一个WindowToken中的,因此更一般的情况是,一个WindowToken或ActivityRecord只包含一个WindowState。

2.4.2.7.4 WallpaperWindowToken
1//frameworks/base/services/core/java/com/android/server/wm/WallpaperWindowToken.java
2class WallpaperWindowToken extends WindowToken {}

其实WindowToken还有一个子类,WallpaperWindowToken,这类的WindowToken用来存放和Wallpaper相关的窗口。

1$ dumpsys activity containers
2#0 WallpaperWindowToken{dc5772f token=android.os.Binder@985f410} type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
3#0 1ea91d com.android.systemui.ImageWallpaper type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]

上面讨论ActivityRecord的时候,我们将窗口划分为App窗口和非App窗口。在引入了WallpaperWindowToken后,我们继续将非App窗口划分为两类,Wallpaper窗口和非Wallpaper窗口。

Wallpaper窗口的层级是比App窗口的层级低的,因此这里我们可以按照层级这一角度将窗口划分为:

  • App之上的窗口,父容器为WindowToken,如StatusBar和NavigationBar(这个层级大于2)
  • App窗口,父容器为ActivityRecord,如Launcher(这个层级为2)
  • App之下的窗口,父容器为WallpaperWindowToken,如ImageWallpaper窗口(这个层级为1)
2.4.2.7.5 WindowToken的容器 (DisplayArea.Tokens)

可以容纳WindowToken的容器,搜索之后,发现为定义在DisplayArea中的内部类Tokens。关于DisplayArea的内容放在2.4.2.7.10中

1//frameworks/base/services/core/java/com/android/server/wm/DisplayArea.java
2public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
3    public static class Tokens extends DisplayArea<WindowToken> {}
4}
2.4.2.7.6 ActivityRecord的容器 (Task)
1//frameworks/base/services/core/java/com/android/server/wm/Task.java
2class Task extends TaskFragment {}
3//frameworks/base/services/core/java/com/android/server/wm/TaskFragment.java
4class TaskFragment extends WindowContainer<WindowContainer> {}

关于Task相关

如果该应用没有Task存在(应用最近没有使用过),则会创建一个新的Task,并且该应用的“主”Activity 将会作为堆栈的根 Activity 打开。在当前 Activity 启动另一个 Activity 时,新的 Activity 将被推送到堆栈顶部并获得焦点。上一个 Activity 仍保留在堆栈中,但会停止。当 Activity 停止时,系统会保留其界面的当前状态。当用户按返回按 钮时,当前 Activity 会从堆栈顶部退出(该 Activity 销毁),上一个 Activity 会恢复(界面会恢复到上一个状态)。堆栈中的 Activity 永远不会重新排列,只会被送入和退出,在当前 Activity 启动时被送入堆栈,在用户使用返回按钮离开时从堆栈中退出。因此,返回堆栈按照“后进先出”的对象结构运作。图 1 借助一个时间轴直观地显示了这种行为。该时间轴显示了 Activity 之间的进展以及每个时间点的当前返回堆栈。

WMS中的Task类的作用和以上说明基本一致,用来管理ActivityRecord。

1$ dumpsys activity containers
2#6 Task=67 type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
3#0 ActivityRecord{cb9b141 u0 com.google.android.apps.messaging/.ui.ConversationListActivity t67} type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
4#0 d01e570 com.google.android.apps.messaging/com.google.android.apps.messaging.ui.ConversationListActivity type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]

接着再启动Message的另外一个界面

1$ dumpsys activity containers
2#6 Task=67 type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
3#1 ActivityRecord{77ab155 u0 com.google.android.apps.messaging/.conversation.screen.ConversationActivity t67} type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
4#0 e50283c com.google.android.apps.messaging/com.google.android.apps.messaging.conversation.screen.ConversationActivity type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
5#0 ActivityRecord{cb9b141 u0 com.google.android.apps.messaging/.ui.ConversationListActivity t67} type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
6#0 d01e570 com.google.android.apps.messaging/com.google.android.apps.messaging.ui.ConversationListActivity type=standard mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]

Task也支持嵌套的,从Task类的定义也可以看出来,Task的嵌套多用于多窗口模式,如分屏

1$ dumpsys activity containers
2#2 Task=5 type=standard mode=split-screen-primary override-mode=split-screen-primary requested-bounds=[0,0][720,770] bounds=[0,0][720,770]
3#0 Task=67 type=standard mode=split-screen-primary override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,770]
4#0 ActivityRecord{cb9b141 u0 com.google.android.apps.messaging/.ui.ConversationListActivity t67} type=standard mode=split-screen-primary override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,770]
5#0 d01e570 com.google.android.apps.messaging/com.google.android.apps.messaging.ui.ConversationListActivity type=standard mode=split-screen-primary override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,770]
2.4.2.7.7 Task的容器 (TaskDisplayArea)
1//frameworks/base/services/core/java/com/android/server/wm/TaskDisplayArea.java
2final class TaskDisplayArea extends DisplayArea<WindowContainer> {}

TaskDisplayArea,代表了屏幕上一块专门用来存放App窗口的区域。它的子容器可能是Task或者是TaskDisplayArea

2.4.2.7.8 DisplayArea容器
1//frameworks/base/services/core/java/com/android/server/wm/DisplayArea.java
2public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {}    

DisplayArea,是DisplayContent之下的对WindowContainer进行分组的容器。

DisplayArea受DisplayAreaPolicy管理,DisplayArea可以包含嵌套DisplayArea

DisplayArea有三种风格,用来保证窗口能够拥有正确的Z轴顺序:

  • BELOW_TASKS,只能包含Task之下的的DisplayArea和WindowToken
  • ABOVE_TASKS,只能包含Task之上的DisplayArea和WindowToken
  • ANY,能包含任何种类的DisplayArea、WindowToken或是Task容器

这里和我们之前分析WindowToken的时候逻辑是一样的,DisplayArea的子类有一个专门用来存放Task的容器类,TaskDisplayArea。层级高于TaskDisplayArea的DisplayArea,会被归类为ABOVE_TASKS,层级低于TaskDisplayArea则属于ABOVE_TASKS。

DisplayArea有三个直接子类,TaskDisplayAreaDisplayArea.TokensDisplayArea.Dimmable

2.4.2.7.9 TaskDisplayArea

2.4.2.7.7中已经列出了具体的类,TaskDisplayArea代表了屏幕上的一个包含App类型的WindowContainer的区域。它的子节点可以是Task,或者是TaskDisplayArea。目前在代码中,创建TaskDisplayArea的地方只有一处,即TaskDisplayArea存放Task的容器

在手机的近期任务列表界面将所有App都清掉后,查看一下此时的TaskDisplayArea情况

 1$ dumpsys activity containers
 2#0 DefaultTaskDisplayArea type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
 3#5 Task=1 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
 4#0 Task=191 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
 5#0 ActivityRecord{31ce9e6 u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity t191} type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
 6#1 220afda com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
 7#0 4cf47c3 com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
 8#4 Task=4 type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
 9#3 Task=3 type=undefined mode=split-screen-secondary override-mode=split-screen-secondary requested-bounds=[0,1498][1440,2960] bounds=[0,1498][1440,2960]
10#2 Task=2 type=undefined mode=split-screen-primary override-mode=split-screen-primary requested-bounds=[0,0][1440,1464] bounds=[0,0][1440,1464]
11#1 Task=5 type=undefined mode=multi-window override-mode=multi-window requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
12#0 Task=6 type=undefined mode=multi-window override-mode=multi-window requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]

正常来说,App端调用startActivity后,WMS会为新创建的ActivityRecord对象创建Task,用来存放这个ActivityRecord。当Task中的所有ActivityRecord都被移除后,这个Task就会被移除,就如Task#1,或者Task#191。但是对于Task#4之类的Task来说并不是这样。这些Task是WMS启动后就由TaskOrganizerController创建的,这些Task并没有和某一具体的App联系起来,因此当它里面的子WindowContainer被移除后,这个Task也不会被销毁,比如分屏Task#3和Task#2

2.4.2.7.10 DisplayArea.Tokens
1//frameworks/base/services/core/java/com/android/server/wm/DisplayArea.java
2public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
3    public static class Tokens extends DisplayArea<WindowToken> {}
4}

Tokens是DisplayArea的内部类,从其定义即可看出,它是一个只能包含WindowToken对象的DisplayArea类型的容器,那么其内部层级结构就相对简单,比如StatusBar

1$ dumpsys activity containers
2#0 Leaf:17:17 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
3#0 WindowToken{ef15750 android.os.BinderProxy@4562c02} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
4#0 d3cbd49 StatusBar type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
2.4.2.7.11DisplayArea.Dimmable
1//frameworks/base/services/core/java/com/android/server/wm/DisplayArea.java
2public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
3    static class Dimmable extends DisplayArea<DisplayArea> {}
4}

Dimmable也是DisplayArea的内部类,从名字可以看出,这类的DisplayArea可以添加模糊效果,并且Dimmable也是一个DisplayArea类型的DisplayArea容器。

它内部有一个Dimmer对象

1//frameworks/base/services/core/java/com/android/server/wm/DisplayArea.java
2private final Dimmer mDimmer = new Dimmer(this);

可以通过Dimmer对象施加模糊效果,模糊图层可以插入到以该Dimmable对象为根节点的层级结构之下的任意两个图层之间。

它有一个直接子类,RootDisplayArea。

2.4.2.7.12 DisplayContent.ImeContainer

DisplayArea.Tokens有一个子类DisplayContent.ImeContainer

1//frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
2class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.DisplayContentInfo {
3    private static class ImeContainer extends DisplayArea.Tokens {}
4}

DisplayContent的内部类,ImeContainer,是存放输入法窗口的容器。它继承的是DisplayArea.Tokens,说明它是一个只能存放WindowToken容器。

1$ dumpsys activity containers
2#0 ImeContainer type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
3#0 WindowToken{5e01794 android.os.Binder@579cfe7} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
4#0 4435738 InputMethod type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
2.4.2.7.13 RootDisplayArea
1//frameworks/base/services/core/java/com/android/server/wm/RootDisplayArea.java
2class RootDisplayArea extends DisplayArea.Dimmable {}

RootDisplayArea,是一个DisplayArea层级结构的根节点。

它可以是:

  • DisplayContent,作为整个屏幕的DisplayArea层级结构根节点。
  • DisplayAreaGroup,作为屏幕上部分区域对应的DisplayArea层级结构的根节点。

这又引申出了RootDisplayArea的两个子类,DisplayContentDisplayAreaGroup(这个是车载用到的,暂时不考虑)。

2.4.2.7.14 DisplayContent
1//frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
2class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.DisplayContentInfo {}

DisplayContent,代表了一个实际的屏幕,那么它作为一个屏幕上的DisplayArea层级结构的根节点也没有毛病。

隶属于同一个DisplayContent的窗口将会被显示在同一个屏幕中。每一个DisplayContent都对应着唯一ID,比如默认屏幕是0

那么以DisplayContent为根节点的DisplayArea层级结构可能是

2.4.2.7.15 RootWindowContainer
1//frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
2class RootWindowContainer extends WindowContainer<DisplayContent>
3        implements DisplayManager.DisplayListener {}

从名字也可以看出,这个类代表当前设备的根WindowContainer,就像View层级结构中的DecorView一样,它是DisplayContent的容器。由于DisplayContent代表了一个屏幕,且RootWindowContainer能够作为DisplayContent的父容器,这也说明了Android是支持多屏幕的,展开来说就是包括一个内部屏幕(内置于手机或平板电脑中的屏幕)、一个外部屏幕(如通过 HDMI 连接的电视)以及一个或多个虚拟屏幕。

如果我们在开发者选项里通过“Simulate secondary displays”开启另一个虚拟屏幕(或者是辅助模拟显示设备),此时的情况是:

1$ dumpsys activity containers
2ROOT type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][720,1612]
3  #1 Display 0 name="Built-in screen" type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][720,1612] bounds=[0,0][720,1612]
4  #0 Display 2 name="Overlay #1" type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][720,480] bounds=[0,0][720,480]

“Root”即RootWindowContainer,“Display 0”代表默认屏幕,“Display 2”是我们开启的虚拟屏幕。

2.4.2.8最终构建窗口层级图

2.4.2.9 总结

除了WindowState可以显示图像以外,大部分的WindowContainer,如WindowToken、TaskDisplayArea是不会有内容显示的,都只是一个抽象的容器概念。WMS如果只为了管理窗口,WMS也可以不创建这些个WindowContainer类,直接用一个类似列表的东西,将屏幕上显示的窗口全部添加到这个列表中,通过这一个列表来对所有的窗口进行管理。但是为了更有逻辑地管理屏幕上显示的窗口,还是需要创建各种各样的窗口容器类,即WindowContainer及其子类,来对WindowState进行分类,从而对窗口进行系统化的管理。

设计复杂的Window容器意义 详细描述
WindowContainer类都有着鲜明的上下级关系,一般不能越级处理 比如DefaultTaskDisplayArea只用来管理调度TaskTask用来管理调度ActivityRecord,而DefaultTaskDisplayArea不能直接越过Task去调度Task中的ActivityRecord。这样TaskDisplayArea只需要关注它的子容器们,即Task的管理,ActivityRecord相关的事务让Task去操心就好,每一级与每一级之间的边界都很清晰,不会在管理逻辑上出现混乱,比如DefaultTaskDisplayArea强行去调整一个ActivityRecord的位置,导致这个ActivityRecord跳出它所在的Task,变成和Task一个层级
保证了每一个WiindowContainer不会越界 比如我点击HOME键回到Launcher,此时DefaultTaskDisplayArea就会把Launcher对应的Task#1,移动到它内部栈的栈顶,而这仅限于DefaultTaskDisplayArea内部的调整,这一点保证了Launcher的窗口将永远不可能高于StatusBar窗口,也不会低于Wallpaper窗口

2.4.3实例分析Window容器

这里举一个实例,以android13设备手机为例

手机型号 三星Galaxy F52 5G
Android版本 Android13
测试场景 屏幕点亮的Launcher界面
后台应用
 1dumpf52x:/ $ dumpsys activity containers
 2ACTIVITY MANAGER CONTAINERS (dumpsys activity containers)
 3ROOT type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
 4  #0 Display 0 name="内置屏幕" type=undefined mode=fullscreen override-mode=fullscreen
 5 requested-bounds=[0,0][1080,2408] bounds=[0,0][1080,2408]
 6   #2 Leaf:36:36 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
 7   #1 HideDisplayCutout:32:35 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
 8    #2 OneHanded:34:35 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
 9     #0 FullscreenMagnification:34:35 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
10      #0 Leaf:34:35 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
11    #1 FullscreenMagnification:33:33 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
12     #0 Leaf:33:33 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
13      #0 WindowToken{258c658 type=2015 android.os.BinderProxy@d28af3b} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
14       #0 c4b73b1 LockscreenShortcutBlur type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
15    #0 OneHanded:32:32 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
16     #0 Leaf:32:32 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
17   #0 WindowedMagnification:0:31 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
18    #6 HideDisplayCutout:26:31 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
19     #0 OneHanded:26:31 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
20      #2 FullscreenMagnification:29:31 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
21       #0 Leaf:29:31 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
22        #0 WindowToken{b23fa21 type=2016 android.os.BinderProxy@b432f6e} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
23         #0 31d42b ShellDropTarget type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
24      #1 Leaf:28:28 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
25      #0 FullscreenMagnification:26:27 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
26       #0 Leaf:26:27 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
27    #5 Leaf:24:25 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
28     #2 WindowToken{d14c3be type=2024 android.os.BinderProxy@da64a79} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
29      #0 a381535 SecondaryHomeHandle0 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
30     #1 WindowToken{23be81 type=2024 android.os.BinderProxy@3ab3a68} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
31      #0 c099b26 EdgeBackGestureHandler0 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
32     #0 WindowToken{62fdc7 type=2019 android.os.BinderProxy@1c389e1} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
33      #0 2aa9419 NavigationBar0 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
34    #4 HideDisplayCutout:18:23 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
35     #0 OneHanded:18:23 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
36      #0 FullscreenMagnification:18:23 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
37       #0 Leaf:18:23 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
38        #0 WindowToken{ece377f type=2226 android.os.BinderProxy@d8c209e} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
39         #0 895a8aa com.samsung.android.app.cocktailbarservice/com.samsung.android.app.cocktailbarservice.CocktailBarService type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
40    #3 OneHanded:17:17 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
41     #0 FullscreenMagnification:17:17 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
42      #0 Leaf:17:17 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
43       #0 WindowToken{11fdd2b type=2040 android.os.BinderProxy@e1219a5} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
44        #0 6f61b46 NotificationShade type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
45    #2 HideDisplayCutout:16:16 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
46     #0 OneHanded:16:16 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
47      #0 FullscreenMagnification:16:16 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
48       #0 Leaf:16:16 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
49    #1 OneHanded:15:15 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
50     #0 FullscreenMagnification:15:15 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
51      #0 Leaf:15:15 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
52       #0 WindowToken{b620a94 type=2000 android.os.BinderProxy@7d4e01} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
53        #0 d5e5283 StatusBar type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
54    #0 HideDisplayCutout:0:14 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
55     #0 OneHanded:0:14 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
56      #1 ImePlaceholder:13:14 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
57       #0 ImeContainer type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
58        #1 WindowToken{33d4588 type=2011 android.os.Binder@1bfed2b} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
59         #0 dd5ca43 InputMethod type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
60        #0 WindowToken{d802601 type=2011 android.os.Binder@7a28fe8} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
61      #0 FullscreenMagnification:0:12 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
62       #2 Leaf:3:12 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
63        #0 WindowToken{6102e33 type=2038 android.os.BinderProxy@81db1a2} type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
64       #1 DefaultTaskDisplayArea type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
65        #2 Task=22 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
66         #0 Task=23 type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
67          #0 ActivityRecord{ba66eb5 u0 com.sec.android.app.launcher/.activities.LauncherActivity} t23} type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
68           #0 6e2f047 com.sec.android.app.launcher/com.sec.android.app.launcher.activities.LauncherActivity type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
69            #0 bec7e6c com.samsung.android.app.spage type=home mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
70        #1 Task=2 type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
71        #0 Task=3 type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
72         #1 Task=5 type=undefined mode=multi-window override-mode=multi-window requested-bounds=[0,0][1080,1222] bounds=[0,0][1080,1222]
73         #0 Task=4 type=undefined mode=multi-window override-mode=multi-window requested-bounds=[0,1245][1080,2408] bounds=[0,1245][1080,2408]
74       #0 Leaf:0:1 type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
75        #0 WallpaperWindowToken{c835f1c token=android.os.Binder@7d6e08f} type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]
76         #0 3d43d44 com.android.systemui.ImageWallpaper type=undefined mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][1080,2408]

window层次逻辑图如下所示,标红的为叶子结点,同色的为同一层级

2.5addView/updateViewLayout/removeView

在 Android 系统中,屏幕的抽象是 DisplayContent ,在屏幕的抽象上,通过不同的窗口,展示不同的应用程序页面和一些其他UI 组件(例如 Dialog、状态栏等)。Window 通过在 Z 轴叠放,从而实现了复杂的交互逻辑。

Window 实际是 View 的直接管理者,例如笔者点击屏幕,对应 Activity 里面收到点击事件后,会首先通过Activity对应的 window 将事件传递到 DecorView,最后再分发到我们的 View 上。而Window 是一个处理顶级窗口外观和行为策略的抽象基类,它的具体实现是 PhoneWindow 类,PhoneWindow 对 View 进行管理

2.5.1Window和Activity以及WindowManager建立联系

这里以Activity为例,其他Dialog、DreamOverlay、FloatWindow等可以自行查找源码阅读。

Activity在启动的过程中,会调用它的attach方法进行Window的创建

 1//frameworks/base/core/java/android/app/Activity.java
 2final void attach(Context context, ActivityThread aThread,
 3                  Instrumentation instr, IBinder token, int ident,
 4                  Application application, Intent intent, ActivityInfo info,
 5                  CharSequence title, Activity parent, String id,
 6                  NonConfigurationInstances lastNonConfigurationInstances,
 7                  Configuration config, String referrer, IVoiceInteractor voiceInteractor,
 8                  Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
 9                  IBinder shareableActivityToken) {
10    ...
11    //[2.5.1.1]
12    mWindow = new PhoneWindow(this, window, activityConfigCallback);
13    ...
14    //[2.5.1.2]    
15    mWindow.setWindowManager(
16        (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
17        mToken, mComponent.flattenToString(),
18        (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
19}

2.5.1.1PhoneWindow

创建了一个PhoneWindow,并传入当前Activity的this对象, PhoneWindow类,继承了Window

 1//frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
 2public class PhoneWindow extends Window {
 3    public PhoneWindow(Context context, Window preservedWindow,
 4            ActivityConfigCallback activityConfigCallback) {
 5        //这里最终会调父类Window的构造方法,并传入Activity的实例context
 6        this(context);
 7        //只有Activity的Window才会使用Decor context,其他依赖使用的Context
 8        mUseDecorContext = true;
 9        ...
10   }
11
12}

调用父类方法,让抽象父类内部持有Activity的实例化对象context

1//frameworks_base/core/java/android/view/Window.java
2public abstract class Window {
3     public Window(@UiContext Context context) {
4        mContext = context;
5        mFeatures = mLocalFeatures = getDefaultFeatures(context);
6    }   
7}

PhoneWindow的构造方法中传入当前Activity对象, 并最终在父类Window构造方法中给mContext赋值,Window得到Activity的引用,由此就建立了Window和Activity的联系。

2.5.1.2setWindowManager

 1//frameworks_base/core/java/android/view/Window.java
 2/**
 3     *用Window来设置窗口管理器,例如,显示面板。Window本身不能用于显示,必须通过客户端设置
 4     *window manager 用来添加新的Windows
 5     */
 6public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
 7                             boolean hardwareAccelerated) {
 8    //前三个参数是基本配置,包括AppToken,AppName和是否硬件加速
 9    mAppToken = appToken;
10    mAppName = appName;
11    mHardwareAccelerated = hardwareAccelerated;
12    if (wm == null) {
13        //[2.5.1.3]
14        wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
15    }
16    //[2.5.1.4]
17    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
18}

2.5.1.3 getSystemService(Context.WINDOW_SERVICE)

这里就是获取系统注册的服务,主要是SystemServer,属于服务应用层的服务,其他包括AMS,PMS等

注意到SystemServiceRegistry的static 方法,这个方法内部注册了很多服务,我们可以在方法里面找到name = Context.WINDOW_SERVICE的服务

 1//frameworks/base/core/java/android/app/SystemServiceRegistry.java
 2static {
 3    registerService(Context.WINDOW_SERVICE, WindowManager.class,
 4                    new CachedServiceFetcher<WindowManager>() {
 5                        @Override
 6                        public WindowManager createService(ContextImpl ctx) {
 7                            //最终会回调到这
 8                            return new WindowManagerImpl(ctx);
 9                        }});
10}

registerService

 1//frameworks/base/core/java/android/app/SystemServiceRegistry.java
 2private static final Map<Class<?>, String> SYSTEM_SERVICE_NAMES =
 3    new ArrayMap<Class<?>, String>();
 4private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
 5    new ArrayMap<String, ServiceFetcher<?>>();
 6private static final Map<String, String> SYSTEM_SERVICE_CLASS_NAMES = new ArrayMap<>();
 7
 8private static <T> void registerService(@NonNull String serviceName,
 9                                        @NonNull Class<T> serviceClass, @NonNull ServiceFetcher<T> serviceFetcher) {
10    //WindowManager.class和Context.WINDOW_SERVICE,组成kv的ArrayMap
11    SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
12    //Context.WINDOW_SERVICE和Fetcher(CachedServiceFetcher)组成kv的ArrayMap
13    SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
14    //Context.WINDOW_SERVICE和WindowManager,组成kv的ArrayMap
15    SYSTEM_SERVICE_CLASS_NAMES.put(serviceName, serviceClass.getSimpleName());
16}

这里的mContext是ContextImpl类的对象

1//frameworks/base/core/java/android/app/ContextImpl.java
2@Override
3public Object getSystemService(String name) {
4    ...
5    return SystemServiceRegistry.getSystemService(this, name);
6}

进入SystemServiceRegistry的getSystemService方法,name = Context.WINDOW_SERVICE

1//frameworks/base/core/java/android/app/SystemServiceRegistry.java
2public static Object getSystemService(ContextImpl ctx, String name) {
3    ...
4    //SYSTEM_SERVICE_FETCHERS是一个ArrayMap的集合
5    final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
6    ...
7    final Object ret = fetcher.getService(ctx);
8    return ret;
9}

先通过Context.WINDOW_SERVICE找到对应的fetcher(上述的CachedServiceFetcher),然后从fetcher中找出其对应的服务

 1static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
 2    @Override
 3    @SuppressWarnings("unchecked")
 4    public final T getService(ContextImpl ctx) {
 5        ...
 6        if (doInitialize) {
 7            T service = null;
 8            @ServiceInitializationState int newState = ContextImpl.STATE_NOT_FOUND;
 9            try {
10                service = createService(ctx);
11                newState = ContextImpl.STATE_READY;
12            } catch (ServiceNotFoundException e) {
13                ...
14            }
15            return ret;
16        }
17    }
18
19    public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
20}

最终回到上述的回调,即对应服务为WindowManagerImpl的实例

2.5.1.4 createLocalWindowManager

这里也是创建了一个WindowManagerImpl,和前面getSystemService创建的WindowManagerImpl区别:这里传入了parentWindow,让WindowManagerImpl持有了Window的引用,这里持有的引用实际上是PhoneWindow,可以对此Window进行操作了。

1//frameworks/base/core/java/android/view/WindowManagerImpl.java
2public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
3    return new WindowManagerImpl(mContext, parentWindow, mWindowContextToken);
4}

2.5.1.5总结

  1. new一个PhoneWindow对象并传入当前Activity引用,建立Window和Activity的一一对应关系。此Window是Window类的子类PhoneWindow的实例。Activity在Window中是以mContext属性存在。
  2. 调用PhoneWindow的setWindowManager方法,在这个方法中让Window和WindowManager建立了一一关系。此WindowManager是WindowManagerImpl的实例

UML类图

流程图

2.5.2Window和View关联

关于Window和View关联,详细可以阅读2.6.1关于activity的xml怎么跟屏幕关联起来的

这里主要梳理一下类图关系,App直接继承Activity和App继承AppCompatActivity流程类似,都是在DecorView内部操作的。

简单概述:( Window和View进行关联)

  1. 我们就给当前Activity的Window创建了一个DecorView
  2. 这个DecorView就是当前Window的rootView
  3. 并对rootView的ContentView设置了mContentParent 实现了将Window和DecorView绑定
 1//frameworks/base/core/java/android/app/Activity.java
 2//1.设置主题
 3public void setTheme(int resid) {
 4    super.setTheme(resid);
 5    mWindow.setTheme(resid);
 6}
 7//2.设置Window属性等
 8public final boolean requestWindowFeature(int featureId) {
 9    return getWindow().requestFeature(featureId);
10}
11
12//3.获取Layout解析器
13public LayoutInflater getLayoutInflater() {
14    return getWindow().getLayoutInflater();
15}

可以发现Activity所有的对View进行的操作几乎都是通过Window进行的。而我们的Activity又是通过AMS来控制生命周期,所以AMS和View的通讯其实就是通过WindowManager这个介子进行的。

2.5.3View上屏、更新、删除

这里的代码比较多,比较还是以精简的方式来叙述,详细具体可以阅读参考文章

屏幕中所有的View首先需要经过WindowManager的处理,最后提交给WMS来处理。

WindowManager的父类为ViewManager(当然ViewManager也是GroupView的接口)

1//frameworks/base/core/java/android/view/ViewManager.java
2public interface ViewManager{
3    public void addView(View view, ViewGroup.LayoutParams params);
4    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
5    public void removeView(View view);
6}

而这几个方法具体实现是在WindowManagerImpl类中

 1//frameworks/base/core/java/android/view/WindowManagerImpl.java
 2public final class WindowManagerImpl implements WindowManager{
 3    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
 4    @Override
 5    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
 6        applyTokens(params);
 7        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
 8                mContext.getUserId());
 9    }
10
11    @Override
12    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
13        applyTokens(params);
14        mGlobal.updateViewLayout(view, params);
15    }
16    @Override
17    public void removeView(View view) {
18        mGlobal.removeView(view, false);
19    }
20    ...
21}

2.5.3.1addView

根据上面的流程,这里是以WindowManager实现的函数调用为例

2.5.3.1.1WindowManagerGlobal.addView
 1//frameworks/base/core/java/android/view/WindowManagerGlobal.java
 2public final class WindowManagerGlobal {
 3    private final ArrayList<View> mViews = new ArrayList<View>();
 4    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
 5    private final ArrayList<WindowManager.LayoutParams> mParams =
 6            new ArrayList<WindowManager.LayoutParams>();
 7    public void addView(View view, ViewGroup.LayoutParams params,
 8                        Display display, Window parentWindow, int userId) {
 9        ...
10        //将params强转为WindowManager.LayoutParams类型
11        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
12        ...
13        ViewRootImpl root;
14        View panelParentView = null;
15
16        synchronized (mLock) {
17            ...
18            IWindowSession windowlessSession = null;
19            // 如果有一个父集,但是我们找不到它,它可能来自一个SurfaceControlViewHost层次结构
20            if (wparams.token != null && panelParentView == null) {
21                for (int i = 0; i < mWindowlessRoots.size(); i++) {
22                    ViewRootImpl maybeParent = mWindowlessRoots.get(i);
23                    if (maybeParent.getWindowToken() == wparams.token) {
24                        windowlessSession = maybeParent.getWindowSession();
25                        break;
26                    }
27                }
28            }
29
30            if (windowlessSession == null) {
31                //创建一个ViewRootImpl对象
32                root = new ViewRootImpl(view.getContext(), display);
33            } else {
34                root = new ViewRootImpl(view.getContext(), display,
35                                        windowlessSession, new WindowlessWindowLayout());
36            }
37
38            view.setLayoutParams(wparams);
39			//存储view到mViews列表,存储root到mRoots列表,存储wparams到mParams列表
40            mViews.add(view);
41            mRoots.add(root);
42            mParams.add(wparams);
43
44            // do this last because it fires off messages to start doing things
45            try {
46                //这里的root是ViewRootImpl
47                root.setView(view, wparams, panelParentView, userId);
48            } catch (RuntimeException e) {
49                final int viewIndex = (index >= 0) ? index : (mViews.size() - 1);
50                // BadTokenException or InvalidDisplayException, clean up.
51                if (viewIndex >= 0) {
52                    removeViewLocked(viewIndex, true);
53                }
54                throw e;
55            }
56        }
57    }   
58}
  • 将params强转为WindowManager.LayoutParams类型
  • 创建一个ViewRootImpl对象root。
  • 然后将当前view,当前params以及1中创建的ViewRootImpl对象root分别存储到对应的List列表中
  • 调用root的setView方法
2.5.3.1.2ViewRootImpl.setView

ViewRootImpl有很多作用

  1. 管理View树,且其是View的根
  2. 触发三大绘制流程:测量,布局,绘制
  3. 输入事件中转站
  4. 管理Surface
  5. 负责与WMS通讯
 1//frameworks/base/core/java/android/view/ViewRootImpl.java
 2final IWindowSession mWindowSession;
 3public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
 4                    int userId) {
 5    synchronized (this) {
 6        if (mView == null) {
 7            mView = view;
 8            // 在添加到窗口管理器之前安排第一个布局,以确保我们在从系统接收任何其他事件之前执行布局。
 9            requestLayout();
10            ...
11            try {
12                res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
13                                                        getHostVisibility(), mDisplay.getDisplayId(),
14                                                        userId,mInsetsController.getRequestedVisibilities(), 
15                                                        inputChannel, mTempInsets,mTempControls,
16                                                        attachedFrame, compatScale);
17
18        }
19    }
20}

这里主要是requestLayout方法和addToDisplayAsUser方法。

前者方法是屏幕绘制部分,这里不展开说明,主要为requestLayout内部主要使用垂直同步信号VSync的方式,在收到GPU提供的VSync信号后,会触发View的三大绘制layoutmesuredraw流程。

2.5.3.1.3mWindowSession.addToDisplayAsUser

后面是addToDisplayAsUser方法。mWindowSession是IWindowSession类型的

 1//frameworks/base/core/java/android/view/ViewRootImpl.java
 2final IWindowSession mWindowSession;
 3public ViewRootImpl(Context context, Display display) {
 4    this(context, display, WindowManagerGlobal.getWindowSession(), new WindowLayout());
 5}
 6
 7public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
 8                    WindowLayout windowLayout) {
 9    mContext = context;
10    mWindowSession = session;
11    ...
12}

可以看到mWindowSession实际上是是一个binder对象,用于进程间通讯,IWindowSession是C端代理,在S端使用的是Session类实现

 1//frameworks/base/core/java/android/view/WindowManagerGlobal.java
 2public static IWindowSession getWindowSession() {
 3    synchronized (WindowManagerGlobal.class) {
 4        if (sWindowSession == null) {
 5            try {
 6                InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
 7                //这里获取的是WindowManagerService的服务的代理类
 8                IWindowManager windowManager = getWindowManagerService();
 9                //真正获取到sWindowSession的对象代理类
10                sWindowSession = windowManager.openSession(
11                    new IWindowSessionCallback.Stub() {
12                        @Override
13                        public void onAnimatorScaleChanged(float scale) {
14                            ValueAnimator.setDurationScale(scale);
15                        }
16                    });
17            } catch (RemoteException e) {
18                throw e.rethrowFromSystemServer();
19            }
20        }
21        return sWindowSession;
22    }
23}
24
25public static IWindowManager getWindowManagerService() {
26    synchronized (WindowManagerGlobal.class) {
27        if (sWindowManagerService == null) {
28            sWindowManagerService = IWindowManager.Stub.asInterface(
29                ServiceManager.getService("window"));
30                ...
31        }
32        return sWindowManagerService;
33    }
34}

openSession

1//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
2public IWindowSession openSession(IWindowSessionCallback callback) {
3    return new Session(this, callback);
4}

调用的Session的构造,并将回调监听也加入

 1//frameworks/base/services/core/java/com/android/server/wm/Session.java
 2class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
 3    final WindowManagerService mService;
 4
 5    public Session(WindowManagerService service, IWindowSessionCallback callback) {
 6        mService = service;
 7        mCallback = callback;
 8        ...
 9    }
10
11    @Override
12    public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
13            int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
14            InputChannel outInputChannel, InsetsState outInsetsState,
15            InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
16            float[] outSizeCompatScale) {
17        //这里的mService就是构造注入的wms的this指针
18        return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
19                requestedVisibilities, outInputChannel, outInsetsState, outActiveControls,
20                outAttachedFrame, outSizeCompatScale);
21    }
22}
2.5.3.1.4mService.addWindow

所以之前的Session类的addToDisplayAsUser方法,会调用addWindow方法,根据前面学习的Window容器,可以知道这里实际上Window容器添加到添加上去。(需要明白一点,先前的View绘制三部曲,测量,布局,绘制完成后,需要后续的图像的合成和显示)

在WMS眼里,一切View都是以Window形式存在的, 剩下的工作就交由WMS进行处理了:在WMS中会为这个Window分配Surface,并确定显示层级, 可见负责显示界面的是画布Surface,而不是窗口本身,WMS将他管理的Surface交由SurfaceFlinger处理,SurfaceFlinger将这些Surface合并后放入到buffer中,屏幕会定时从buffer中获取显示数据,显示到屏幕上。

查看源码addWindow,这里正好和上面的Window层级联系上了

 1//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
 2final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();
 3
 4public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
 5                     int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
 6                     InputChannel outInputChannel, InsetsState outInsetsState,
 7                     InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
 8                     float[] outSizeCompatScale) {
 9   
10
11    WindowState parentWindow = null;
12    ...
13    synchronized (mGlobalLock) {
14        final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);
15
16		//type类型获取
17        if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
18            parentWindow = windowForClientLocked(null, attrs.token, false);
19            if (parentWindow == null) {
20                return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
21            }
22            if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
23                && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
24                return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
25            }
26        }
27
28        if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) {
29            return WindowManagerGlobal.ADD_PERMISSION_DENIED;
30        }
31
32        if (type == TYPE_PRESENTATION && !displayContent.getDisplay().isPublicPresentation()) {
33            return WindowManagerGlobal.ADD_INVALID_DISPLAY;
34        }
35		//token获取
36        if (token == null) {
37            if (hasParent) {
38                // Use existing parent window token for child windows.
39                token = parentWindow.mToken;
40            } else if (mWindowContextListenerController.hasListener(windowContextToken)) {
41                // Respect the window context token if the user provided it.
42                final IBinder binder = attrs.token != null ? attrs.token : windowContextToken;
43                final Bundle options = mWindowContextListenerController
44                    .getOptions(windowContextToken);
45                token = new WindowToken.Builder(this, binder, type)
46                    .setDisplayContent(displayContent)
47                    .setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
48                    .setRoundedCornerOverlay(isRoundedCornerOverlay)
49                    .setFromClientToken(true)
50                    .setOptions(options)
51                    .build();
52            } else {
53                final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
54                token = new WindowToken.Builder(this, binder, type)
55                    .setDisplayContent(displayContent)
56                    .setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
57                    .setRoundedCornerOverlay(isRoundedCornerOverlay)
58                    .build();
59            }
60        } else if (rootType >= FIRST_APPLICATION_WINDOW
61                   && rootType <= LAST_APPLICATION_WINDOW) {
62            //token获取
63        }
64		//[2.5.3.1.5]
65        final WindowState win = new WindowState(this, session, client, token, parentWindow,
66                                                appOp[0], attrs, viewVisibility, session.mUid, userId,
67                                                session.mCanAddInternalSystemWindow);
68        // 这里筛选子窗口类型,因为子窗口必须附加到父窗口,子窗口的Token和父窗口一致
69        //[2.5.3.1.6]
70        win.mToken.addWindow(win);
71        ...
72    }
73
74    Binder.restoreCallingIdentity(origId);
75
76    return res;
77}

流程比较复杂

  1. 通过 WindowManagerPolicy 的 checkAddPermission 检查添加权限

  2. 通过 displayId 获取所添加到的屏幕抽象 DisplayContent 对象

  3. 根据 Window 是否是子窗口,创建 WindowToken

  4. 如果上一步创建 token 失败,重新根据是否存在父窗口创建 WindowToken

    1. 子窗口直接取父窗口的 WindowToken
    2. 新的窗口直接构造一个新的 WindowToken
  5. 根据根窗口的 type 来处理不同的情况(Toast、键盘、无障碍浮层等不同类型返回不同的结果)

  6. 创建 WindowState

  7. 检查客户端状态,判断是否可以将 Window 添加到系统中

  8. 以 session 为 key ,windowState 为 value ,存入到 mWindowMap 中

  9. 将 WindowState 添加到相应的 WindowToken 中

    1. 若 WindowState 代表一个子窗口,直接 return
    2. 若仍没有 SurfaceControl ,为该 token 创建 Surface
    3. 更新 Layer
    4. 根据 Z 轴排序顺序将 WindowState 所代表的 Window 添加到合适的位置,此数据结构保存在 WindowToken 的父类 WindowContainer 的 mChildren 中:
  10. 检查是否需要转换动画

  11. 更新窗口焦点

  12. 最后返回执行结果

最重要的就是[2.5.3.1.5]和[2.5.3.1.6]的操作,也是添加Window过程中Window容器之间的添加。

笔者这里之阐述跟Window容器相关的流程,关于WMS中的Surface,还有Input流程等需要到具体WMS章节阅读,可以点击这里

2.5.3.1.5WindowState
 1//frameworks/base/services/core/java/com/android/server/wm/WindowState.java
 2class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState,
 3InsetsControlTarget, InputTarget {
 4    WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
 5                WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
 6                int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow,
 7                PowerManagerWrapper powerManagerWrapper) {
 8        //下面这些信息都会通过dumpsys window windows出现
 9        super(service);
10        mSession = s;
11        mClient = c;
12        mAppOp = appOp;
13        mToken = token;
14        mActivityRecord = mToken.asActivityRecord();
15        mWindowId = new WindowId(this);
16        mViewVisibility = viewVisibility;
17        mPolicy = mWmService.mPolicy;
18        mContext = mWmService.mContext;
19        ...
20        //这一段在上述Window层级中出现,现在回过头再看就眼熟很多
21        if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW) {
22            // The multiplier here is to reserve space for multiple
23            // windows in the same type layer.
24            mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)
25                * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
26            mSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);
27            mIsChildWindow = true;
28
29            mLayoutAttached = mAttrs.type !=
30                WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
31            mIsImWindow = parentWindow.mAttrs.type == TYPE_INPUT_METHOD
32                || parentWindow.mAttrs.type == TYPE_INPUT_METHOD_DIALOG;
33            mIsWallpaper = parentWindow.mAttrs.type == TYPE_WALLPAPER;
34        } else {
35            // The multiplier here is to reserve space for multiple
36            // windows in the same type layer.
37            mBaseLayer = mPolicy.getWindowLayerLw(this)
38                * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
39            mSubLayer = 0;
40            mIsChildWindow = false;
41            mLayoutAttached = false;
42            mIsImWindow = mAttrs.type == TYPE_INPUT_METHOD
43                || mAttrs.type == TYPE_INPUT_METHOD_DIALOG;
44            mIsWallpaper = mAttrs.type == TYPE_WALLPAPER;
45        }
46        ...
47        // 确保在添加到parentwwindow之前初始化所有字段,以防止onDisplayChanged期间出现异常
48        if (mIsChildWindow) {
49            ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Adding %s to %s", this, parentWindow);
50            parentWindow.addChild(this, sWindowSubLayerComparator);
51        }
52    }
53}

这里构造只会让WindowState和父WindowState联系起来,并不会将WindowToken和WindowState关联起来

2.5.3.1.6win.mToken.addWindow

会将WindowToken和WindowState关联起来

 1//frameworks/base/services/core/java/com/android/server/wm/WindowToken.java
 2void addWindow(final WindowState win) {
 3    if (win.isChildWindow()) {
 4        // 子窗口被添加到它们的父窗口
 5        return;
 6    }
 7    ...
 8    if (!mChildren.contains(win)) {
 9        addChild(win, mWindowComparator);
10        mWmService.mWindowsChanged = true;
11    }
12}

最终会调用到父类WindowContainer的addChild方法

 1//frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java
 2protected final WindowList<E> mChildren = new WindowList<E>();
 3private WindowContainer<WindowContainer> mParent = null;
 4@CallSuper
 5protected void addChild(E child, Comparator<E> comparator) {
 6    int positionToAdd = -1;
 7    if (comparator != null) {
 8        final int count = mChildren.size();
 9        for (int i = 0; i < count; i++) {
10            if (comparator.compare(child, mChildren.get(i)) < 0) {
11                positionToAdd = i;
12                break;
13            }
14        }
15    }
16
17    if (positionToAdd == -1) {
18        mChildren.add(child);
19    } else {
20        mChildren.add(positionToAdd, child);
21    }
22
23    // Set the parent after we've actually added a child in case a subclass depends on this.
24    child.setParent(this);
25}
26
27final protected void setParent(WindowContainer<WindowContainer> parent) {
28    final WindowContainer oldParent = mParent;
29    mParent = parent;
30
31    if (mParent != null) {
32        mParent.onChildAdded(this);
33    } else if (mSurfaceAnimator.hasLeash()) {
34        mSurfaceAnimator.cancelAnimation();
35    }
36    if (!mReparenting) {
37        onSyncReparent(oldParent, mParent);
38        if (mParent != null && mParent.mDisplayContent != null
39            && mDisplayContent != mParent.mDisplayContent) {
40            onDisplayChanged(mParent.mDisplayContent);
41        }
42        onParentChanged(mParent, oldParent);
43    }
44}

上述添加规则mWindowComparator,实际上就是上面层级关系2.3.2.3的WindowState添加规则

2.5.3.1.7流程图

补充UML图

2.5.3.2updateViewLayout

和Window 添加类似,主要简单介绍为主,感兴趣的读者可以自行查看源码。它的实现也是在 WindowManagerImpl 中,同样的,实际调用的是 WindowManagerGlobal 对应的方法 updateViewLayout

 1//frameworks/base/core/java/android/view/WindowManagerGlobal.java
 2public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
 3    if (view == null) {
 4        throw new IllegalArgumentException("view must not be null");
 5    }
 6    if (!(params instanceof WindowManager.LayoutParams)) {
 7        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
 8    }
 9
10    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
11
12    view.setLayoutParams(wparams);
13
14    synchronized (mLock) {
15        int index = findViewLocked(view, true);
16        ViewRootImpl root = mRoots.get(index);
17        mParams.remove(index);
18        mParams.add(index, wparams);
19        root.setLayoutParams(wparams, false);
20    }
21}

这里的root直接调用对应的setLayoutParams方法,其中后一个参数为false,表示无需重绘

 1//frameworks/base/core/java/android/view/ViewRootImpl.java
 2public void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
 3    synchronized (this) {
 4        final int oldInsetLeft = mWindowAttributes.surfaceInsets.left;
 5        final int oldInsetTop = mWindowAttributes.surfaceInsets.top;
 6        final int oldInsetRight = mWindowAttributes.surfaceInsets.right;
 7        final int oldInsetBottom = mWindowAttributes.surfaceInsets.bottom;
 8        final int oldSoftInputMode = mWindowAttributes.softInputMode;
 9        final boolean oldHasManualSurfaceInsets = mWindowAttributes.hasManualSurfaceInsets;
10        // Keep track of the actual window flags supplied by the client.
11        mClientWindowLayoutFlags = attrs.flags;
12
13        // Preserve compatible window flag if exists.
14        final int compatibleWindowFlag = mWindowAttributes.privateFlags
15            & WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
16
17        // Preserve system UI visibility.
18        final int systemUiVisibility = mWindowAttributes.systemUiVisibility;
19        final int subtreeSystemUiVisibility = mWindowAttributes.subtreeSystemUiVisibility;
20
21        // Preserve appearance and behavior.
22        final int appearance = mWindowAttributes.insetsFlags.appearance;
23        final int behavior = mWindowAttributes.insetsFlags.behavior;
24        final int appearanceAndBehaviorPrivateFlags = mWindowAttributes.privateFlags
25            & (PRIVATE_FLAG_APPEARANCE_CONTROLLED | PRIVATE_FLAG_BEHAVIOR_CONTROLLED);
26
27        final int changes = mWindowAttributes.copyFrom(attrs);
28        if ((changes & WindowManager.LayoutParams.TRANSLUCENT_FLAGS_CHANGED) != 0) {
29            // Recompute system ui visibility.
30            mAttachInfo.mRecomputeGlobalAttributes = true;
31        }
32        if ((changes & WindowManager.LayoutParams.LAYOUT_CHANGED) != 0) {
33            // Request to update light center.
34            mAttachInfo.mNeedsUpdateLightCenter = true;
35        }
36        if (mWindowAttributes.packageName == null) {
37            mWindowAttributes.packageName = mBasePackageName;
38        }
39
40        // Restore preserved flags.
41        mWindowAttributes.systemUiVisibility = systemUiVisibility;
42        mWindowAttributes.subtreeSystemUiVisibility = subtreeSystemUiVisibility;
43        mWindowAttributes.insetsFlags.appearance = appearance;
44        mWindowAttributes.insetsFlags.behavior = behavior;
45        mWindowAttributes.privateFlags |= compatibleWindowFlag
46            | appearanceAndBehaviorPrivateFlags
47            | WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
48
49        if (mWindowAttributes.preservePreviousSurfaceInsets) {
50            // Restore old surface insets.
51            mWindowAttributes.surfaceInsets.set(
52                oldInsetLeft, oldInsetTop, oldInsetRight, oldInsetBottom);
53            mWindowAttributes.hasManualSurfaceInsets = oldHasManualSurfaceInsets;
54        } else if (mWindowAttributes.surfaceInsets.left != oldInsetLeft
55                   || mWindowAttributes.surfaceInsets.top != oldInsetTop
56                   || mWindowAttributes.surfaceInsets.right != oldInsetRight
57                   || mWindowAttributes.surfaceInsets.bottom != oldInsetBottom) {
58            mNeedsRendererSetup = true;
59        }
60
61        applyKeepScreenOnFlag(mWindowAttributes);
62		//这里无需重绘
63        if (newView) {
64            mSoftInputMode = attrs.softInputMode;
65            requestLayout();
66        }
67
68        // Don't lose the mode we last auto-computed.
69        if ((attrs.softInputMode & SOFT_INPUT_MASK_ADJUST)
70            == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
71            mWindowAttributes.softInputMode = (mWindowAttributes.softInputMode
72                                               & ~SOFT_INPUT_MASK_ADJUST) | (oldSoftInputMode & SOFT_INPUT_MASK_ADJUST);
73        }
74		//如果softInputMode有调整,会触发这里的scheduleTraversals
75        if (mWindowAttributes.softInputMode != oldSoftInputMode) {
76            requestFitSystemWindows();
77        }
78
79        mWindowAttributesChanged = true;
80        scheduleTraversals();
81    }
82}

这里的scheduleTraversals在上述addView也有,这里不做展开,申请下一帧进行绘制。

2.5.3.3removeView

Window 的删除过程通过 WindowManager 的 removeView 方法进行的

 1//frameworks/base/core/java/android/view/WindowManagerImpl.java
 2@Override
 3public void removeView(View view) {
 4    mGlobal.removeView(view, false);
 5}
 6
 7@Override
 8public void removeViewImmediate(View view) {
 9    mGlobal.removeView(view, true);
10}

上面在WindowManagerImpl将remove操作划分,通过是否立刻来判断是否是同步,false为异步,true为同步。通常Activity走的是异步,Dialog会走同步。

实际逻辑在 WindowManagerGlobal 的实现中。和Window 添加类似,主要简单介绍为主,感兴趣的读者可以自行查看源码。

 1//frameworks/base/core/java/android/view/WindowManagerGlobal.java
 2public void removeView(View view, boolean immediate) {
 3    if (view == null) {
 4        throw new IllegalArgumentException("view must not be null");
 5    }
 6
 7    synchronized (mLock) {
 8        //[2.5.3.3.1]
 9        int index = findViewLocked(view, true);
10        //[2.5.3.3.2]
11        View curView = mRoots.get(index).getView();
12        //[2.5.3.3.3]
13        removeViewLocked(index, immediate);
14        if (curView == view) {
15            return;
16        }
17
18        throw new IllegalStateException("Calling with view " + view
19                                        + " but the ViewAncestor is attached to " + curView);
20    }
21}
2.5.3.3.1findViewLocked

获取view在mViews中的索引

1//frameworks/base/core/java/android/view/WindowManagerGlobal.java
2private int findViewLocked(View view, boolean required) {
3    final int index = mViews.indexOf(view);
4    if (required && index < 0) {
5        throw new IllegalArgumentException("View=" + view + " not attached to window manager");
6    }
7    return index;
8}
2.5.3.3.2mRoots.get(index).getView()

从 mRoots 中根据索引找出 ViewRootImpl ,通过 ViewRootImpl 找到它的 View。通常我们访问的View可能是一个子View,然后直接添加的View可能是一个View树,这个时候如果删除那么就是删除这个子View对应的之前setView添加的View树。当然可能这个现在的View就是之前的setView进来的View

 1//frameworks/base/core/java/android/view/ViewRootImpl.java
 2View mView;
 3public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
 4                    int userId) {
 5    synchronized (this) {
 6        if (mView == null) {
 7            mView = view;
 8        }
 9    }
10    ...
11}
12
13public View getView() {
14    return mView;
15}

说明一下addView传入的View和removeView传入的View

2.5.3.3.3removeViewLocked
 1//frameworks/base/core/java/android/view/WindowManagerGlobal.java
 2private void removeViewLocked(int index, boolean immediate) {
 3    ViewRootImpl root = mRoots.get(index);
 4    View view = root.getView();
 5
 6    if (root != null) {
 7        //调用去清除输入焦点,结束处理输入法相关的逻辑
 8        root.getImeFocusController().onWindowDismissed();
 9    }
10    boolean deferred = root.die(immediate);
11    if (view != null) {
12        view.assignParent(null);
13        if (deferred) {
14            mDyingViews.add(view);
15        }
16    }
17}

在这个方法中,首先也是获取 ViewRootImpl 和 View 对象,然后调用 ViewRootImpl 对象的 die 方法,其中传入的immediate是根据外部传入的值来判断,如果为true同步移除才执行后续的dodie方法。

 1//frameworks/base/core/java/android/view/ViewRootImpl.java
 2boolean die(boolean immediate) {
 3    // 如果是同步移除,则立即调用doDie方法
 4    if (immediate && !mIsInTraversal) {
 5        doDie();
 6        return false;
 7    }
 8
 9    if (!mIsDrawing) {
10        destroyHardwareRenderer();
11    } else {
12        Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
13              "  window=" + this + ", title=" + mWindowAttributes.getTitle());
14    }
15    //异步移除,发出一个msg进行移除,在主线程移除
16    mHandler.sendEmptyMessage(MSG_DIE);
17    return true;
18}
2.5.3.3.4doDie
 1//frameworks/base/core/java/android/view/ViewRootImpl.java
 2void doDie() {
 3    ...
 4    synchronized (this) {
 5        //mAdded必须在setView之后,且还没有doDie之前,不然再次调用doDie,mAdded也为false
 6        if (mAdded) {
 7            //[2.5.3.3.5]	
 8            dispatchDetachedFromWindow();
 9        }
10        ..
11    }
12    //[2.5.3.3.6]
13    WindowManagerGlobal.getInstance().doRemoveView(this);
14}
2.5.3.3.5dispatchDetachedFromWindow
 1//frameworks/base/core/java/android/view/ViewRootImpl.java
 2void dispatchDetachedFromWindow() {
 3    //这里可以看出我们可以在View销毁前,在View视图树分发的dispatchDetachedFromWindow做一些资源释放操作
 4    if (mView != null && mView.mAttachInfo != null) {
 5        mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
 6        mView.dispatchDetachedFromWindow();
 7    }
 8	//硬件渲染的销毁释放
 9    destroyHardwareRenderer();
10	//将mView的root置为null
11    mView.assignParent(null);
12    mView = null;
13    mAttachInfo.mRootView = null;
14	//释放当前Surface
15    destroySurface();
16
17    try {
18        //[2.5.3.3.7]通过Session IPC调用了WMS移除Window,这里移除的实际上是Window容器
19        mWindowSession.remove(mWindow);
20    } catch (RemoteException e) {
21    }
22    // 销毁对应的input接受者
23    if (mInputEventReceiver != null) {
24        mInputEventReceiver.dispose();
25        mInputEventReceiver = null;
26    }
27	//删除待执行的 Traversals
28    unregisterListeners();
29    unscheduleTraversals();
30}

dispatchDetachedFromWindow 方法中

  1. 开始向视图树中的 View 分发 DetachedFromWindow ,
  2. 然后释放一些资源,释放 Surface
  3. 通过 Session IPC 调用了 WMS 移除 Window
  4. 删除待执行的 Traversals

这里的unscheduleTraversals和上面的scheduleTraversals恰好相反,这里是移除同步屏障,使得上述scheduleTraversals还未执行的消息队列失效

2.5.3.3.6doRemoveView

这里主要是对WindowManagerGlobal保存的数据(mRoots、mParams、mViews)进行移除

 1//frameworks/base/core/java/android/view/WindowManagerGlobal.java
 2void doRemoveView(ViewRootImpl root) {
 3    boolean allViewsRemoved;
 4    synchronized (mLock) {
 5        final int index = mRoots.indexOf(root);
 6        if (index >= 0) {
 7            mRoots.remove(index);
 8            mParams.remove(index);
 9            final View view = mViews.remove(index);
10            mDyingViews.remove(view);
11        }
12        allViewsRemoved = mRoots.isEmpty();
13    }
14    if (ThreadedRenderer.sTrimForeground) {
15        doTrimForeground();
16    }
17
18    if (allViewsRemoved) {
19        InsetsAnimationThread.release();
20    }
21}
2.5.3.3.7remove

根据addView分析,mWindowSession是WMS进程中的Session类

1//frameworks/base/services/core/java/com/android/server/wm/Session.java
2public void remove(IWindow window) {
3    mService.removeWindow(this, window);
4}
2.5.3.3.8removeWindow
 1//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
 2void removeWindow(Session session, IWindow client) {
 3    synchronized (mGlobalLock) {
 4        //[2.5.3.3.9]通过windowid找到对应的WindowState容器
 5        WindowState win = windowForClientLocked(session, client, false);
 6        if (win != null) {
 7            //[2.5.3.3.10]移除相关的WindowState容器
 8            win.removeIfPossible();
 9            return;
10        }
11
12        //如果令牌属于EmbeddedWindow,则删除EmbeddedWindow映射
13        mEmbeddedWindowController.remove(client);
14    }
15}
2.5.3.3.9windowForClientLocked
 1//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
 2final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();
 3public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
 4                     int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
 5                     InputChannel outInputChannel, InsetsState outInsetsState,
 6                     InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
 7                     float[] outSizeCompatScale) {
 8    final WindowState win = new WindowState(this, session, client, token, parentWindow,
 9                    appOp[0], attrs, viewVisibility, session.mUid, userId,
10                    session.mCanAddInternalSystemWindow);
11    //addWindow中传入windowstate
12    mWindowMap.put(client.asBinder(), win);
13    win.mToken.addWindow(win);
14}
15
16final WindowState windowForClientLocked(Session session, IBinder client, boolean throwOnError) {
17    //从hashmap中去除WindowState
18    WindowState win = mWindowMap.get(client);
19    //校验WindowState的合法性
20    if (win == null) {
21        if (throwOnError) {
22            throw new IllegalArgumentException(
23                "Requested window " + client + " does not exist");
24        }
25        ProtoLog.w(WM_ERROR, "Failed looking up window session=%s callers=%s", session,
26                   Debug.getCallers(3));
27        return null;
28    }
29    if (session != null && win.mSession != session) {
30        if (throwOnError) {
31            throw new IllegalArgumentException("Requested window " + client + " is in session "
32                                               + win.mSession + ", not " + session);
33        }
34        ProtoLog.w(WM_ERROR, "Failed looking up window session=%s callers=%s", session,
35                   Debug.getCallers(3));
36        return null;
37    }
38
39    return win;
40}
2.5.3.3.10removeIfPossible

获取到WindowState容器,开始移除

 1//frameworks/base/services/core/java/com/android/server/wm/WindowState.java
 2@Override
 3void removeIfPossible() {
 4    //如果当前的WindowState存在mChildren,那么也需要对mChildren中的Window容器移除
 5    super.removeIfPossible();
 6    removeIfPossible(false /*keepVisibleDeadWindow*/);
 7}
 8
 9private void removeIfPossible(boolean keepVisibleDeadWindow) {
10    final DisplayContent displayContent = getDisplayContent();
11    final long origId = Binder.clearCallingIdentity();
12    try {
13        //[2.5.3.3.11]条件判断过滤,满足条件会直接 return 延迟删除操作
14        removeImmediately();
15        ...
16        //删除一个可见窗口将影响计算方向,如果需要,更新方向
17        if (needToSendNewConfiguration) {
18            displayContent.sendNewConfiguration();
19        }
20        //更新 WMS 的 Window 焦点
21        mWmService.updateFocusedWindowLocked(isFocused()
22                                             ? UPDATE_FOCUS_REMOVING_FOCUS
23                                             : UPDATE_FOCUS_NORMAL,
24                                             true /*updateInputWindows*/);
25        ...
26    } finally {
27        Binder.restoreCallingIdentity(origId);
28    }
29}
2.5.3.3.11removeImmediately
 1//frameworks/base/services/core/java/com/android/server/wm/WindowState.java
 2@Override
 3void removeImmediately() {
 4    //确保同时只能处理一个
 5    if (mRemoved) {
 6        // Nothing to do.
 7        ProtoLog.v(WM_DEBUG_ADD_REMOVE,
 8                   "WS.removeImmediately: %s Already removed...", this);
 9        return;
10    }
11
12    mRemoved = true;
13    super.removeImmediately();
14    final DisplayContent dc = getDisplayContent();
15    final int type = mAttrs.type;
16    //判断windowstate类型,只支持五种
17    //STATUS_BAR
18    //NOTIFICATION_SHADE
19    //NAVIGATION_BAR
20    //INPUT_METHOD_DIALOG
21    //VOLUME_OVERLAY
22    if (WindowManagerService.excludeWindowTypeFromTapOutTask(type)) {
23        dc.mTapExcludedWindows.remove(this);
24    }
25
26    // Remove this window from mTapExcludeProvidingWindows. If it was not registered, this will
27    // not do anything.
28    dc.mTapExcludeProvidingWindows.remove(this);
29    //这里移除对应的STATUS_BAR、NAVIGATION_BAR、CLIMATE_BAR、EXTRA_NAVIGATION_BAR之类的Window
30    dc.getDisplayPolicy().removeWindowLw(this);
31	//处理 Session 相关的逻辑,从 mSessions 中删除了当前 mSession ,并清除 mSession 对应的 SurfaceSession 资源
32    //SurfaceSession 是 SurfaceFlinger 的一个连接,通过这个连接可以创建多个 Surface 并渲染到屏幕上
33    mSession.windowRemovedLocked();
34    try {
35        //解除与客户端的连接
36        mClient.asBinder().unlinkToDeath(mDeathRecipient, 0);
37    } catch (RuntimeException e) {
38
39    }
40	//执行一些集中清理的工作
41    mWmService.postWindowRemoveCleanupLocked(this);
42}

2.5.4与View相关总结

2.5.4.1activity与 PhoneWindow与DecorView关系

DecorView是FrameLayout的子类, 它可以被认为是Android视图树的根节点视图

2.5.4.2WindowManagerGlobal 作用

设计模式中的桥接模式,在WindowManagerImpl和WMS之间起到了桥梁作用

2.5.4.3ViewRootImpl 作用

  1. View树的树根并管理View树
  2. 触发View的测量、 布局和绘制
  3. 输入响应的中转站
  4. 负责与WMS进行进程间通信

2.5.4.4View/ViewGroup结构

View/ViewGroup主要是树型结构

2.5.4.5绘制流程

2.5.4.6输入事件流程

2.5.4.7WMS通信

2.5.4.8屏幕刷新流程

ViewRootImpl会调用scheduleTraversals准备重绘, 但是,重绘一般不会立即执行, 而是往Choreographer的Choreographer.CALLBACK_TRAVERSAL队列中添加了一个mTraversalRunnable,同时申请VSYNC, 这个mTraversalRunnable要一直等到申请的VSYNC到来后才会被执行

2.6补充Window相关问题

2.6.1关于activity的xml怎么跟屏幕关联起来的

2.6.1.1performLaunchActivity

这里从应用启动开始,加载ActivityThread调用performLaunchActivity方法中的onCreate,直接调用的应用的onCreate方法

 1//frameworks/base/core/java/android/app/ActivityThread.java
 2private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
 3    ...
 4    if (r.isPersistable()) {
 5        mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
 6    } else {
 7        mInstrumentation.callActivityOnCreate(activity, r.state);
 8    }
 9    ...
10}

2.6.1.2App#onCreate

应用的代码如下

 1//App,这里用到的setContentView是AppCompatActivity中的
 2class MainActivity : AppCompatActivity() {
 3    private lateinit var binding: ActivityMainBinding
 4    override fun onCreate(savedInstanceState: Bundle?) {
 5        super.onCreate(savedInstanceState)
 6                // 使用了 ViewBinding
 7        binding = ActivityMainBinding.inflate(layoutInflater)
 8                // ① 加载 activity 的布局
 9        setContentView(binding.root)
10    }
11}

2.6.1.3AppCompatActivity#setContentView

1//androidx.appcompat:appcompat:1.3.12aar
2//androidx/appcompat/app/AppComPatActivity.java
3@Override
4public void setContentView(View view) {
5    //initViewTreeOwners方法作用
6    //在设置内容视图之前设置视图树所有者,以便膨胀过程和附加侦听器将看到它们已经存在
7    initViewTreeOwners();
8    getDelegate().setContentView(view);
9}

getDelegate

1//androidx.appcompat:appcompat:1.3.12aar
2//androidx/appcompat/app/AppComPatActivity.java
3public AppCompatDelegate getDelegate() {
4    if (mDelegate == null) {
5        //传入的两个参数都是AppComPatActivity
6        mDelegate = AppCompatDelegate.create(this, this);
7    }
8    return mDelegate;
9}

AppCompatActivity 将很多事都委托给了别人,就是 AppCompatDelegateImpl

1//androidx.appcompat:appcompat:1.3.12aar
2//androidx/appcompat/app/AppCompatDelegate.java
3public static AppCompatDelegate create(@NonNull Activity activity,
4                                       @Nullable AppCompatCallback callback) {
5    return new AppCompatDelegateImpl(activity, callback);
6}

2.6.1.4AppCompatDelegateImpl#setContentView

 1//androidx.appcompat:appcompat:1.3.12aar
 2//androidx/appcompat/app/AppCompatDelegateImpl.java
 3@Override
 4public void setContentView(View v) {
 5    ensureSubDecor();
 6    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
 7    contentParent.removeAllViews();
 8    //这里的addView跟动态添加View类似,都是父布局中添加布局,这的父布局是contentParent,子布局是v
 9    contentParent.addView(v);
10    ...
11}

2.6.1.5AppCompatDelegateImpl#ensureSubDecor

创建一个 SubDecor,然后找到里面 id 为 content 的控件,将从 xml 创建出来的 View 添加到 content 里面去。

我们看看这个 SubDecor 是啥,创建 SubDecor 的逻辑也不多:

 1//androidx.appcompat:appcompat:1.3.12aar
 2//androidx/appcompat/app/AppCompatDelegateImpl.java
 3private void ensureSubDecor() {
 4    if (!mSubDecorInstalled) {
 5        mSubDecor = createSubDecor();
 6        ...
 7    }
 8}
 9
10private ViewGroup createSubDecor() {
11    ...
12    //下面会根据不同的情况去加载不同的布局,即应用布局并不是固定不变的   
13    final LayoutInflater inflater = LayoutInflater.from(mContext);
14    ViewGroup subDecor = null;
15    if (!mWindowNoTitle) {
16        if (mIsFloating) {
17            // If we're floating, inflate the dialog title decor
18            subDecor = (ViewGroup) inflater.inflate(
19                R.layout.abc_dialog_title_material, null);
20        } else if (mHasActionBar) {
21            subDecor = (ViewGroup) LayoutInflater.from(themedContext)
22                .inflate(R.layout.abc_screen_toolbar, null);
23
24            mDecorContentParent = (DecorContentParent) subDecor
25                .findViewById(R.id.decor_content_parent);
26            mDecorContentParent.setWindowCallback(getWindowCallback());
27            ...
28        }
29    } else {
30        if (mOverlayActionMode) {
31            subDecor = (ViewGroup) inflater.inflate(
32                R.layout.abc_screen_simple_overlay_action_mode, null);
33        } else {
34            //本文是这里
35            subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
36        }
37    }
38
39    return subDecor;
40}

首先它会根据设置的 theme 来加载不同的 layout,比如这里调试的发现它使用的是 R.layout.abc_screen_simple,搜索一下,发现它的内容如下:

 1<!--androidx.appcompat:appcompat:1.3.12aar -->
 2<!--/res/layout/abc_screen_simple.xml -->
 3<androidx.appcompat.widget.FitWindowsLinearLayout
 4    xmlns:android="http://schemas.android.com/apk/res/android"
 5    android:id="@+id/action_bar_root"
 6    android:layout_width="match_parent"
 7    android:layout_height="match_parent"
 8    android:orientation="vertical"
 9    android:fitsSystemWindows="true">
10
11    <androidx.appcompat.widget.ViewStubCompat
12        android:id="@+id/action_mode_bar_stub"
13        android:inflatedId="@+id/action_mode_bar"
14        android:layout="@layout/abc_action_mode_bar"
15        android:layout_width="match_parent"
16        android:layout_height="wrap_content" />
17
18    <include layout="@layout/abc_screen_content_include" />
19
20</androidx.appcompat.widget.FitWindowsLinearLayout>

include 里面就是一个 androidx.appcompat.widget.ContentFrameLayout 没有其他布局。SubDecor 的整个布局如下

2.6.1.6AppCompatDelegateImpl#createSubDecor

上面已经提到了subDecor的初步创建

 1//androidx.appcompat:appcompat:1.3.12aar
 2//androidx/appcompat/app/AppCompatDelegateImpl.java
 3private ViewGroup createSubDecor() {
 4    subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
 5    ...
 6    //1.找到xml中的名称为action_bar_activity_content,类型为ContentFrameLayout的布局
 7    final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
 8        R.id.action_bar_activity_content);
 9    //2.这个mWindow布局来自框架screen_simple.xml,找到对应的布局android.R.id.content
10    final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
11    if (windowContentView != null) {
12        //3.这里是Window窗口的添加,具体见上文2.4
13        while (windowContentView.getChildCount() > 0) {
14            final View child = windowContentView.getChildAt(0);
15            windowContentView.removeViewAt(0);
16            contentView.addView(child);
17        }
18        //4.将布局windowContentView对应的的id从android.R.id.content变成View.NO_ID
19        windowContentView.setId(View.NO_ID);
20        //5.将对应布局contentView中的id从R.id.action_bar_activity_content变成android.R.id.content
21        contentView.setId(android.R.id.content);
22    }
23    //把子布局放到父布局中
24    mWindow.setContentView(subDecor);
25    return subDecor;
26}

其中abc_screen_simple.xml存在abc_screen_content_include布局,这个布局中有action_bar_activity_content布局

 1<!--androidx.appcompat:appcompat:1.3.12aar -->
 2<!--/res/layout/abc_screen_content_include.xml -->
 3<merge xmlns:android="http://schemas.android.com/apk/res/android">
 4
 5    <androidx.appcompat.widget.ContentFrameLayout
 6            android:id="@id/action_bar_activity_content"
 7            android:layout_width="match_parent"
 8            android:layout_height="match_parent"
 9            android:foregroundGravity="fill_horizontal|top"
10            android:foreground="?android:attr/windowContentOverlay" />
11
12</merge>

其中,上述的mWindow实际上PhoneWindow,mWindow.findViewById

1//frameworks/base/core/java/android/view/Window.java
2//可以得知这个mWindow布局是依托在DecorView中的
3@Nullable
4public <T extends View> T findViewById(@IdRes int id) {
5    return getDecorView().findViewById(id);
6}

getDecorView生成布局

 1//frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
 2protected ViewGroup generateLayout(DecorView decor) {
 3    //这里桶前文createSubDecor类似,也是根据不同的情况生成对应的布局
 4    int layoutResource;
 5    int features = getLocalFeatures();
 6    ...
 7    if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
 8        // If no other features and not embedded, only need a title.
 9        // If the window is floating, we need a dialog layout
10        if (mIsFloating) {
11            TypedValue res = new TypedValue();
12            getContext().getTheme().resolveAttribute(
13                R.attr.dialogTitleDecorLayout, res, true);
14            layoutResource = res.resourceId;
15        } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
16            layoutResource = a.getResourceId(
17                R.styleable.Window_windowActionBarFullscreenDecorLayout,
18                R.layout.screen_action_bar);
19        } else {
20            layoutResource = R.layout.screen_title;
21        }
22    } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
23        layoutResource = R.layout.screen_simple_overlay_action_mode;
24    } else {
25        //本文的布局在这里
26        layoutResource = R.layout.screen_simple;
27    }
28    return contentParent;
29}

即getDecorView对应的布局是screen_simple.xml

 1<!--/frameworks/base/core/res/res/layout/screen_simple.xml -->
 2<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3    android:layout_width="match_parent"
 4    android:layout_height="match_parent"
 5    android:fitsSystemWindows="true"
 6    android:orientation="vertical">
 7    <ViewStub android:id="@+id/action_mode_bar_stub"
 8              android:inflatedId="@+id/action_mode_bar"
 9              android:layout="@layout/action_mode_bar"
10              android:layout_width="match_parent"
11              android:layout_height="wrap_content"
12              android:theme="?attr/actionBarTheme" />
13    <FrameLayout
14         android:id="@android:id/content"
15         android:layout_width="match_parent"
16         android:layout_height="match_parent"
17         android:foregroundInsidePadding="false"
18         android:foregroundGravity="fill_horizontal|top"
19         android:foreground="?android:attr/windowContentOverlay" />
20</LinearLayout>

这里我们发现,subDecor 被添加到了 mContentParent 里面,mContentParent 是啥呢?它是DecorView里面的 id 叫 Content 的一个FrameLayout 控件。但是 subDecor 里面不也有一个 id 叫 Content 的控件吗?

这里解释一下,subDecor-content 在xml中的ID是 action_bar_activity_content,经过一番操作后,它的 id 会被代码替换成 R.id.content,而 decorView-content 控件的 id 被设置为了 NO_ID。由于 mContentParent 已经保存了 decorView-content 控件的引用,所以设置成 NO_ID 也没关系。

2.6.1.7完整的界面的布局如下

原理都是一样,只是使用AppCompatActivity继承的布局会多2层嵌套

应用程序继承Activity

应用程序继承AppCompatActivity

上屏流程如下

2.6.2关于锁屏之后为什么看不到Launcher

锁屏理论上也只是一个window盖在最顶层,理论即使这个锁屏window透明,那么透看到的也是有桌面Activity的情况,但现实情况是我们只看到了壁纸,并没有看到桌面。

首先我们知道壁纸属于系统中一个特殊窗口,一直处于系统最底层。这个窗口在系统中有专门类进行他的显示情况

1//frameworks/services/core/java/com/android/server/wm/WallpaperController.java
2if (DEBUG_WALLPAPER) {
3    Slog.v(TAG, "Wallpaper visibility: " + visible + " at display "
4            + mDisplayContent.getDisplayId());
5}

这里我们把DEBUG_WALLPAPER就可以把log打开了

 1WindowManager: Win Window{60c77f1 u0 ScreenDecorOverlayBottom}: isOnScreen=true mDrawState=4
 2WindowManager: Win Window{df0a78b u0 ScreenDecorOverlay}: isOnScreen=true mDrawState=4
 3WindowManager: Win Window{b7a35a u0 NavigationBar0}: isOnScreen=true mDrawState=4
 4WindowManager: Win Window{3cd76e8 u0 NotificationShade}: isOnScreen=true mDrawState=4
 5WindowManager: Found wallpaper target: Window{3cd76e8 u0 NotificationShade}
 6WindowManager: New wallpaper target: Window{3cd76e8 u0 NotificationShade} prevTarget: null
 7WindowManager: Report new wp offset Window{803c2b8 u0 com.android.systemui.ImageWallpaper} x=0.0 y=0.5 zoom=0.0
 8WindowManager: Wallpaper visibility: true at display 0
 9WindowManager: Wallpaper token android.os.Binder@3a6eda2 visible=true
10WindowManager: New wallpaper: target=Window{3cd76e8 u0 NotificationShade} prev=null
11WindowManager: powerPress: eventTime=118823847 interactive=true count=0 beganFromNonInteractive=true mShortPressOnPowerBehavior=1

打开后我们点亮锁屏和在桌面锁屏发现都有类似以下打印

WindowManager: Found wallpaper target: Window{3cd76e8 u0 NotificationShade}

看名字大家大概就知道这个log是在寻找一个wallpaper target:,而且找到的是NotificationShade即锁屏窗口

前面疑惑中就写到正常应该是桌面

WindowManager: Found wallpaper target: Window{8d79f3 u0 com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher}

这日志其实就可以给我们非常重要线索,可以找出对应代码在哪

 1//frameworks/services/core/java/com/android/server/wm/WallpaperController.java
 2final boolean hasWallpaper = w.hasWallpaper() || animationWallpaper;
 3boolean hasWallpaper() {
 4    return (mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0
 5        || (mActivityRecord != null && mActivityRecord.hasWallpaperBackgroudForLetterbox());
 6}
 7https://blog.csdn.net/learnframework/article/details/127483545
 8private final ToBooleanFunction<WindowState> mFindWallpaperTargetFunction = w -> {
 9    ...
10    else if (hasWallpaper && w.isOnScreen()
11    ...
12};

这里其实也就是判断window中是否有FLAG_SHOW_WALLPAPER属性,而且经过额外加log打印发现hasWallpaper值变化在锁屏window解锁前后

那么其实我们可以猜测是不是锁屏window会去动态改变自己的FLAG_SHOW_WALLPAPER属性,在有桌面显示时候锁屏的window实际是没有这个属性,在锁屏状态下是有这个属性。根据猜想去systemui代码中grep相关FLAG_SHOW_WALLPAPER关键字

 1//frameworks/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
 2private void applyKeyguardFlags(State state) {
 3    ...
 4    if ((keyguardOrAod && !state.mBackdropShowing && !scrimsOccludingWallpaper)
 5        || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe()) {
 6        mLpChanged.flags |= LayoutParams.FLAG_SHOW_WALLPAPER;
 7    } else {
 8        mLpChanged.flags &= ~LayoutParams.FLAG_SHOW_WALLPAPER;
 9    }
10}

这里其实就是代表有锁屏时候需要有 mLpChanged.flags |= LayoutParams.FLAG_SHOW_WALLPAPER;

锁屏退出时清除锁屏的FLAG_SHOW_WALLPAPER

mLpChanged.flags &= ~LayoutParams.FLAG_SHOW_WALLPAPER;

2.6.3Activity究竟对应几个Window

上面提到一个Activity对应一个PhoneWindow,那么为什么图还存在一个Activity可以出现两个Window,似乎有所矛盾

提到这个问题,我们必须要先复现当前问题

名称 说明
手机型号 红米K30pro
Android版本 Android10
测试场景 屏幕点亮的Launcher界面
后台应用 Wechat

如下图所示

 1# 这里输出简化表示
 2picasso:/ $ dumpsys window windows
 3WINDOW MANAGER WINDOWS (dumpsys window windows)
 4Window #0 Window{54fd848 u0 InputMethod}:
 5  mBaseLayer=141000 mSubLayer=0    mToken=WindowToken{226fcc5 android.os.Binder@3f373c}
 6  isVisible=false
 7Window #1 Window{6474d57 u0 RoundCorner}:
 8  mBaseLayer=311000 mSubLayer=0    mToken=WindowToken{7706d6 android.os.BinderProxy@359caf1}
 9  isVisible=true
10Window #2 Window{98435fe u0 RoundCorner}:
11  mBaseLayer=311000 mSubLayer=0    mToken=WindowToken{a6ec0b2 android.os.BinderProxy@1af12bd}
12  isVisible=true
13Window #3 Window{987738d u0 NavigationBar}:
14  mBaseLayer=231000 mSubLayer=0    mToken=WindowToken{cdfac24 android.os.BinderProxy@1bf9cb7}
15  isVisible=true
16Window #4 Window{85d4239 u0 StatusBar}:
17  mBaseLayer=181000 mSubLayer=0    mToken=WindowToken{e3e1a00 android.os.BinderProxy@bf7a332}
18  isVisible=true
19Window #5 Window{5c8904f u0 RoundCorner}:
20  mBaseLayer=171000 mSubLayer=0    mToken=WindowToken{c281aae android.os.BinderProxy@485e729}
21  isVisible=false
22Window #6 Window{e12503c u0 Aspect}:
23  mBaseLayer=111000 mSubLayer=0    mToken=WindowToken{cb4622f android.os.BinderProxy@bba880e}
24  isVisible=false
25Window #7 Window{31fbd71 u0 AssistPreviewPanel}:
26  mBaseLayer=41000 mSubLayer=0    mToken=WindowToken{f8d1f18 android.os.BinderProxy@c09ec8a}
27  isVisible=false
28Window #8 Window{67faf21 u0 DockedStackDivider}:
29  mBaseLayer=21000 mSubLayer=0    mToken=WindowToken{f740988 android.os.BinderProxy@d47412b}
30  isVisible=false
31Window #9 Window{19af82e u0 LauncherOverlayWindow:com.miui.personalassistant}:
32  mBaseLayer=21000 mSubLayer=0    mToken=AppWindowToken{e921607 token=Token{361c446 ActivityRecord{6d5b688 u0 com.miui.home/.launcher.Launcher t1}}}
33  isVisible=false
34Window #10 Window{6163c5a u0 com.miui.home/com.miui.home.launcher.Launcher}:
35  mBaseLayer=21000 mSubLayer=0    mToken=AppWindowToken{e921607 token=Token{361c446 ActivityRecord{6d5b688 u0 com.miui.home/.launcher.Launcher t1}}}
36  isVisible=true
37Window #11 Window{d7d87d0 u0 com.tencent.mm/com.tencent.mm.ui.LauncherUI}:
38  mBaseLayer=21000 mSubLayer=0    mToken=AppWindowToken{e299b34 token=Token{4ee9e07 ActivityRecord{ebb6c46 u0 com.tencent.mm/.ui.LauncherUI t87}}}
39  isVisible=false
40Window #12 Window{f63516 u0 com.android.keyguard.wallpaper.service.MiuiKeyguardLiveWallpaper}:
41  mBaseLayer=11000 mSubLayer=0    mToken=WallpaperWindowToken{f661030 token=android.os.BinderProxy@3481973}
42  isVisible=true
43Window #13 Window{cf10cf2 u0 com.android.systemui.ImageWallpaper}:
44  mBaseLayer=11000 mSubLayer=0    mToken=WallpaperWindowToken{5182c91 token=android.os.Binder@2b356b8}
45  isVisible=true

可以看到有14个Window,但不是所有Window都显示的,只有7个Window才显示出来的,其中可以看到有9,10两个Window对应同一个WindowToken,并且mBaseLayer和mSubLayer都相同,根据上面的Window层级关系,实际上就是一个Activity会存在两个Window,似乎跟上文的一个一个Activity对应一个PhoneWindow矛盾了。

实际上不然,9和10两个Window这个Activity相同,WindowToken相同,这个类似悬浮窗,虽然是同一个Activity,前者9属于一个最小化的图标,后者10才是真正意义上的一个Activity对应一个PhoneWindow,这是一种Overlay机制。并且9通常是隐藏的,10才是可以显示出来的。Window可以两者存在,并且可以同时显示。

最终显示结果从上往下

2.6.4Activity、Dialog、PopupWindow和Toast 的Window创建

2.6.4.1 Activity的Window创建

Activity在ActivityThread中调用performLaunchActivity方法中的Activity#attach

2.6.4.1.1Activity#attach

通过代码可以知道在 attach 方法中,创建了 Window

 1//frameworks/base/core/java/android/app/Activity.java
 2private Window mWindow;
 3final void attach(Context context, ActivityThread aThread,
 4            Instrumentation instr, IBinder token, int ident,
 5            Application application, Intent intent, ActivityInfo info,
 6            CharSequence title, Activity parent, String id,
 7            NonConfigurationInstances lastNonConfigurationInstances,
 8            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
 9            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
10            IBinder shareableActivityToken) {
11	//创建 PhoneWindow
12    mWindow = new PhoneWindow(this, window, activityConfigCallback);
13	...
14}
2.6.4.1.2Activity#setContentView

Activity 的视图是通过 setContentView 方法提供的,如果是AppCompatActivity类中的setContentView ,请参考2.6.1的流程,这里阐述直接的Activity 类中的setContentView

1//frameworks/base/core/java/android/app/Activity.java
2public void setContentView(@LayoutRes int layoutResID) {
3    //这里的getWindow对应PhoneWindow
4    getWindow().setContentView(layoutResID);
5    initWindowDecorActionBar();
6}
2.6.4.1.3PhoneWindow#setContentView
 1//frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
 2@Override
 3public void setContentView(int layoutResID) {
 4    if (mContentParent == null) {
 5        // 1,创建 DecorView
 6        installDecor();
 7    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
 8        mContentParent.removeAllViews();
 9    }
10
11    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
12        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
13                                                       getContext());
14        transitionTo(newScene);
15    } else {
16        //2 添加 activity 的布局文件
17        mLayoutInflater.inflate(layoutResID, mContentParent);
18    }
19    mContentParent.requestApplyInsets();
20    final Callback cb = getCallback();
21    if (cb != null && !isDestroyed()) {
22        //通知 window 的callback 接口回调
23        cb.onContentChanged();
24    }
25    mContentParentExplicitlySet = true;
26}

如果没有 DecorView 就创建它,一般来说它内部包含了标题栏和内容栏,但是这个会随着主题的改变而发生改变。但是不管怎么样,内容栏是一定存在的,并且内容栏有固定的 id content,完整的 id 是 android.R.id.content

  1. 通过 generateDecor 创建了 DecorView,接着会调用 generateLayout 来加载具体的布局文件到 DecorView 中,这个要加载的布局就和系统版本以及定义的主题有关了。加载完之后就会将内容区域的 View 返回出来,也就是 mContentParent
  2. 将 activity 需要显示的布局添加到 mcontentParent
  3. 由于 activity 实现了 window 的callback 接口,这里表示 activity 的布局文件已经被添加到 decorView 的 mParentView 中了,于是通知 接口回调
2.6.4.1.4 handlerResumeActivity

ActivityThread 的 handlerResumeActivity 中,会调用 activity 的 onResume 方法,接着就会将 DecorView 添加到 Window 中

 1@Override
 2public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
 3                                 boolean isForward, boolean shouldSendCompatFakeFocus, String reason) {
 4    if (!performResumeActivity(r, finalStateRequest, reason)) {
 5        return;
 6    }
 7
 8    final Activity a = r.activity;
 9    if (r.window == null && !a.mFinished && willBeVisible) {
10        r.window = r.activity.getWindow();
11        View decor = r.window.getDecorView();
12        decor.setVisibility(View.INVISIBLE);
13        ViewManager wm = a.getWindowManager();
14        WindowManager.LayoutParams l = r.window.getAttributes();
15        a.mDecor = decor;
16        //这里可以看到type是TYPE_BASE_APPLICATION
17        l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
18        l.softInputMode |= forwardBit;
19        
20        if (a.mVisibleFromClient) {
21            if (!a.mWindowAdded) {
22                a.mWindowAdded = true;
23                //这里就是addView流程了,不在展开描述
24                wm.addView(decor, l);
25            } 
26        }
27    }
28}

可以看到type类型是TYPE_BASE_APPLICATION,有上文的层级关系流程,在WindowManagerPolicy类中查找对应的主次层级的序号

1//frameworks/base/services/core/java/com/android/server/policy/WindowManagerPolicy.java
2default int getWindowLayerFromTypeLw(int type, boolean canAddInternalSystemWindow,
3                                     boolean roundedCornerOverlay) {
4	...
5    //TYPE_BASE_APPLICATION值为1,正好在[1,99]范围内,返回APPLICATION_LAYER,值为2
6    if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
7        return APPLICATION_LAYER;
8    }
9}

即Activity所在层级为App层级,主层级为2

2.6.4.2Dialog的Window创建

2.6.4.2.1Dialog::Dialog

Dialog 中创建 Window 是在其构造方法中完成

 1//frameworks/base/core/java/android/app/Dialog.java
 2private final WindowManager mWindowManager;
 3final Context mContext;
 4final Window mWindow;
 5
 6public Dialog(@UiContext @NonNull Context context, @StyleRes int themeResId) {
 7    this(context, themeResId, true);
 8}
 9
10Dialog(@UiContext @NonNull Context context, @StyleRes int themeResId,
11       boolean createContextThemeWrapper) {
12	//获取 WindowManager
13    mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
14	//创建 Window
15    final Window w = new PhoneWindow(mContext);
16    mWindow = w;
17    ...
18}
2.6.4.2.2Dialog#setContentView

初始化 DecorView,将 dialog 的视图添加到 DecorView 中。这个和 activity 的类似,都是通过 Window 去添加指定的布局文件

1//frameworks/base/core/java/android/app/Dialog.java
2public void setContentView(@LayoutRes int layoutResID) {
3    mWindow.setContentView(layoutResID);
4}
2.6.4.2.3Dialog#show

将 DecorView 添加到 Window 中显示

1//frameworks/base/core/java/android/app/Dialog.java
2public void show() {
3	//...
4    mDecor = mWindow.getDecorView();
5    WindowManager.LayoutParams l = mWindow.getAttributes();
6    mWindowManager.addView(mDecor, l);
7	//发送回调消息
8    sendShowMessage();
9}
2.6.4.2.4mWindow.getAttributes

这里没有显示type类型,追溯mWindow.getAttributes()

1//frameworks/base/core/java/android/view/Window.java
2private final WindowManager.LayoutParams mWindowAttributes =
3        new WindowManager.LayoutParams();
2.6.4.2.5WindowManager.LayoutParams构造

这里可以看出默认如果没有设置type类型,默认的类型是TYPE_APPLICATION,跟上面Activity类似,都属于主层级为2

1//frameworks/base/core/java/android/view/WindowManager.java
2public LayoutParams() {
3    super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
4    type = TYPE_APPLICATION;
5    format = PixelFormat.OPAQUE;
6}

从上面几个步骤可以发现,Dialog 的 Window 创建和 Activity 的 Window 创建很类似,二者几乎没有什么区别。

2.6.4.2.6Dialog#dismissDialog

当 dialog 关闭时,它会通过 WindowManager来移除 DecorView

 1//frameworks/base/core/java/android/app/Dialog.java
 2@UnsupportedAppUsage
 3void dismissDialog() {
 4	...
 5    try {
 6        //移除操作
 7        mWindowManager.removeViewImmediate(mDecor);
 8    } finally {
 9        if (mActionMode != null) {
10            mActionMode.finish();
11        }
12        mDecor = null;
13        mWindow.closeAllPanels();
14        onStop();
15        mShowing = false;
16
17        sendDismissMessage();
18    }
19}

普通的 Dialog 有一个特殊的地方,就是必须采用 Activity 的 Context,如果采用 Application 的 Context,就会报错。

错误信息很明确,是没有 Token 导致的,而 Token 一般只有 Activity 拥有,所以这里只需要用 Activity 作为 Context 即可。

1Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
2.6.4.2.7window#setType

系统 Window 比较特殊,他可以不需要 Token,我们可以将 Dialog 的 Window Type 修改为系统类型就可以了

可以调用Dialog实例化对象的window.setType()。Type类型可以是系统的WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY(对应主层级11)或者是WindowManager.LayoutParams.TYPE_SYSTEM_ALERT(对应主层级9)

额外需要申请需要申请悬浮窗权限即可

1//frameworks/base/core/java/android/view/Window.java
2public void setType(int type) {
3    final WindowManager.LayoutParams attrs = getAttributes();
4    attrs.type = type;
5    dispatchWindowAttributesChanged(attrs);
6}

2.6.4.3PopupWindow的Window创建

2.6.4.3.1PopupWindow#PopupWindow
 1//frameworks/base/core/java/android/widget/PopupWindow.java
 2public PopupWindow(Context context, AttributeSet attrs) {
 3    this(context, attrs, com.android.internal.R.attr.popupWindowStyle);
 4}
 5
 6public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
 7    mContext = context;
 8    mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
 9    ...
10}
2.6.4.3.2PopupWindow#setContentView

可以看到,和刚才看到的构造函数基本相同,保存了ContentView变量后,获取ContextWindowManger对象。

 1//frameworks/base/core/java/android/widget/PopupWindow.java
 2public void setContentView(View contentView) {
 3    mContentView = contentView;
 4    if (mContext == null && mContentView != null) {
 5        mContext = mContentView.getContext();
 6    }
 7
 8    if (mWindowManager == null && mContentView != null) {
 9        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
10    }
11
12    if (mContext != null && !mAttachedInDecorSet) {
13
14        setAttachedInDecor(mContext.getApplicationInfo().targetSdkVersion
15                           >= Build.VERSION_CODES.LOLLIPOP_MR1);
16    }
17
18}
2.6.4.3.3PopupWindow#showAsDropDown
 1//frameworks/base/core/java/android/widget/PopupWindow.java
 2public void showAsDropDown(View anchor) {
 3    showAsDropDown(anchor, 0, 0);
 4}
 5
 6public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
 7    TransitionManager.endTransitions(mDecorView);
 8	//绑定监听,设置变量
 9    attachToAnchor(anchor, xoff, yoff, gravity);
10
11    mIsShowing = true;
12    mIsDropdown = true;
13	//创建LayoutParams
14    final WindowManager.LayoutParams p =
15        createPopupLayoutParams(anchor.getApplicationWindowToken());
16    //准备Popupwindow
17    preparePopup(p);
18
19    final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
20                                                     p.width, p.height, gravity, mAllowScrollingAnchorParent);
21    updateAboveAnchor(aboveAnchor);
22    p.accessibilityIdOfAnchor = (anchor != null) ? anchor.getAccessibilityViewId() : -1;
23	//添加布局到Window中
24    invokePopup(p);
25}
2.6.4.3.4PopupWindow#createPopupLayoutParams

可以看到创建默认的LayoutParams时候类型为TYPE_APPLICATION_PANEL,说明PopupWindow是一个子Window,主层级关系就是2,次层级关系是1,也就是说会覆盖在App层级之上

 1//frameworks/base/core/java/android/widget/PopupWindow.java
 2private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
 3@UnsupportedAppUsage
 4protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
 5    final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
 6
 7    p.gravity = computeGravity();
 8    p.flags = computeFlags(p.flags);
 9    p.type = mWindowLayoutType;
10    p.token = token;
11    p.softInputMode = mSoftInputMode;
12    p.windowAnimations = computeAnimationResource();
13    
14    p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH
15        | PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
16    return p;
17}
2.6.4.3.5PopupWindow#preparePopup
 1//frameworks/base/core/java/android/widget/PopupWindow.java
 2@UnsupportedAppUsage
 3private void preparePopup(WindowManager.LayoutParams p) {
 4    //设置Background包裹
 5    if (mBackground != null) {
 6        mBackgroundView = createBackgroundView(mContentView);
 7        mBackgroundView.setBackground(mBackground);
 8    } else {
 9        mBackgroundView = mContentView;
10    }
11	//这里创建根布局,包裹mBackground
12    mDecorView = createDecorView(mBackgroundView);
13    mDecorView.setIsRootNamespace(true);
14    ...
15}
2.6.4.3.6PopupWindow#createBackgroundView

显然它的布局就是PopupBackgroundView

 1//frameworks/base/core/java/android/widget/PopupWindow.java
 2private PopupBackgroundView createBackgroundView(View contentView) {
 3    final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
 4    final int height;
 5    if (layoutParams != null && layoutParams.height == WRAP_CONTENT) {
 6        height = WRAP_CONTENT;
 7    } else {
 8        height = MATCH_PARENT;
 9    }
10
11    final PopupBackgroundView backgroundView = new PopupBackgroundView(mContext);
12    final PopupBackgroundView.LayoutParams listParams = new PopupBackgroundView.LayoutParams(
13        MATCH_PARENT, height);
14    backgroundView.addView(contentView, listParams);
15
16    return backgroundView;
17}
2.6.4.3.7PopupWindow#createDecorView

显然它的布局就是PopupDecorView

 1//frameworks/base/core/java/android/widget/PopupWindow.java
 2private PopupDecorView createDecorView(View contentView) {
 3    final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
 4    final int height;
 5    
 6    if (layoutParams != null && layoutParams.height == WRAP_CONTENT) {
 7        height = WRAP_CONTENT;
 8    } else {
 9        height = MATCH_PARENT;
10    }
11
12    final PopupDecorView decorView = new PopupDecorView(mContext);
13    decorView.addView(contentView, MATCH_PARENT, height);
14    decorView.setClipChildren(false);
15    decorView.setClipToPadding(false);
16
17    return decorView;
18}
2.6.4.3.8PopupWindow#invokePopup

最终添加window,可以看出根布局是PopupDecorView,里面包裹着PopupBackgroundView,mContentView就是传入的View

 1//frameworks/base/core/java/android/widget/PopupWindow.java
 2@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
 3private void invokePopup(WindowManager.LayoutParams p) {
 4    if (mContext != null) {
 5        p.packageName = mContext.getPackageName();
 6    }
 7
 8    final PopupDecorView decorView = mDecorView;
 9    decorView.setFitsSystemWindows(mLayoutInsetDecor);
10
11    setLayoutDirectionFromAnchor();
12	//addView
13    mWindowManager.addView(decorView, p);
14
15    if (mEnterTransition != null) {
16        decorView.requestEnterTransition(mEnterTransition);
17    }
18}

PopupWindow它是一个子Window,需要设置ContentView

利用自定义View包裹我们的ContentView,自定义View重写了键盘事件和触摸事件分发,实现了点击外部消失

最终利用WindowManger的addView加入布局,并没有创建新的Window

2.6.4.4Toast的Window创建

Toast 也是基于 Window 来实现的,但是他的工作过程有些复杂。Toast有进程间的通信,Toast对应服务为NotificationManagerService和ITransientNotification。

Toast 属于系统 Window,内部视图有两种定义方式,一种是系统默认的,另一种是通过 setView 方法来指定一个 View(setView 方法在 android 11 以后已经废弃了,不会再展示自定义视图),他们都对应 Toast 的一个内部成员 mNextView

2.6.4.4.3Toast#makeText
 1//frameworks/base/core/java/android/widget/Toast.java
 2public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
 3                             @NonNull CharSequence text, @Duration int duration) {
 4    //这里主要是构造[2.6.4.4.2]
 5    Toast result = new Toast(context, looper);
 6
 7    if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
 8        result.mText = text;
 9    } else {
10        //然后将mNextView赋值为getTextToastView[2.6.4.4.6]
11        result.mNextView = ToastPresenter.getTextToastView(context, text);
12    }
13
14    result.mDuration = duration;
15    return result;
16}
2.6.4.4.2Toast#Toast

在Toast构造中,可以发现内部维护了一个mTN的类属性,里面还有回调的可扩容数组

 1//frameworks/base/core/java/android/widget/Toast.java
 2final TN mTN;
 3public Toast(Context context) {
 4    this(context, null);
 5}
 6
 7public Toast(@NonNull Context context, @Nullable Looper looper) {
 8    //context是外部App端调用传入的值
 9    mContext = context;
10    mToken = new Binder();
11    looper = getLooper(looper);
12    mHandler = new Handler(looper);
13    //回调的可扩容数组
14    mCallbacks = new ArrayList<>();
15    mTN = new TN(context, context.getPackageName(), mToken,
16                 mCallbacks, looper);
17   ...
18}
2.6.4.4.3TN#TN

这里的ToastPresenter实例化对象很关键,是后续Toast内部维护View的桥梁,真正加载布局的地方

 1//frameworks/base/core/java/android/widget/Toast.java
 2//这个就是上述服务类的实现,实现在Toast类的内部
 3private static class TN extends ITransientNotification.Stub {
 4    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
 5    private final WindowManager.LayoutParams mParams;
 6    private final ToastPresenter mPresenter;
 7    TN(Context context, String packageName, Binder token, List<Callback> callbacks,
 8       @Nullable Looper looper) {
 9        IAccessibilityManager accessibilityManager = IAccessibilityManager.Stub.asInterface(
10            ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
11        //初始化ToastPresenter
12        mPresenter = new ToastPresenter(context, accessibilityManager, getService(),
13                                        packageName);
14        mParams = mPresenter.getLayoutParams();
15        mPackageName = packageName;
16        mToken = token;
17        mCallbacks = callbacks;
18        ...
19    }
20}
2.6.4.4.4ToastPresenter#ToastPresenter
 1//frameworks_base/core/java/android/widget/ToastPresenter.java
 2public ToastPresenter(Context context, IAccessibilityManager accessibilityManager,
 3                      INotificationManager notificationManager, String packageName) {
 4    mContext = context;
 5    mResources = context.getResources();
 6    mWindowManager = context.getSystemService(WindowManager.class);
 7    mNotificationManager = notificationManager;
 8    mPackageName = packageName;
 9    mAccessibilityManager = accessibilityManager;
10    //创建默认的LayoutParams
11    mParams = createLayoutParams();
12}
2.6.4.4.5ToastPresenter#createLayoutParams

找到Toast的window type类型为TYPE_TOAST,对应层级关系主层级为7。结果上面2.6.4.2的Dialog,如果没有设置Type那么Toast会在Dialog上面,如果设置系统Type,那么Dialog会覆盖Toast。

 1//frameworks_base/core/java/android/widget/ToastPresenter.java
 2private WindowManager.LayoutParams createLayoutParams() {
 3    WindowManager.LayoutParams params = new WindowManager.LayoutParams();
 4    params.height = WindowManager.LayoutParams.WRAP_CONTENT;
 5    params.width = WindowManager.LayoutParams.WRAP_CONTENT;
 6    params.format = PixelFormat.TRANSLUCENT;
 7    params.windowAnimations = R.style.Animation_Toast;
 8    //这里的type类型是TYPE_TOAST
 9    params.type = WindowManager.LayoutParams.TYPE_TOAST;
10    params.setFitInsetsIgnoringVisibility(true);
11    params.setTitle(WINDOW_TITLE);
12    params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
13        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
14        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
15    setShowForAllUsersIfApplicable(params, mPackageName);
16    return params;
17}
2.6.4.4.6ToastPresenter#getTextToastView

这里可以发现Toast的布局,实际上是在ToastPresenter中添加对应布局,根布局为R.layout.transient_notification,我们所在的text就是内部的com.android.internal.R.id.message所在位置。

1//frameworks_base/core/java/android/widget/ToastPresenter.java
2@VisibleForTesting
3public static final int TEXT_TOAST_LAYOUT = R.layout.transient_notification;
4public static View getTextToastView(Context context, CharSequence text) {
5    View view = LayoutInflater.from(context).inflate(TEXT_TOAST_LAYOUT, null);
6    TextView textView = view.findViewById(com.android.internal.R.id.message);
7    textView.setText(text);
8    return view;
9}

其中xml为

 1<!-- //frameworks/base/core/res/res/layout/transient_notification.xml-->
 2<LinearLayout
 3    xmlns:android="http://schemas.android.com/apk/res/android"
 4    android:layout_width="wrap_content"
 5    android:layout_height="wrap_content"
 6    android:orientation="horizontal"
 7    android:gravity="center_vertical"
 8    android:maxWidth="@dimen/toast_width"
 9    android:background="?android:attr/toastFrameBackground"
10    android:elevation="@dimen/toast_elevation"
11    android:layout_marginEnd="16dp"
12    android:layout_marginStart="16dp"
13    android:paddingStart="16dp"
14    android:paddingEnd="16dp">
15
16    <TextView
17        android:id="@android:id/message"
18        android:layout_width="wrap_content"
19        android:layout_height="wrap_content"
20        android:ellipsize="end"
21        android:maxLines="2"
22        android:paddingTop="12dp"
23        android:paddingBottom="12dp"
24        android:textAppearance="@style/TextAppearance.Toast"/>
25</LinearLayout>
2.6.4.4.7Toast#show
 1//frameworks/base/core/java/android/widget/Toast.java
 2public void show() {
 3    INotificationManager service = getService();
 4    String pkg = mContext.getOpPackageName();
 5    TN tn = mTN;
 6    tn.mNextView = mNextView;
 7    final int displayId = mContext.getDisplayId();
 8
 9    try {
10        if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
11            if (mNextView != null) {
12                //mNextView文字存在
13                service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
14            } else {
15               ...
16            }
17        } 
18    } catch (RemoteException e) {
19        // Empty
20    }
21}
2.6.4.4.8NotificationManagerService#enqueueTextToast

上面代码中对给定应用的 toast 数量进行判断,如果超过 50 条,就直接退出,这是为了防止 DOS ,如果某个应用一直循环弹出 toast 就会导致其他应用无法弹出,这显然是不合理的。

 1//frameworks_base/services/core/java/com/android/server/notification/NotificationManagerService.java
 2@Override
 3public void enqueueToast(String pkg, IBinder token, ITransientNotification callback,
 4                         int duration, int displayId) {
 5    enqueueToast(pkg, token, null, callback, duration, displayId, null);
 6}
 7//传入上面的callback,类型为ITransientNotificationCallback,指的是mTN
 8private void enqueueToast(String pkg, IBinder token, @Nullable CharSequence text,
 9                          @Nullable ITransientNotification callback, int duration, int displayId,
10                          @Nullable ITransientNotificationCallback textCallback) {
11
12    synchronized (mToastQueue) {
13        try {
14            //创建对应的 ToastRecord
15            ToastRecord record;
16            int index = indexOfToastLocked(pkg, token);
17            // If it's already in the queue, we update it in place, we don't
18            // move it to the end of the queue.
19            if (index >= 0) {
20                //实际上就是获取队列中的record元素
21                record = mToastQueue.get(index);
22                record.update(duration);
23            } else {
24                ...
25                    //如果超过 50 条,就直接退出    
26                    if (count >= MAX_PACKAGE_TOASTS) {
27                        Slog.e(TAG, "Package has already queued " + count
28                               + " toasts. Not showing more. Package=" + pkg);
29                        return;
30                    }
31                //创建对应的 ToastRecord
32                record = getToastRecord(callingUid, callingPid, pkg, isSystemToast, token,text, callback, duration, windowToken, displayId, textCallback);
33            }
34            // 0 表示只有一个 toast了,直接显示,否则就是还有toast,排队等待显示
35            if (index == 0) {
36                showNextToastLocked(false);
37            }
38        } finally {
39            Binder.restoreCallingIdentity(callingId);
40        }
41    }
42}
2.6.4.4.9NotificationManagerService#getToastRecord

说明这里的ToastRecord有两种类型,分别为TextToastRecord和CustomToastRecord,我们这里存在callback,所以为CustomToastRecord

 1//frameworks_base/services/core/java/com/android/server/notification/NotificationManagerService.java
 2private ToastRecord getToastRecord(int uid, int pid, String packageName, boolean isSystemToast,
 3                                   IBinder token, @Nullable CharSequence text, @Nullable ITransientNotification callback,
 4                                   int duration, Binder windowToken, int displayId,
 5                                   @Nullable ITransientNotificationCallback textCallback) {
 6    if (callback == null) {
 7        return new TextToastRecord(this, mStatusBar, uid, pid, packageName,
 8                                   isSystemToast, token, text, duration, windowToken, displayId, textCallback);
 9    } else {
10        return new CustomToastRecord(this, uid, pid, packageName,
11                                     isSystemToast, token, callback, duration, windowToken, displayId);
12    }
13}
2.6.4.4.10NotificationManagerService#showNextToastLocked

展示下一个Toast

 1//frameworks_base/services/core/java/com/android/server/notification/NotificationManagerService.java
 2@GuardedBy("mToastQueue")
 3void showNextToastLocked(boolean lastToastWasTextRecord) {
 4    //获取队头元素
 5    ToastRecord record = mToastQueue.get(0);
 6    while (record != null) {
 7        //存在队头元素,处理元素
 8        if (tryShowToast(
 9            record, rateLimitingEnabled, isWithinQuota, isPackageInForeground)) {
10            scheduleDurationReachedLocked(record, lastToastWasTextRecord);
11            mIsCurrentToastShown = true;
12            if (rateLimitingEnabled && !isPackageInForeground) {
13                mToastRateLimiter.noteEvent(userId, record.pkg, TOAST_QUOTA_TAG);
14            }
15            return;
16        }
17    }
18}
19
20private boolean tryShowToast(ToastRecord record, boolean rateLimitingEnabled,
21                             boolean isWithinQuota, boolean isPackageInForeground) {
22    return record.show();
23}
2.6.4.4.11CustomToastRecord#show

显然,这里有回调到对应的Callback中,callback.show

 1//frameworks/base/services/core/java/com/android/server/notification/toast/CustomToastRecord.java
 2public final ITransientNotification callback;
 3public boolean show() {
 4    if (DBG) {
 5        Slog.d(TAG, "Show pkg=" + pkg + " callback=" + callback);
 6    }
 7    try {
 8        //这里的callback指的是mTN
 9        callback.show(windowToken);
10        return true;
11    } catch (RemoteException e) {
12		...
13    }
14}
2.6.4.4.12Toast#TN#show

最终回调到Toast内部类TN的show

 1//frameworks/base/core/java/android/widget/Toast.java
 2private static class TN extends ITransientNotification.Stub {
 3    @Override
 4    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
 5    public void show(IBinder windowToken) {
 6        if (localLOGV) Log.v(TAG, "SHOW: " + this);
 7        mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
 8    }
 9}
10
11public void handleShow(IBinder windowToken) {
12    if (mView != mNextView) {
13        ...
14        //最终在mPresenter中show
15        mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY,
16                        mHorizontalMargin, mVerticalMargin,
17                        new CallbackBinder(getCallbacks(), mHandler));
18    }
19}

Toast 的窗口类型是 TYPE_TOAST,属于系统类型,Toast 有自己的 token,不受 Activity 控制。

Toast 通过 WindowManager 将 view 直接添加到了 Window 中,并没有创建 PhoneWindowDecorView,这点和 Activity 与 Dialog 不同。

2.6.4.5总结

Activity和Dialog都会创建PhoneWindow和DecorView,即这两个是有根布局的,而Toast不创建PhoneWindow和DecorView,其根布局是transient_notification.xml。PopupWindow是子窗口,也没有所谓的PhoneWindow和DecorView,层级关系是主层级为2,子层级为1。

Activity和Dialog流程类似,详看2.6.1

PopupWindow流程

Toast流程

3总结

关于专业名词的罗列

名称 含义
Window 一个窗口,处理顶级窗口外观和行为策略的抽象类,这个窗口承载了需要绘制的View
PhoneWindow Window的具体实现类
WindowManager 一个接口类,继承ViewManager,对Window进行管理
WindowManagerImpl WindowManager的具体实现类,一个app层面的WMSWindow的中间人
WMS(WindowManagerService) 真正对Window添加,负责管理窗口的启动、添加、删除、大小和层级、处理窗口的触摸事件、处理窗口之间切换动画等
WindowManagerGlobal 一个单例,一个进程只有一个实例,这里属于桥接模式
ViewRootImpl Android 视图层次结构的顶部,View树的树根并管理View树 ,但它不是View视图树的一部分。它是DecorView WindowManager 之间的桥梁,是输入响应的中转站,并且触发View的测量、 布局和绘制
DecorView FrameLayout的子类,是Android View视图树的根布局
Session 应用程序和WMS通信之间的通道,应用程序都会有一个Session,保存在WMS中的mSessions
WindowContainer Window容器抽象,定义了一组可以直接或通过其子类以层次结构形式保存 Window 的类的常用功能。
WindowState Window容器,WMS管理的一个窗口,可以认为是Window容器在WMS中的一种事实窗口的表示
WindowToken Window容器,WMS 中一组 Window 的容器,主要作用向WMS提供令牌、将属于同一个应用组件的窗口组织在一起
RootWindowContainer Window容器,根窗口容器。它的孩子是DisplayContent
ActivityRecord Window容器,WindowToken的子类,WMS中一个ActivityRecord对象就代表一个Activity对象
WallpaperWindowToken Window容器,WindowToken的子类,用来存放和Wallpaper相关的窗口
DisplayContent.ImeContainer Window容器,DisplayArea.Tokens的子类,用来存放输入法相关的窗口

根据android11和android13Window容器不同,这里区分开来

android11

名称 含义
DisplayContent Window容器,Window容器,管理一个逻辑屏上的所有窗口,有几个屏幕就会有几个DisplayContent,使用displayId来区分
WindowContainers Window容器,DisplayContent内部类,用于AppWindow的管理集
NonAppWindowContainers Window容器,DisplayContent内部类,用于非AppWindow的管理集
DisplayArea Window容器,DisplayContent之下的对WindowContainer进行分组的容器,受DisplayAreaPolicy管理
DisplayArea.Root Window容器,App Window的根容器
TaskDisplayArea Window容器,代表了屏幕上一块专门用来存放App窗口的区域
DisplayArea.Tokens Window容器,DisplayArea的内部类,一个只能包含WindowToken对象的DisplayArea类型的容器
Task Window容器,存放ActivityRecord的容器,用来管理ActivityRecord
ActivityStack Window容器,Task的子类,用来记录Activity历史

继承关系如下

android13

名称 含义
DisplayArea Window容器,DisplayContent之下的对WindowContainer进行分组的容器,受DisplayAreaPolicy管理
TaskDisplayArea Window容器,代表了屏幕上一块专门用来存放App窗口的区域
Task Window容器,存放ActivityRecord的容器,用来管理ActivityRecord
DisplayArea.Tokens Window容器,DisplayArea的内部类,一个只能包含WindowToken对象的DisplayArea类型的容器
DisplayContent.ImeContainer Window容器,一个只能包含WindowToken对象的DisplayArea类型的容器,存放输入法窗口的容器
DisplayArea.Dimmable Window容器,Dimmable也是一个DisplayArea类型的DisplayArea容器,用于模糊效果。
RootDisplayArea Window容器,DisplayArea层级结构的根节点
DisplayContent Window容器,Window容器,管理一个逻辑屏上的所有窗口,有几个屏幕就会有几个DisplayContent,使用displayId来区分
DisplayAreaGroup Window容器,屏幕上的部分区域对应的DisplayArea层级结构的根节点,用于车载

继承关系如下

关于Window和WIndow相关总结

Window 的管理是基于 C/S 架构,依赖系统服务。服务端是 WMS ,客户端是 WindowManager 。

Window 的更新过程不涉及 WMS ,而添加和删除需要服务端与客户端一同合作,WindowManager 来请求执行,并处理 UI 渲染刷新,WMS 则是负责管理 Window 与系统其他能力结合。

Window 的概念不同于 View ,尽管看起来十分相似,但它们是两种概念。

  • Window 代表一套视图树的容器,而 View 是视图树中的节点。

  • ViewRootImpl 是视图树对象。

  • WindowState 用来描述一个 Window。

  • WindowToken 用来表示一组 Window 的容器,可以代表 Activity 和嵌套窗口的父容器。

WindowManager 是我们访问 Window 的入口,Window 的具体实现位于 WindowManagerService 中。WindowManagerWindowManagerService 交互是一个 IPC 的过程,最终的 IPC 是在 RootViewImpl 中完成的。

4问题解答

1)Window,WMS,Activity之间的联系(什么时候Window和Activity和WMS产生联系)

时机:Activity和Window之间的联系是在Activity.attach的时候产生的。

其中new一个PhoneWindow对象并传入当前Activity引用,建立Window和Activity的一一对应关系。此Window是Window类的子类PhoneWindow的实例。Activity在Window中是以mContext属性存在。

时机:AMS和Window之间的联系是在addView/removeView产生的。通过

2)Window和View区别和联系(什么时候Window和View产生联系)

联系:每一个 Window 都对应着一个 View 和 一个 ViewRootImpl 。Window 表示一个窗口的概念,也是一个抽象的概念,它并不是实际存在的,它是以 View 的方式存在的。

区别:Window 的概念不同于 View ,尽管看起来十分相似,但它们是两种概念。Window 代表一套视图树的容器,而 View 是视图树中的节点。

时机:Window和View产生联系是在外部App调用AppCompatActivity#setContent时候,最终会生成DecorView。

3)Window的类别和层次关系(为什么PopWindow会遮住App的显示,锁屏为什么看不到Launcher)

Window分为不同容器,其中主要是WindowToken和WindowSate,一个ActivityRecord(WindowToken容器)可以包含多个WindowState容器。

每种不同的容器代表不同的层级,主要分成主层级和次层级,主层级分为[1,36],次层级为[-2,-3]。主层级越大,离用户越近,主层级相同,次层级越大离用户越近。主层级主要看WindowToken相关容器,次层级主要看WindowState相关容器。

PopWindow会遮住App的显示:App主层级为2,次层级默认为0,popWindow是不能独立存在,也就是主层级和App相同为2,次层级为1大于App所在的Window,会遮住App显示

锁屏为什么看不到Launcher:锁屏之后显示的是锁屏界面,这个界面的主层级在App层之上,那么会遮住App的显示,即看不到Launcher。

4)activity与 PhoneWindow与DecorView关系 (如果我添加一个View,会添加到系统层面去吗)

PhoneWindow内部持有DecorView,而且所有跟当前Window相关的View处理,最终都在DecorView里面进行。

添加一个View,包括自定义View,实际上就是所在的DecorView里面添加的一个App层的布局,是不会跟系统层面相关的。

源码

查看方式可以直接访问下面的网站,点击这里

直接搜索对应变量或者方法名

除了上述方式之外,如果大量的查找相关的方法,上面的方式反而比较笨拙,可以下载对应的源码,/frameworks/base点击这里,/frameworks/native点击这里

目前看来网页内置了VSCode,搜索还是联想确实会比直接在github上看快多了,不过有一个缺点就是直接打开文件功能比较弱

demo下载

另外,处理本文举例的几个简单例子,可能有读者并没有android13的设备,可以下载笔者准备的几个demo,分析具体的Window容器、层级关系等,可以点击这里

参考

源码下载

[1] 星辰之力, 官网 Android framework源码git地址, 2022.

[2] GrapheneOS, Android framework_native, 2023.

[3] aosp-mirror, Android framework_base, 2023.

AMS启动

[1] Pingred, 手把手带你搞懂AMS启动原理, 2023.

[2] Avengong, Android渲染(一)_系统服务WMS启动过程(基于Android10), 2022.

[3] 345丶, Android | WMS 解析 (一), 2022.

window and windowmanager

[1] Chunyu J, Android Window 机制, 2022.

[2] 小余的自习室, 源码学习时间,Window Manager in Android, 2023.

[3] 345丶, Android | 理解 Window 和 WindowManager, 2022.

addView/addWindow

[1] a5right, Android View从绘制到上屏全过程解析, 2023.

[2] Anderson大码渣, What is DecorView and android.R.id.content?, 2017.

[3] 刘望舒, Android解析WindowManagerService(二)WMS的重要成员和Window的添加过程, 2017.

[4] 刘望舒, Android解析WindowManagerService(三)Window的删除过程, 2018.

[5] learnframework, android 13 WMS/AMS系统开发-SplashScreen的添加与移除分析, 2023.

[6] 被代码淹没的小伙子, 【Window系列】——PopupWindow的前世今生, 2019.

window层级/windowcontainer

[1] TechMerger, 你知道 Android 是如何管理复杂的 Window 层级的?, 2022.

[2] 小余的自习室, “一文读懂”系列:无处不在的WMS, 2022.

[3] Looperjing, Android窗口系统第一篇—Window的抽象概念和WMS相关数据结构理解, 2017.

[4] aofan9566, WindowState注意事项, 2015.

[5] weixin_41591109, android 11 的Window的屏幕的架构图, 2022.

[6] qluka, Android 窗口结构(一) 窗口层级构造, 2022.

[7] qluka, Android 窗口结构(二) 添加Home Task, 2022.

[8] Geralt_z_Rivii, 2【Android 12】WindowContainer类, 2022.

[9] Geralt_z_Rivii, 3【Android 12】DisplayArea层级结构, 2022.

[10] TigherGuo, 安卓12窗口层次: DisplayArea树, 2022.

[11] HansChen_, Android 12 - WMS 层级结构 && DisplayAreaGroup 引入, 2021.

[12] learnframework, android 13 WMS/AMS系统开发-窗口层级相关DisplayArea,WindowContainer, 2023.

[13] learnframework, android 13 WMS/AMS系统开发-窗口层级相关DisplayArea,WindowContainer第二节, 2023.

[14] learnframework, android 13 WMS/AMS系统开发-窗口层级相关Task/ActivityRecord/WindowState/WindowToken放置图层创建 第三节, 2023.

[15] learnframework, android 13 WMS/AMS系统开发-窗口层级相关SurfaceFlinger图层创建 第三节, 2023.

锁屏相关

[1] learnframework, Android 12中系统Wallpaper详解1–锁屏透看壁纸和桌面透看壁纸的切换, 2022.

view相关

[1] 凶残的程序员, Android 屏幕绘制机制及硬件加速, 2019.

[2] 沧浪之水, Android GPU硬件加速渲染流程(上), 2022.

[3] 沧浪之水, Android GPU硬件加速渲染流程(下), 2022.

[4] 𝓑𝓮𝓲𝓨𝓪𝓷𝓰, 软件绘制 & 硬件加速绘制 【DisplayList & RenderNode】, 2022.

[5] 𝓑𝓮𝓲𝓨𝓪𝓷𝓰, 硬件加速绘制基础知识, 2022.

[6] 失落夏天, View绘制流程3-Vsync信号是如何发送和接受的, 2022.

[7] houliang120, Android垂直同步信号VSync的产生及传播结构详解, 2016.