关于一些Android WMS的解析

1 、WMS的职责

  • 1.窗口管理

    WMS是窗口的管理者,它负责窗口的启动、添加和删除,另外窗口的大小和层级也是由WMS进行管理的。窗口管理的核心成员有DisplayContent、WindowToken和WindowState。

  • 2.窗口动画

    窗口间进行切换时,使用窗口动画可以显得更炫一些,窗口动画由WMS 的动画子系统来负责,动画子系统的管理者为WindowAnimator。

  • 3.输入系统的中转站

    通过对窗口的触摸从而产生触摸事件,InputManagerService(IMS)会对触摸事件进行处理,它会寻找一个最合适的窗口来处理触摸反馈信息,WMS是窗口的管理者,它作为输入系统的中转站再合适不过了。

  • 4.Surface管理

    窗口并不具备绘制的功能,因此每个窗口都需要有一块Surface来供自己绘制,为每个窗口分配Surface是由WMS来完成的。

WMS的职责可以简单总结为如图所示:

image.png

2、 WMS的创建过程

WMS是在SystemServer进程中创建的,查看SystemServer的main方法,如下所示:

image.png

在main方法中只调用了SystemServer的run方法,如下所示:

image.png

在注释1处加载了动态库libandroid_servers.so。

接下来在注释2处创建SystemServiceManager,它会对系统的服务进行创建、启动和生命周期管理。

在注释3处的startBootstrapServices方法中用SystemServiceManager启动了ActivityManagerService、PowerManagerService、PackageManagerService等服务。

在注释4处的startCoreServices方法中则启动了DropBoxManagerService、BatteryService、UsageStatsService和WebViewUpdateService。

在注释5处的startOtherServices 方法中启动了CameraService、AlarmManagerService、VrManagerService等服务。

这些服务的父类均为SystemService。从注释3、注释4、注释5处的方法可以看出,官方把系统服务分为了三种类型,分别是引导服务、核心服务和其他服务,其中其他服务是一些非紧要和不需要立即启动的服务,WMS就是其他服务的一种。我们来查看在startOtherServices方法中是如何启动WMS的:

image.png
image.png

startOtherServices方法用于启动其他服务,其他服务大概有100个左右,上面的代码只列出了WMS以及和它相关的IMS的启动逻辑,剩余的其他服务的启动逻辑也都大同小异。

在注释1、注释2处分别得到Watchdog实例并对它进行初始化,Watchdog用来监控系统的一些关键服务的运行状况。

在注释3处创建了IMS,并赋值给IMS类型的inputManager对象。

在注释4处执行了WMS的main方法,其内部会创建WMS,需要注意的是main方法其中一个传入的参数就是在注释1处创建的IMS,WMS是输入事件的中转站,其内部包含了IMS引用并不意外。

结合上文,我们可以得知WMS的main 方法是运行在SystemServer的run方法中的,换句话说就是运行在“system_server”线程中。

在注释5和注释6处分别将WMS和IMS注册到ServiceManager中,这样如果某个客户端想要使用WMS,就需要先去ServiceManager中查询信息,然后根据信息与WMS 所在的进程建立通信通路,客户端就可以使用WMS 了。

在注释7处用来初始化屏幕显示信息,

在注释8处则用来通知WMS,系统的初始化工作已经完成,其内部调用了WindowManagerPolicy的systemReady方法。

我们来查看注释4处WMS的main方法,如下所示:

image.png

在注释1处调用了DisplayThread的getHandler方法,用来得到DisplayThread的Handler实例。

DisplayThread是一个单例的前台线程,这个线程用来处理需要低延时显示的相关操作,并只能由WindowManager、DisplayManager和InputManager实时执行快速操作。

在注释1处的runWithScissors方法中使用了Java 8中的Lambda表达式,它等价于如下代码:

image.png

在注释2处创建了WMS的实例,这个过程运行在Runnable的run方法中,而Runnable则传到了DisplayThread对应Handler的runWithScissors方法中,说明WMS的创建是运行在android.display线程中的。需要注意的是,runWithScissors方法的第二个参数传入的是0。

下面来查看Handler的runWithScissors方法做了什么:

image.png

