Android13 WMS窗口相关流程(一)

一、介绍

什么是窗口
窗口即是屏幕上的一块用于绘制各种UI元素并可以响应用户输入的一个矩形区域。从原理上讲,窗口的概念是独自占有一个Surface实例的显示区域(我们在屏幕上看到的图形都需要绘制在Surface上)。
Window是个抽象类其实现类为PhoneWindow。
可以先学习层级结构树相关内容,有助于本文的理解
Android T 窗口层级其一 —— 容器类
Android T 窗口层级其二 —— 层级结构树的构建
Android T 窗口层级其三 —— 层级结构树添加窗口

二、流程简述

当Activity.onResume()被调用之后,客户端会与WMS进行通信将我们的布局显示在屏幕上。其中主要涉及以下几个过程:
客户端通知WMS创建一个窗口,并添加到WindowToken。即addToDisplayAsUser阶段。
客户端通知WMS创建Surface,并计算窗口尺寸大小。即relayoutWindow阶段。
客户端获取到WMS计算的窗口大小后,进一步测量该窗口下View的宽度和高度。即performMeasure阶段。
客户端确定该窗口下View的尺寸和位置。即performLayout阶段。
确定好View的尺寸大小位置之后,便对View进行绘制。即performDraw阶段。
通知WMS,客户端已经完成绘制。WMS进行系统窗口的状态刷新以及动画处理,并最终将Surface显示出来。即reportDrawFinished阶段。


image.png

这里以Activity.onResume()被调用之后为起点

1.客户端

WindowManager:是一个接口类,负责窗口的管理(增、删、改)。

WindowManagerImpl:WindowManager的实现类,但是他把对于窗口的具体管理操作交给WindowManagerGlobal来处理。

WindowManagerGlobal:是一个单例类,实现了窗口的添加、删除、更新的逻辑,但是

ViewRootImpl:通过IWindowSession与WMS进行通信。其内部类W实现了WMS与ViewRootImpl的通信。


image.png

ActivityThread.java

handleResumeActivity
通过WindowManager接口添加view,即wm.addView(decor, l);,wm为ViewManager对象,即ViewManager wm = a.getWindowManager();
WindowManagerImpl.java

addView
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,mContext.getUserId());mGlobal为WindowManagerGlobal对象。
WindowManagerGlobal.java

addView
root.setView(view, wparams, panelParentView, userId);root为ViewRootImpl对象。
parentWindow.adjustLayoutParamsForSubWindow(wparams);parentWindow为Window(Window为抽象类,PhoneWindow继承于Window),即在Window中调用adjustLayoutParamsForSubWindow,用于赋值参数布局的token以及title
ViewRootImpl.java

setView
1.addToDisplayAsUser
客户端通知WMS创建一个窗口,并添加到WindowToken
res = mWindowSession.addToDisplayAsUser(mWindow,mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(), userId,mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,mTempControls);
2.requestLayout
在添加到窗口管理器之前安排第一个布局,以确保我们在从系统接收任何其他事件之前进行重新布局
scheduleTraversals->doTraversal->performTraversals
performTraversals中调用了五个关键方法:
relayoutWindow
客户端通知WMS创建Surface,并计算窗口尺寸大小
performMeasure
客户端获取到WMS计算的窗口大小后,进一步测量该窗口下View的宽度和高度
performLayout
客户端确定该窗口下View的尺寸和位置
performDraw
确定好View的尺寸大小位置之后,便对View进行绘制
createSyncIfNeeded->reportDrawFinished
通知WMS,客户端已经完成绘制。WMS进行系统窗口的状态刷新以及动画处理,并最终将Surface显示出来

2. 通信方式

Session表示一个客户端和服务端的交互会话。一般来说不同的应用通过不同的会话来和WindowManagerService交互,但是处于同一个进程的不同应用通过同一个Session来交互。

IWindowSession.aidl
ViewRootImpl中通过此接口调用服务端
1.addToDisplayAsUser
2.relayout
3.finishDrawing

Session.java
IWindowSession的实现在这里,最终调用到WMS中
1.addToDisplayAsUser->addWindow
2.relayout->relayoutWindow
3.finishDrawing->finishDrawingWindow

3. 服务端

WindowManagerService:负责为Activity对应的窗口分配Surface,管理Surface的显示顺序以及位置尺寸,控制窗口动画,并且还是输入系统的一个重要中转站。

WindowState:和客户端窗口一一对应,在向WMS添加一个窗口时,WMS会为其创建一个WindowState,来表示窗口的所有属性,WindowState相当于属性窗口管理(比如对外提供操作接口,属于层级结构中最底部的容器),窗口画面相关都剥离给了WindowStateAnimator,WindowState也是WMS中事实上的窗口。

