Android窗口

3.3 Android窗口

3.3.1 概述

Android系统中,窗口管理系统是基于C/S模式的,客户端(App)请求创建窗口和使用窗口,服务端(WMS)管理所有窗口,包括创建、删除窗口,以及将某个窗口设置为焦点窗口(当前正在和用户交互的窗口)。

Android Gui系统

我们先简单来介绍一下Android的GUI系统,它包含以下部分内容:

  1. 应用框架系统 — AMS
  2. 窗口和图形系统 — WMS
  3. 显示渲染系统 — SurfaceFlinger
  4. 用户输入系统 — InputManager

它们之间的关系,如下图所示:

简单来讲,AMS负责Activity的启动,以及生命周期的管理。一个Activity对应一个应用窗口,WMS负责管理这个窗口(创建、删除等)以及事件分发。View系统管理每个窗口中的具体布局,最终这个View系统中最顶端的根View(即DecorView)会被作为窗口,添加到WMS中。WMS管理着所有这些添加的窗口,负责管理这些窗口的层次,显示位置等内容。每个窗口都有一块自己的Surface,SurfaceFlinger负责把这些Surface显示到屏幕上。

窗口的概念

看AndroidSDK文档的描述:Window是一个抽象基类,用于控制顶层窗口的外观和行为,如绘制背景、标题栏和按键处理等。View是一个基本的UI单元,占据屏幕的一块矩形区域,用于绘制显示UI,接收处理事件等。

而窗口的概念,从不同的角度来看,其含义是不一样的,有时候是指Window,有时候是指View。我们知道,WMS管理所有的窗口,这里的窗口其实是一个View(DecorView),而不是Window。WMS负责管理这些View的Z-order,显示区域,以及把消息派发到对应的View。

3.3.2 窗口的类型

Framework中定义了三种类型的窗口:应用窗口,子窗口,系统窗口。

应用窗口

Activity对应的窗口就是应用窗口,其默认的窗口类型是TYPE_BASE_APPLICATION。而Dialog的窗口类型是TYPE_APPLICATION,而很多Dialog的子类,修改了窗口类型,如ContextMenu,本质是用Dialog来实现的,但是在添加窗口前,修改了type类型,赋值为TYPE_APPLICATION_ATTACHED_DIALOG。从这个我们可以看到,WMS并没有把应用窗口与子窗口区分得那么清楚。

与应用窗口相关的窗口表示类是PhoneWindow,PhoneWindow继承自Window,其本身只是一个窗口封装类,其核心是成员mDecorView,mDecorView是一个顶层的View,窗口的添加就是通过调用WindowManager.addView()把该View添加到WMS。

子窗口

子窗口是指该窗口必须要有一个父窗口,父窗口可以是一个应用类型窗口,也可以是其他类型的窗口。例如前面手Q界面中,点击右上角的按钮显示一个PopupWindow,它就是一个子窗口,其类型一般TYPE_APPLICATION_PANEL。既然称为子窗口,其与父窗口的关系是比较容易理解的:B是A的子窗口,A不可见时,B也会不可见的。

系统窗口

一般来讲,系统窗口应该由系统来创建的,例如发生异常,ANR时的提示框,状态栏,屏保等。但是,Framework还是定义了一些可以被应用所创建的系统窗口,如TYPE_TOAST,TYPE_INPUT_METHOD和TYPE_WALLPAPER等等。系统窗口的添加也是直接调用WindowManager.addView()将目标View添加到WMS。

3.3.3 窗口的创建、显示与移除

这里以应用窗口为例简单介绍以下窗口的创建,显示和移除过程。

窗口的创建

我们知道每个Activity对应一个PhoneWindow,当我们调用setContentView时,其实最终结果是把我们的View树作为子View添加到PhoneWindow的DecorView中,而这个DecorView会在ActivityThread的handleResumeActivity方法中,通过WindowManager.addView()方法添加到WMS中去的。

AMS在接收到启动Activity请求时,首先生成一个token作为该Activity的唯一标识,然后在WMS中添加一个AppWindowToken,其封装了Activity的token。接着AMS启动应用客户端进程并把token传递到该进程,在客户端进程里完成Activity的初始化。在Activity的attach()方法中,Activity完成PhoneWindow的创建,并且把token传递给PhoneWindow。在Activity调用WindowManager.addView()时,在WindowManager内部会把token和该View关联,真正向WMS申请创建窗口的时候,再把token传递给WMS。WMS接收到创建窗口的请求的时候,通过mTokenMap查询对应该token的AppWindowToken,如果为空则抛出异常,否则创建一个WindowState并完成初始化工作和其他数据结构的调整工作。在这个过程中,token贯穿了服务端的AMS、WMS和客户端的Activity、Window。

应用请求创建窗口时,和应用直接交互的是WindowManager对象,其负责管理一个应用的所有本地窗口。当应用调用addView()创建窗口时,WindowManager会生成一个ViewRoot对象与之相对应,并且把相应的参数LayoutParams保存起来。addView()的执行流程如下:

  1. 检查所添加的窗口是否已经添加过,不允许重复添加;
  2. 如果所添加窗口为子窗口类型,找到其父窗口,并保存在内部变量中;
  3. 创建一个新的ViewRoot,并保存对应的View(DecorView)和LayoutParams;
  4. 调用ViewRoot的setView()方法,完成添加工作。