开头对传入的Runnable和timeout进行了判断,如果Runnable为null或者timeout小于0则抛出异常。

在注释1处根据每个线程只有一个Looper 的原理来判断当前的线程(system_server线程)是否是Handler所指向的线程(android.display线程),如果是则直接执行Runnable的run方法,如果不是则调用BlockingRunnable的postAndWait方法,并将当前线程的Runnable作为参数传进去,

BlockingRunnable是Handler的内部类,代码如下所示:

image.png

在注释2处将当前的BlockingRunnable 添加到Handler 的任务队列中。

前面runWithScissors方法的第二个参数为0,因此timeout等于0,这样如果mDone为false的话会一直调用注释3处的wait方法使得当前线程(system_server线程)进入等待状态,那么等待的是哪个线程呢?我们往上看,在注释1处执行了传入的Runnable的run方法(运行在android.display线程),执行完毕后在finally代码块中将mDone设置为true,并调用notifyAll方法唤醒处于等待状态的线程,这样就不会继续调用注释3处的wait方法。因此得出结论,system_server线程等待的就是android.display线程,一直到android.display线程执行完毕再执行system_server线程,这是因为android.display线程内部执行了WMS的创建,而WMS的创建优先级要更高。

最后查看WMS的构造方法:

image.png

注释1处用来保存传进来的IMS,这样WMS就持有了IMS的引用。

在注释2处通过DisplayManager的getDisplays方法得到Display数组(每个显示设备都有一个Display实例),接着遍历Display 数组,

在注释3处的createDisplayContentLocked 方法将Display 封装成DisplayContent,DisplayContent用来描述一块屏幕。

在注释4处得到AMS实例,并赋值给mActivityManager,这样WMS就持有了AMS的引用。

在注释5处创建了WindowAnimator,它用于管理所有的窗口动画。

在注释6处初始化了窗口管理策略的接口类WindowManagerPolicy(WMP),它用来定义一个窗口策略所要遵循的通用规范。

在注释7处将自身也就是WMS通过addMonitor方法添加到Watchdog中,Watchdog用来监控系统的一些关键服务的运行状况(比如传入的WMS的运行状况),这些被监控的服务都会实现Watchdog.Monitor接口。Watchdog每分钟都会对被监控的系统服务进行检查,如果被监控的系统服务出现了死锁,则会杀死Watchdog所在的进程,也就是SystemServer进程。

查看注释6处的initPolicy方法,如下所示:

image.png

initPolicy方法和此前讲的WMS的main方法的实现类似,在注释1处执行了WMP的init方法,WMP是一个接口,init方法具体在PhoneWindowManager(PWM)中实现。

PWM的init方法运行在android.ui线程中,它的优先级要高于initPolicy方法所在的android.display线程,因此android.display 线程要等PWM的init方法执行完毕后,处于等待状态的android.display线程才会被唤醒从而继续执行下面的代码。

本文共提到了3个线程,分别是system_server、android.display和android.ui,为了便于理解,下面给出这3个线程之间的关系,如图所示。

image.png

三个线程之间的关系分为三个步骤来实现:

  • (1)首先在system_server 线程中执行了SystemServer的startOtherServices 方法,在startOtherServices方法中会调用WMS的main方法,main方法会创建WMS,创建的过程在android.display线程中实现,创建WMS的优先级更高,因此system_server线程要等WMS创建完成后,处于等待状态的system_server线程才会被唤醒从而继续执行下面的代码。

  • (2)在WMS的构造方法中会调用WMS的initPolicy方法,在initPolicy方法中又会调用PWM 的init 方法,PWM的init方法在android.ui线程中运行,它的优先级要高于android.display线程,因此“android.display”线程要等PWM的init方法执行完毕后,处于等待状态的android.display线程才会被唤醒从而继续执行下面的代码。

  • (3)PWM的init方法执行完毕后,android.display线程就完成了WMS的创建,等待的system_server线程被唤醒后继续执行WMS的main 方法后的代码逻辑,比如WMS的displayReady方法用来初始化屏幕显示信息(SystemServer的startOtherServices方法的注释7处)。

3.3 WMS的重要成员