WindowStateAnimator:主要用于管理WindowState相关画面surface,通过mDrawState参数来描述Surface所处的状态。

WindowToken:保存了所有具有同一个token的WindowState,将属于同一个activity的窗口组织在一起,activity在需要更新窗口时,必须向WMS提供WindowToken以表名自己的身份,并且窗口的类型必须与所持有的的WindowToken类型一致。
补充:一个WindowToken可以对应多个WindowState。 WindowToken是一个用于表示窗口层次结构中的窗口的标识符。每个Window具有一个与之关联的WindowToken,它用于帮助系统管理窗口的显示和交互。
一个WindowToken可以有多个WindowState表示与之相关的窗口。这是因为在Android系统中,可能会存在一些特殊情况,例如PopupWindow、Dialog等,它们属于同一个WindowToken,但是显示在不同的窗口上。
因此,一个WindowToken可以与多个WindowState关联,这样可以实现多个窗口的操作和管理。

WindowSurfaceController:用来创建SurfaceControl。

DisplayContent:即代表的是单个屏幕。隶属于同一个DisplayContent的窗口将会被显示在同一个屏幕中。每个DisplayContent都对应着一个唯一的id,在添加窗口时可以通过指定这个ID决定将其显示在哪个屏幕中。

WindowSurfacePlacer:整个窗口层次结构刷新的入口。

RootWindowContainer:是窗口容器的顶层容器,其直接管理DisplayContent。

WindowManagerService.java

3.1.addWindow
image.png

1.根据客户端传来的token获取WindowToken或创建WindowToken,并将其挂载到对应的层级节点上
WindowToken token = displayContent.getWindowToken(hasParent ? parentWindow.mAttrs.token : attrs.token);
判断WindowToken是否有父亲,即parentWindow 是否不为空
final boolean hasParent = parentWindow != null;
注:前面代码有判断是否是子窗口,是则会给parentWindow 赋值;否则parentWindow仍为初始值,即为空


image.png

关于窗口类型,见 窗口常见参数汇总
Activity启动时会在ActivityRecord的构造方法中new Token()。
应用侧直接通过addView的方式添加窗口不会有ActivityRecord,因此不会在ActivityRecord的构造方法中new Token()。
系统侧直接添加的窗口(状态栏、导航栏等),是通过new WindowToken.Builder的方式添加
即主动使用ViewManager.addView来添加一个窗口则不会在ActivityRecord的构造方法中new Token(),否则通过new WindowToken.Builder的方式添加。
attrs.token这个参数一可以在应用端设置,应用没有设置token那么就为空,token为IBinder类型对象,默认值为空public IBinder token = null;
例如:
在应用侧可通过mLayoutParams.token的方式设置值
private WindowManager.LayoutParams mLayoutParams;
mLayoutParams.token = null;

后面会继续判断token是否为空,最终会到最后的else中创建token


image.png