ViewRoot本质上是一个Handler,并且实现了ViewParent接口。ViewRoot的主要功能是:

  • 负责分发消息事件,如Key、Motion事件等;
  • 负责和WMS的交互,分发WMS的交互命令;
  • 作为DecorView的parent,对DecorView进行measure、layout和draw等操作;

在addView()的第3、4步完成之后,之后和WMS的交互工作就由ViewRoot负责。而ViewRoot和WMS之间的双向对话,主要是通过以下两个数据结构进行的:IWindowSession,IWindow。其中IWindowSession负责ViewRoot到WMS的请求,IWindow则用于WMS回调ViewRoot。在ViewRoot对象内部,存在着一个IWindowSession的静态成员和一个IWindow的非静态成员,所以一个进程里只有一个IWindowSession对象,但是可以有多个IWindow对象。参见下图:

到此为止,整个窗口管理系统整体架构可表示如下:

窗口显示

从Client端调用WindowManager的addView()方法到WMS完成WindowState的初始化,在这个过程中,主要是完成了窗口数据结构的创建以及Client端的窗口和Server端的窗口建立起连接关系:WMS能够对Client端的窗口进行操作,同时WMS也能够接收Client端窗口的请求,对WindowState进行相应的调整。

一个Window想要显示在屏幕上,必须申请一个显示缓存(Surface),这个显示缓存的管理和维护是在底层图形模块实现的。WindowState申请到Surface对象之后,会将此Surface对象的相关数据拷贝到Client端的ViewRoot中,ViewRoot中也维护了一个Surface对象,这两个对象是指向同一块显示缓存。ViewRoot有了这块显示缓存的引用之后,即可以通过lockCanvas来获取绘画画布,绘制完毕之后通过unlockAndPostCanvas来将绘制内容刷新到显示缓存中。也就是说,Client端窗口和Server端窗口共用一个Surface,Client负责绘制Surface的内容,Server负责控制Surface在屏幕上的大小位置等。

ViewRoot通过IWindowSession的relayout方法来向WMS发送请求命令,包括窗口的显示和隐藏,窗口的布局信息如位置大小,同时还会接收WMS的处理结果。WMS会根据屏幕大小和Client请求的布局参数来决定窗口最终的布局信息,同时也会根据Client请求的显示隐藏命令来返回一个有效的或者无效的Surface对象。通常一个窗口的显示过程为:

  1. Client请求显示窗口,并且传递布局参数;
  2. WMS根据布局参数,申请一个Surface对象并返回给Client;
  3. Client对Surface进行绘画操作,完成后告诉WMS;
  4. WMS将Surface显示在屏幕上,并且进行层级等相应调整;

于是一个窗口从添加到显示可用以下时序图表示:

一个横跨Activity、View、Window、WMS以及Surface的整体概念如下如所示:

窗口的移除

窗口的移除是通过调用WindowManager.removeView()来完成的,具体流程如下:

ViewRootImpl在收到要删除窗口的命令后,会执行以下操作:

  1. 判断是否可以立即删除窗口,否则会等下次UI操作时执行;
  2. 确认需要删除窗口时,会执行doDie方法,通过dispatchDetachedFromWindow通知View树,窗口要被删除了;
  3. dispatchDetachedFromWindow执行以下操作:通过dispatchDetachedFromWindow,通知View树,窗口已经移除了;把窗口对应的HardRender、Surface给释放了;通过mWindowSession,通知WMS,窗口要移除了,WMS会把跟这个窗口相关的WindowState,以及WindowToken给移除,同时更新其它窗口的显示;通知Choreographer,这个窗口不需要显示了,跟这个窗口相关的一些UI刷新操作,可以取消了。
  4. 当根View收到dispatchDetachedFromWindow调用后,会遍历View树中的每一个View,把这个通知传递下来。

3.3.4 总结

这里我们总结一下,Android中窗口的相关内容:

  1. 在Window System中,分为两部分的内容,一部分是运行在SystemServer进程中的WMS及相关类,另一部分是运行在应用进程的WindowManager,ViewRoot等相关类。WMS用WindowState来描述一个窗口,而应用进程用DecorView、ViewRoot以及WindowManager.LayoutParams等来描述一个窗口的相关内容。
  2. 对于WMS来讲,窗口对应一个View对象,而不是Window对象。通过WindowManager.addView()方法添加一个窗口,通过removeView方法移除一个窗口,通过updateViewLayout()方法更新一个窗口的属性。
  3. Android把窗口分为三种类型:应用窗口,子窗口以及系统窗口。不同类型的窗口,在执行添加窗口操作时,对于WindowManager.LayoutParams中的参数token具有不同的要求。应用窗口,LayoutParams中的token,必须是某个有效的Activity的mToken。而子窗口,LayoutParams中的token,必须是父窗口的ViewRootImpl中的W对象。
  4. 在调用WindowManager.addView()之前,如果没有给token赋值,则会走默认的token赋值逻辑:如果mParentWindow不为空,则会调用其adjustLayoutParamsForSubWindow方法,在该方法中,如果当前要添加的窗口是应用窗口,则会把当前PhoneWindow的mToken赋值给token。如果是子窗口,则会把当前PhoneWindow对应的DecorView的mAttachInfo中的mWindowToken赋值给token。而View中的mAttachIno来自ViewRootImpl的mAttachInfo。因此这个token本质就是父窗口的ViewRootImpl中的W类对象。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容