image.png
  • 1.mPolicy:WindowManagerPolicy

    mPolicy是WindowManagerPolicy(WMP)类型的变量。WindowManagerPolicy是窗口管理策略的接口类,用来定义一个窗口策略所要遵循的通用规范,并提供了WindowManager所有的特定的UI行为。它的具体实现类为PhoneWindowManager,这个实现类在WMS创建时被创建。WMP允许定制窗口层级和特殊窗口类型以及关键的调度和布局。

  • 2.mSessions:ArraySet

    mSessions是ArraySet类型的变量,元素类型为Session,它主要用于进程间通信,其他的应用程序进程想要和WMS进程进行通信就需要经过Session,并且每个应用程序进程都会对应一个Session,WMS保存这些Session用来记录所有向WMS提出窗口管理服务的客户端。

  • 3.mWindowMap:WindowHashMap

    mWindowMap 是WindowHashMap类型的变量,WindowHashMap继承了HashMap,它限制了HashMap的key值的类型为IBinder,value值的类型为WindowState。WindowState用于保存窗口的信息,在WMS中它用来描述一个窗口。综上得出结论,mWindowMap就是用来保存WMS中各种窗口的集合。

  • 4.mFinishedStarting:ArrayList

    mFinishedStarting 是ArrayList 类型的变量,元素类型为AppWindowToken,它是WindowToken的子类。要想理解mFinishedStarting的含义,需要先了解WindowToken是什么。WindowToken主要有两个作用:

    • 可以理解为窗口令牌,当应用程序想要向WMS 申请新创建一个窗口,则需要向WMS出示有效的WindowToken。AppWindowToken作为WindowToken的子类,主要用来描述应用程序的WindowToken 结构,应用程序中每个Activity 都对应一个AppWindowToken。

    • WindowToken会将同一个组件(比如同一个Activity)的窗口(WindowState)集合在一起,方便管理。

mFinishedStarting 就是用于存储已经完成启动的应用程序窗口(比如Activity)的AppWindowToken的列表。除了mFinishedStarting外,还有类似的mFinishedEarlyAnim和mWindowReplacementTimeouts,其中mFinishedEarlyAnim存储了已经完成窗口绘制并且不需要展示任何已保存surface的应用程序窗口的AppWindowToken。mWindowReplacementTimeout存储了等待更换的应用程序窗口的AppWindowToken,如果更换不及时,旧窗口就需要被处理。
  • 5.mResizingWindows:ArrayList

    mResizingWindows是ArrayList类型的变量,元素类型为WindowState。mResizingWindows是用来存储正在调整大小的窗口的列表。与mResizingWindows 类似的还有mPendingRemove、mDestroySurface和mDestroyPreservedSurface等,其中mPendingRemove是在内存耗尽时设置的,里面存有需要强制删除的窗口,mDestroySurface里面存有需要被销毁的Surface,mDestroyPreservedSurface 里面存有窗口需要保存的等待销毁的Surface,为什么窗口要保存这些Surface?这是因为当窗口经历Surface变化时,窗口需要一直保持旧Surface,直到新Surface的第一帧绘制完成。

  • 6.mAnimator:WindowAnimator

    mAnimator是WindowAnimator类型的变量,用于管理窗口的动画以及特效动画。

  • 7.mH:H

    mH 是H 类型的变量,系统的Handler 类,用于将任务加入到主线程的消息队列中,这样代码逻辑就会在主线程中执行。

  • 8.mInputManager:InputManagerService

  • mInputManager 是InputManagerService 类型的变量,输入系统的管理者。InputManagerService(IMS)会对触摸事件进行处理,它会寻找一个最合适的窗口来处理触摸反馈信息,WMS是窗口的管理者,因此WMS作为输入系统的中转站是再合适不过了。

3、 Window的添加过程(WMS处理部分)

我们知道Window的操作分为两大部分,一部分是WindowManager处理部分,另一部分是WMS处理部分。

无论是系统窗口还是Activity,它们的Window的添加过程都会调用WMS的addWindow方法,由于这个方法代码逻辑比较多,这里分为3个部分来阅读。

3.1 addWindow方法part1

image.png
image.png

WMS的addWindow返回的是addWindow的各种状态,比如添加Window成功,无效的display等,这些状态被定义在WindowManagerGlobal中。