2.创建WindowState
final WindowState win = new WindowState(this, session, client, token, parentWindow, appOp[0], attrs, viewVisibility, session.mUid, userId,session.mCanAddInternalSystemWindow);
3.验证当前窗口是否可以添加到WMS
res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
该方法会对窗口TYPE,FLAG等多方面判断。只有返回ADD_OKAY时表示允许当前窗口的添加,反之则不允许添加该窗口。假如想禁止某些应用做添加窗口操作时,可以在里面通过应用的包名过滤该应用,也可以直接在WindowManagerGlobal.java的addView()方法中直接对应用想要添加的窗口进行过滤。
注:ADD_OKAY在WindowManagerGlobal中定义,这个类里面还有一些其他的返回值,所有返回给res的常量最终会在ViewRootImpl的setView方法中判断
4.调用openInputChannel,初始化input相关通路(本文不做讨论)
final boolean openInputChannels = (outInputChannel != null && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
if (openInputChannels) { win.openInputChannel(outInputChannel); }
5.将WindowState加入到WindowToken
win.mToken.addWindow(win);
WMS窗口添加之后,还没有创建Surface,此时mDrawState状态为NO_SURFACE

3.2 relayoutWindow
image.png

1.根据客户端传递过来的IWindow的mWindowMap获取窗口添加阶段创建的WindowState
final WindowState win = windowForClientLocked(session, client, false);
2.设置DisplayContent.mLayoutNeeded以及shouldRelayout标志位
win.setDisplayLayoutNeeded();win为WindowState对象,该方法实际操作在DisplayContent中
final boolean shouldRelayout = viewVisibility == View.VISIBLE &&(win.mActivityRecord == null || win.mAttrs.type == TYPE_APPLICATION_STARTING || win.mActivityRecord.isClientVisible());
3.创建SurfaceControl
在layoutWindow()调用了createSurfaceControl方法创建SurfaceControl
result = createSurfaceControl(outSurfaceControl, result, win, winAnimator);该方法的实现仍然在WMS中
这里以createSurfaceControl方法为起点


image.png

在createSurfaceControl()中调用WindowStateAnimator执行具体的SurfaceControl的创建 surfaceController = winAnimator.createSurfaceLocked();
创建Surface后,Surface还未进行绘制,此时mDrawState状态为DRAW_PENDING
将创建的SurfaceControl赋值给客户端的outSurfaceControl
surfaceController.getSurfaceControl(outSurfaceControl);
4.窗口尺寸的计算以及Surface状态更新
在layoutWindow()调用了performSurfacePlacement
mWindowPlacerLocked.performSurfacePlacement(true /* force */);mWindowPlacerLocked为WindowSurfacePlacer对象,因此这里以WindowSurfacePlacer的performSurfacePlacement()为起点


image.png

处理窗口布局循环
WindowSurfacePlacer.performSurfacePlacementLoop()
处理Surface的状态更变,以及调用LayoutWindowLw的流程
RootWindowContainer.performSurfacePlacementNoTrace()
计算窗口位置大小
DisplayPolicy.layoutWindowLw()
3.3 finishDrawingWindow
image.png

调用commitFinishDrawingLocked()
改变mDrawState状态将mDrawState更新为READY_TO_SHOW,
最终mDrawState更新为HAS_DRAW后,再次请求窗口布局

3.执行show Surface
showSurfaceRobustlyLocked(t)
注:WindowStateAnimator的commitFinishDrawingLocked()方法中,如果是应用通过addView的方式创建窗口,则不会有ActivityRecord,或者该窗口类型为启动窗口,则直接调用result = mWin.performShowLocked();,即WindowState的performShowLocked()方法改变窗口状态为HAS_DRAW,否则会从RootWindowContainer的checkAppTransitionReady方法逐步调用到performShowLocked()

4.窗口状态变化总结
WMS为了管理窗口的显示进度,在WindowStateAnimator中定义了mDrawState来描述Surface所处的状态。主要有如下五种状态:
NO_SURFACE:WMS添加窗口,即调用addWindow之后,还没有创建Surface,mDrawState处于该状态。
DRAW_PENDING:app调用relayoutWindow创建Surface后,但是Surface还没有进行绘制,mDrawState处于该状态。
COMMIT_DRAW_PENDING:app完成Surface的绘制,调用finishDrawing,将mDrawState设置为该状态。
READY_TO_SHOW:在performSurfacePlacement过程中会将所有处于COMMIT_DRAW_PENDING状态的mDrawState变更为READY_TO_SHOW。
HAS_DRAW:若准备显示窗口,WMS执行performShowLocked,将mDrawState设置为该状态

窗口显示相关方法 工作内容解释
addWindow App向WMS请求添加窗口记录,会在WMS里新建WindowState(NO_SURFACE)
relayoutWindow App向WMS申请surface用于绘制,执行后window拥有了surface(NO_SURFACE->DRAW_PENDING)
finishDrawingWindow App在surface上完成绘制后,通知WMS(DRAW_PENDING->COMMIT_DRAW_PENDING)
commitFinishDrawingLocked WMS遍历window,对于完成绘制的window(COMMIT_DRAW_PENDING->READY_TO_SHOW)
performShowLocked 判断系统是否允许窗口显示isReadyForDisplay(READY_TO_SHOW->HAS_DRAWN)
showSurfaceRobustlyLocked 对HAS_DRAWN状态的窗口,用SurfaceControl通知SurfaceFlinger显示出来

5.移除流程简述
窗口移除从App端发起,当Activity执行destroy(),即以handleDestroyActivity()为起点,执行wm.removeViewImmediate()开启;
通过WindowManagerGlobal–>ViewRootImpl–>Session–>WindowManagerService的removeWindow(),调用到WindowState的removeIfPossible()–>removeImmediately(),接着调用到WindowStateAnimator的destroySurfaceLocked()–>destroySurface(),逐步调用改变绘制状态为NO_SURFACE–>WindowSurfaceController的destroy()最终调用到SurfaceControl的remove()来通知SurfaceFlinger来移除layer;

————————————————
版权声明:本文为CSDN博主「yi诺千金」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yimelancholy/article/details/130339779

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349