WMS-Window
11800 Words|Read in about 55 Min|本文总阅读量次
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类别
-
Application Window: Activity就是一个典型的应用程序窗口
-
Sub Window: 子窗口, 顾名思义, 它不能独立存在, 需要附着在其他窗口才可以, PopupWindow就属于子窗口
-
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不在本文讨论,详情可以看这里。
- 通过屏幕管理对象mDisplayManager得到所有的显示屏幕,然后构造DisplayContent对象
- 通过addChild方法将DisplayContent对象添加到RootWindowContainer根对象的树状结构中
- 默认显示屏幕的mDisplayId 是DEFAULT_DISPLAY,mDisplayId 为0作为基本显示屏幕
- 获取默认屏幕的TaskDisplayArea,创建一个根Home任务
- 最后把默认屏幕放在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}
- 新建一个TaskDisplayArea 实例defaultTaskDisplayArea
- 接着创建HierarchyBuilder 实例, 将输入法窗口容器和defaultTaskDisplayArea 都设置到它的成员变量里面
- 接着判断显示屏是否是可信任的,主要是添加一些特色模式
- 采用建造者模式新建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}
- 调用mRootHierarchyBuilder.build构造层级
- 这里没有使用到displayAreaGroupRoots,所以跳过2
- 如果mSelectRootForWindowFunc 没有设置值的话,设置一个默认的对象
- 新生成一个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对象。
这里有几点需要说明:
-
可以根据窗口的添加方式将窗口分为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。
-
WindowToken既然是WindowState的直接父容器,那么每次添加窗口的时候,就需要创建一个WindowToken,或者一个ActivityRecord。
-
上述情况也有例外,即存在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有三个直接子类,TaskDisplayArea,DisplayArea.Tokens和DisplayArea.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的两个子类,DisplayContent和DisplayAreaGroup(这个是车载用到的,暂时不考虑)。
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 只用来管理调度Task ,Task 用来管理调度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总结
- new一个PhoneWindow对象并传入当前Activity引用,建立Window和Activity的一一对应关系。此Window是Window类的子类PhoneWindow的实例。Activity在Window中是以mContext属性存在。
- 调用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进行关联)
- 我们就给当前Activity的Window创建了一个DecorView
- 这个DecorView就是当前Window的rootView
- 并对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有很多作用
- 管理View树,且其是View的根
- 触发三大绘制流程:测量,布局,绘制
- 输入事件中转站
- 管理Surface
- 负责与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的三大绘制layout,mesure,draw流程。
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}
流程比较复杂
-
通过 WindowManagerPolicy 的 checkAddPermission 检查添加权限
-
通过 displayId 获取所添加到的屏幕抽象 DisplayContent 对象
-
根据 Window 是否是子窗口,创建 WindowToken
-
如果上一步创建 token 失败,重新根据是否存在父窗口创建 WindowToken
-
- 子窗口直接取父窗口的 WindowToken
- 新的窗口直接构造一个新的 WindowToken
-
根据根窗口的 type 来处理不同的情况(Toast、键盘、无障碍浮层等不同类型返回不同的结果)
-
创建 WindowState
-
检查客户端状态,判断是否可以将 Window 添加到系统中
-
以 session 为 key ,windowState 为 value ,存入到 mWindowMap 中
-
将 WindowState 添加到相应的 WindowToken 中
- 若 WindowState 代表一个子窗口,直接 return
- 若仍没有 SurfaceControl ,为该 token 创建 Surface
- 更新 Layer
- 根据 Z 轴排序顺序将 WindowState 所代表的 Window 添加到合适的位置,此数据结构保存在 WindowToken 的父类 WindowContainer 的 mChildren 中:
-
检查是否需要转换动画
-
更新窗口焦点
-
最后返回执行结果
最重要的就是[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 方法中
- 开始向视图树中的 View 分发 DetachedFromWindow ,
- 然后释放一些资源,释放 Surface
- 通过 Session IPC 调用了 WMS 移除 Window
- 删除待执行的 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 作用
- View树的树根并管理View树
- 触发View的测量、 布局和绘制
- 输入响应的中转站
- 负责与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界面 |
后台应用 |
如下图所示
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
。
- 通过
generateDecor
创建了 DecorView,接着会调用generateLayout
来加载具体的布局文件到 DecorView 中,这个要加载的布局就和系统版本以及定义的主题有关了。加载完之后就会将内容区域的 View 返回出来,也就是mContentParent
- 将 activity 需要显示的布局添加到
mcontentParent
中 - 由于 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
变量后,获取Context
和WindowManger
对象。
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 中,并没有创建 PhoneWindow
和 DecorView
,这点和 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 层面的WMS 和Window 的中间人 |
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 内部类,用于App 中Window 的管理集 |
NonAppWindowContainers |
Window 容器,DisplayContent 内部类,用于非App 中Window 的管理集 |
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
中。WindowManager
和 WindowManagerService
交互是一个 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.
[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.