在注释1处根据Window的属性,调用WMP的checkAddPermission方法来检查权限,具体在PhoneWindowManager的checkAddPermission方法中实现,如果没有权限则不会执行后续的代码逻辑。

在注释2处通过displayId 来获得窗口要添加到哪个DisplayContent 上,如果没有找到DisplayContent,则返回WindowManagerGlobal.ADD_INVALID_DISPLAY 这一状态,其中DisplayContent用来描述一块屏幕。

在注释3处,type代表一个窗口的类型,它的数值介于FIRST_SUB_WINDOW和LAST_SUB_WINDOW之间(1000~1999),这个数值定义在WindowManager中,说明这个窗口是一个子窗口。

在注释4处,attrs.token是IBinder类型的对象,windowForClientLocked 方法内部会根据attrs.token 作为key值从mWindowMap中得到该子窗口的父窗口。接着对父窗口进行判断,如果父窗口为null或者type的取值范围不正确则会返回错误的状态。

3.2 addWindow方法part2

image.png
image.png

在注释1处通过displayContent的getWindowToken方法得到WindowToken。

在注释2处,如果有父窗口就将父窗口的type 值赋值给rootType,如果没有将当前窗口的type值赋值给rootType。

接下来如果WindowToken为null,则根据rootType或者type的值进行区分判断,如果rootType值等于TYPE_INPUT_METHOD、TYPE_WALLPAPER 等值时,则返回状态值WindowManagerGlobal.ADD_BAD_APP_TOKEN,说明rootType值等于TYPE_INPUT_METHOD、TYPE_WALLPAPER 等值时是不允许WindowToken为null的。

通过多次的条件判断筛选,最后会在注释3处隐式创建WindowToken,这说明当我们添加窗口时可以不向WMS提供WindowToken,前提是rootType和type的值不为前面条件判断筛选的值。

WindowToken隐式和显式的创建肯定是要加以区分的,注释3处的第4个参数为false就代表这个WindowToken是隐式创建的。接下来的代码逻辑就是WindowToken不为null的情况,根据rootType和type的值进行判断,比如在注释4处判断如果窗口为应用程序窗口,在注释5处将WindowToken 转换为专门针对应用程序窗口的AppWindowToken,然后根据AppWindowToken的值进行后续的判断。

3.3 addWindow方法part3

image.png
image.png

在注释1处创建了WindowState,它存有窗口的所有的状态信息,在WMS中它代表一个窗口。在创建WindowState传入的参数中,this指的是WMS,client指的是IWindow,IWindow会将WMS中窗口管理的操作回调给ViewRootImpl,token指的是WindowToken

紧接着在注释2和注释3处分别判断请求添加窗口的客户端是否已经死亡、窗口的DisplayContent是否为null,如果是则不会再执行下面的代码逻辑。

在注释4处调用了WMP的adjustWindowParamsLw方法,该方法在PhoneWindowManager中实现,此方法会根据窗口的type对窗口的LayoutParams的一些成员变量进行修改。

在注释5处调用WMP的prepareAddWindowLw方法,用于准备将窗口添加到系统中。

在注释6处将WindowState添加到mWindowMap中。

在注释7处将WindowState添加到该WindowState对应的WindowToken中(实际是保存在WindowToken的父类WindowContainer中),这样WindowToken就包含了同一个组件的WindowState。

3.4 addWindow方法总结

addWindow方法分了3个部分来进行讲解,主要就是做了下面4件事:

  • 对所要添加的窗口进行检查,如果窗口不满足一些条件,就不会再执行下面的代码逻辑。

  • WindowToken相关的处理,比如有的窗口类型需要提供WindowToken,没有提供的话就不会执行下面的代码逻辑,有的窗口类型则需要由WMS隐式创建WindowToken。

  • WindowState的创建和相关处理,将WindowToken和WindowState相关联。

  • 创建和配置DisplayContent,完成窗口添加到系统前的准备工作。

3.5 Window的删除过程

和Window的创建和更新过程一样,要删除Window需要先调用WindowManagerImpl的removeView方法,在removeView方法中又会调用WindowManagerGlobal的removeView方法,我们就从这里开始讲起。为了表述得更易于理解,本节将要删除的Window(View)简称为V。WindowManagerGlobal的removeView方法如下所示:

image.png

在注释1处找到V在View列表中的索引,

在注释2处调用了removeViewLocked方法并将这个索引传进去,如下所示:

image.png

在注释1处根据传入的索引在ViewRootImpl列表中获得V的ViewRootImpl。

在注释2处得到InputMethodManager实例,如果InputMethodManager实例不为null则在注释3处调用InputMethodManager的windowDismissed方法来结束V的输入法相关的逻辑。

在注释4处调用ViewRootImpl的die方法,如下所示:

image.png

在注释1处如果immediate为ture(需要立即执行),并且mIsInTraversal值为false则执行注释2处的代码,mIsInTraversal在执行ViewRootImpl的performTraversals方法时会被设置为true,在performTraversals方法执行完时被设置为false,因此注释1处可以理解为die 方法需要立即执行并且此时ViewRootImpl 不再执行performTraversals 方法。

在注释2处的doDie方法如下所示:

image.png
image.png

在注释1处用于检查执行doDie方法的线程的正确性,在注释1处的内部会判断执行doDie方法线程是否是创建V的原始线程,如果不是就会抛出异常,这是因为只有创建V的原始线程才能够操作V。

注释2到注释3处的代码用于防止doDie方法被重复调用。

在注释4处V有子View就会调用注释5处的dispatchDetachedFromWindow方法来销毁View。

在注释6处如果V有子View并且不是第一次被添加,就会执行后面的代码逻辑。

注释7处的WindowManagerGlobal的doRemoveView方法,如下所示:

image.png

在WindowManagerGlobal中维护了和Window操作相关的三个列表,doRemoveView方法会从这三个列表中清除V对应的元素。

在注释1处找到V对应的ViewRootImpl在ViewRootImpl列表中的索引,接着根据这个索引从ViewRootImpl列表、布局参数列表和View列表中删除与V对应的元素。

我们接着回到ViewRootImpl的doDie方法,查看注释5处的dispatchDetachedFromWindow方法做了什么:

image.png

在dispatchDetachedFromWindow方法中主要调用了IWindowSession的remove方法,IWindowSession在Server端的实现为Session。

Session的remove方法如下所示:

image.png

接着查看WMS的removeWindow方法:

image.png

在注释1处用于获取Window对应的WindowState,WindowState用于保存窗口的信息,在WMS中它用来描述一个窗口。

接着在注释2处调用WindowState的removeIfPossible方法,如下所示:

image.png

又会调用removeIfPossible方法,如下所示:

image.png

removeIfPossible方法和它的名字一样,并不是直接执行删除操作的,而是进行多个条件判断过滤,满足其中一个条件就会return,推迟删除操作。比如V正在运行一个动画,这时就得推迟删除操作,直到动画完成。通过这些条件判断过滤就会执行注释1处的removeImmediately方法:

image.png
image.png

removeImmediately 方法如同它的名字一样,用于立即进行删除操作。

在注释1处的mRemoved为true意味着正在执行删除Window操作,在注释1到注释2处之间的代码用于防止重复删除操作。

在注释3处如果当前要删除的Window是StatusBar或者NavigationBar就会将这个Window从对应的控制器中删除。

在注释4处将V对应的Session从WMS的ArraySet<Session> mSessions 中删除并清除Session 对应的SurfaceSession 资源(SurfaceSession是SurfaceFlinger的一个连接,通过这个连接可以创建1个或者多个Surface并渲染到屏幕上)。

在注释5处调用了WMS的postWindowRemoveCleanupLocked方法用于对V进行一些集中的清理工作。

Window 的删除过程就讲到这里,虽然删除的操作逻辑比较复杂,但是可以简单地总结为以下4点:

  • 检查删除线程的正确性,如果不正确就抛出异常。

  • 从ViewRootImpl列表、布局参数列表和View列表中删除与V对应的元素。

  • 判断是否可以直接执行删除操作,如果不能就推迟删除操作。

  • 执行删除操作,清理和释放与V相关的一切资源。

链接:https://juejin.cn/post/7120522752200540196
作者:复制粘贴改改改

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

推荐阅读更多精彩内容