Android中WMS的理解与简介

1. WMS的概念

从名字可以看出,window表明它是与窗口相关的,Manager表明它具有管理者的身份。简单来讲,它是窗口管理员。窗口是一个抽象的概念,从用户的角度来讲,它是一个界面。从SufaceFlinger的角度来讲,它是一个Layer,承载着和界面有关的数据和属性。所以它是一个WindowState,用于管理和界面有关的状态。

WMS也是系统服务,由SystemServer启动。直到关机时才会退出。发生异常时必须重启。

  • 1.1 WMS涉及的元素:

    • WindowManagerPolicy-mPolicy:窗口策略类。

      Android提供了WindowManagerPolicy接口定义Ui显示策略,手机的实现类为PhoneWindowManager。也可以自己实现WindowManagerPolicy来自定义策略。

    窗口策略:这里的initPolicy()初始化的PhoneWindowManager代表的是一种窗口策略。应用到WMS中则代表了Android显示系统所遵循的统一的窗口显示规则。针对不同的产品,UI显示策略通常是不一样的。如手机一般都有Status Bar,但是平板没有。所以要设定不同的策略。

    • ArraySet<Session>-mSessions:主要用于进程间通信,其他应用程序想要和WMS通信就需要经过Session,每个应用程序进程都会对应一个Session,WMS保存这些Session用来记录所有向WMS提出窗口管理服务的客户端。

    • WindowHashMap<IBinder,WindowState>-mWindowMap:WindowState用于保存窗口信息,用来描述一个窗口。mWindowMap其实就是用来保存WMS中各种窗口的集合

    • ArrayList<AppWindowToken>-mFinishedStarting:AppWindowToken为WindowToken的子类,WindowToken主要有2个作用:

      • 窗口令牌,当应用程序想要向WMS申请新创建一个窗口,则需要向WMS出示有效的WindowToken。AppWindowToken主要用来描述应用程序的WindowToken结构,应用程序中每个Activity都对应一个AppWindowToken
      • WindowToken会将同一个组件(比如同一个Activity)的窗口(WindowState)集合在一起,方便管理

      mFinishedStarting就是用于存储已经完成启动的应用程序窗口(比如Activity)的AppWindowToken列表。

    • ArrayList<WindowState>-mResizingWindows:用来存储正在调整大小的窗口列表。

    • WindowAnimator-mAnimator:用于管理窗口的动画以及特效动画

    • H-h:系统的Handler类,用于将任务加入到主线程消息队列中

    • InputManagerService-mInputManager:输入系统的管理者。会对触摸事件进行处理,他会寻找一个合适的窗口来处理触摸返回信息,WMS是窗口的管理者,所以需要持有IMS引用

    • 显示图形的需求

      • Application Window:普通应用程序显示申请所产生的window,和系统窗口相比,它们的窗口层级值比较低。
      • System Window:系统顶部的系统状态栏,壁纸等
      • Sub Window:Toast等弹窗
    • ActivityManagerService:AMS管理者所有的Activity,而Activity的变化通常会带来界面上的改变。那么界面上产生的变化(淡出等动画效果)也要涉及WMS

  • 1.2 WMS包含的功能

    • 窗口的添加与删除:当某个进程由显示需求时,它可以请求WMS添加一个窗口,并于不再需要显示时移除该窗口
    • 启动窗口:当增了一个窗口时,在某些条件下需要添加一个启动窗口
    • 窗口动画:窗口切换时,采用窗口动画可以加强UI特效。可以定制
    • 窗口大小: Android系统支持显示不同大小的窗口,如StatusBar就只是屏幕最顶层的一条Bar,还有对话框,浮窗等
    • 窗口层级:由Z-Order决定,值越大,排序越靠后,会被值小的窗口盖住
    • 事件派发
  • 1.3 内部组织方式

    当一个新的Activity被启动时,它首先需要在AMS中注册,此时AMS会在内部生成一个ActivityRecord来记录这个Activity;另外因为Activity是四大组件中专门用于UI显示的,所以WMS也会对它以WindowState的形式进行记录。

    所以Activity在AMS中的表现形式为ActivityRecord,在WMS中的表现形式为WindowState,其中WMS还有一个变量AppWindowToken和AMS中的ActivityRecord相对应,这样它们就关联了起来。

2. WMS的启动

WMS由SystemServer进程启动,在SystemServer的main()中执行了 startOtherServices(),WMS就在这里启动,看下代码:

       private void startOtherServices() {
                     //part1
               final Watchdog watchdog = Watchdog.getInstance();
               watchdog.init(context, mActivityManagerService);
                     //part2
               inputManager = new InputManagerService(context);
                     //part4
               wm = WindowManagerService.main(context, inputManager,
                       mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
                       !mFirstBoot, mOnlyCore, new PhoneWindowManager());
                     //part5
               ServiceManager.addService(Context.WINDOW_SERVICE, wm);
               ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
  • part1部分初始化了WatchDog,WatchDog用来监控系统的一些关键服务运行的情况,如WMS,它每分钟都会对被监控的系统服务进行检查,如果被监控的服务出现了死锁,则会杀死WatchDog所在进程,也就是SystemServer进程。前面提到过,WMS发生异常时必须重启,就是在这里进行监控
  • part2部分构造了IMS,IMS负责输入消息,WMS需要持有它的引用
  • part3部分通过WindowManagerService.main()构造了一个WMS对象,并传入了构造好的IMS,这样WMS就持有了IMS引用。
  • part5中WMS,IMS注册到了ServiceManager中,这样如果某个客户端想使用它们的功能,就可以去ServiceManager查询,进而可以进行进程间通信

WMS是通过它自己的main()构造的,继续看这个方法:

       public static WindowManagerService main() {
           DisplayThread.getHandler().runWithScissors(() ->
                   sInstance = new WindowManagerService(context, im, haveInputMethods, showBootMsgs,
                           onlyCore, policy), 0);
           return sInstance;
       }

在DisplayThread线程中创建了WMS,这个线程是一个单例的前台线程,它用来处理需要低延迟显示的相关操作,并只能由WM,DisplayManager和InputManager实时执行快速操作。由于WMS的优先级更高,所以system_server线程需要等待DisplayThread线程执行完创建WMS的操作,才会继续执行,在这之前它会一直等待。

再看WMS的构造方法:

       private WindowManagerService() {
                     //持有IMS引用
           mInputManager = inputManager; // Must be before createDisplayContentLocked.
                     //获取DisplayManager
           mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
           //获取Display数组
                     mDisplays = mDisplayManager.getDisplays();
           //遍历Display,将Display封装成DisplayContent,DisplayContent用来描述一块屏幕
           for (Display display : mDisplays) {
               createDisplayContentLocked(display);
           }
                 //持有AMS引用
           mActivityManager = ActivityManager.getService();
                 //创建mAnimator,用于管理窗口动画
           mAnimator = new WindowAnimator(this);
           //创建窗口策略管理类PhoneWindowManager
                     initPolicy();
           //将WMS添加到WatchDog中
           Watchdog.getInstance().addMonitor(this);
       }

在WMS的构造中进行了一些初始化,持有了AMS,IMS引用,获取了所有管理的屏幕对象DisplayContent,创建了策略管理类PWM,最后将自己添加到WathDog中。再看PWM的初始化,也就是initPolicy():

       private void initPolicy() {
           UiThread.getHandler().runWithScissors(new Runnable() {
               @Override
               public void run() {
                   WindowManagerPolicyThread.set(Thread.currentThread(), Looper.myLooper());
   
                   mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this);
               }
           }, 0);
       }
   

PWM的init()运行在UI线程中,它的优先级高于Display线程,因此DisPlay线程要等待它执行完后才会执行。

总结:

  • 首先在System_server线程中执行了SystemServer的starOtherService(),在starOtherService()中会调用WMS的main(),这个main()会创建WMS,创建的过程在disPlay线程中实现,因WMS的优先级更高,因此System_server线程要等WMS创建完成后,处于等待状态的System_server线程才会被唤醒从而继续执行下面的代码
  • 在WMS的构造中会调用WMS的initPolicy(),在initPolicy()中又会调用PWM的init(),这个init()在UI线程中运行,它的优先级要高于display线程,因此display线程要等到PWM的init()执行完毕后,才会被唤醒执行后面的代码
  • PWM的init()执行完毕后,disPlay线程就完成了WMS的创建,等待的system_server线程就会被唤起继续执行main()之后的逻辑

3. Window添加过程

window的添加从WMS的addWindow()开始:

       public int addWindow() {
                     //part1权限检查
           int res = mPolicy.checkAddPermission(attrs, appOp);
   
           synchronized(mWindowMap) {
                     //part2寻找displayContent
           final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
                     //避免重复添加
           if (mWindowMap.containsKey(client.asBinder())) {
                   return WindowManagerGlobal.ADD_DUPLICATE_ADD;
               }
                 //part3判断是否子窗口
           if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
                    //寻找父窗口
             parentWindow = windowForClientLocked(null, attrs.token, false);
            }
            //part4获取WindowToken
            WindowToken token = displayContent.getWindowToken(
                       hasParent ? parentWindow.mAttrs.token : attrs.token);
                         //如果有父窗口就将父窗口的type赋值给rootType,如果没有则将当前窗口的type赋值给rootType
             final int rootType = hasParent ? parentWindow.mAttrs.type : type;
             
                        if (token == null) {
            //part5如果token为null,则隐式创建token,这说明创建窗口时可以不向WMS提供提供WindowToken。这里的参数false代表token是隐式创建的,不是传进来的
                token = new WindowToken(this, binder, type, false, displayContent,
                           session.mCanAddInternalSystemWindow);
               } else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
             //part6如果为应用程序窗口,将windowToken转换为应用程序窗口的AppWindowToken
             atoken = token.asAppWindowToken();
              }
             
             //part7创建windowState,它存有窗口的所有信息,代表一个窗口
             final WindowState win = new WindowState(this, session, client, token, parentWindow, appOp[0], seq, attrs, viewVisibility, session.mUid, session.mCanAddInternalSystemWindow);
              //part8判断请求添加窗口的客户端是否已经死亡
              if (win.mDeathRecipient == null) {
                   return WindowManagerGlobal.ADD_APP_EXITING;
               }
                        //part9判断窗口的DisplayContent是否为null
               if (win.getDisplayContent() == null) {
                   return WindowManagerGlobal.ADD_INVALID_DISPLAY;
               }
                        //part10根据窗口的type对窗口的LayoutParams成员变量进行修改
                mPolicy.adjustWindowParamsLw(win.mAttrs);
              win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));
                        //part11准备将窗口添加到系统中
              res = mPolicy.prepareAddWindowLw(win, attrs);
                win.attach();
              //part12将windowState添加到windowMap中
                mWindowMap.put(client.asBinder(), win);

                            //part13将windowstate添加到该windowstate对应的windowtoken中,这样windowtoken中就包含了同一个组件的windowstate
              win.mToken.addWindow(win);

   
           return res;
       }
  • part1处进行了权限检查,如果没有权限则不能添加系统窗口,需要在在manifest中注册权限。没有权限就不会执行后续代码,但是Tost这种系统窗口是用户可以创建的,所以这里需要对权限进行控制。
  • part2处通过displayId来获得窗口要添加到哪个DisplayContent上。
  • part3处判断了是否是子窗口,如果是子窗口,那么还要找出父窗口;而且父窗口本身不能是其他窗口的子窗口,否则会添加失败
  • part4处创建windowToken,如果有父窗口则把父窗口的type赋值给rootType,如果没有则把自身的type赋值给rootType
  • part5判断如果token为null,则隐式创建token,这说明创建窗口时可以不向WMS提供提供WindowToken。这里的参数false代表token是隐式创建的,不是传进来的
  • part6如果token不为null且为应用程序窗口,将windowToken转换为应用程序窗口的AppWindowToken
  • part7创建windowState,它存有窗口的所有信息,代表一个窗口
  • part8判断请求添加窗口的客户端是否已经死亡
  • part9判断窗口的DisplayContent是否为null
  • part10根据窗口的type对窗口的LayoutParams成员变量进行修改
  • part11准备将窗口添加到系统中
  • part12将windowState添加到windowMap中
  • part13将windowstate添加到该windowstate对应的windowtoken中,这样windowtoken中就包含了同一个组件的windowstate

总结:addWindow()主要做了一下事情

  • 对所要添加的窗口进行检查,如果窗口不满足一些条件,就不会执行下面的代码。
  • WindowToken的相关处理,有的窗口类型需要提供windowToken,没有的话就不会执行下面的逻辑,有的则需要WMS隐式创建windowToken
  • WindowState的创建和相关处理,将WindowToken和WindowState相关联
  • 创建和配置DisplayContent,完成窗口添加到系统前到准备工作

4. Window的删除过程

窗口的删除从WindowManagerGlobal.removeVIew()发起,看代码:

       public void removeView(View view, boolean immediate) {
           synchronized (mLock) {
                 //获取要删除的view的索引
               int index = findViewLocked(view, true);
               //传入索引
               removeViewLocked(index, immediate);
      }

这里获取了要删除的View的索引,然后调用了removeViewLocked():

       private void removeViewLocked(int index, boolean immediate) {
             //获取viewRoot
           ViewRootImpl root = mRoots.get(index);
           View view = root.getView();
           if (view != null) {
               //获取InputMethodManager实例
               InputMethodManager imm = InputMethodManager.getInstance();
               if (imm != null) {
                   //结束view的输入法相关逻辑
                   imm.windowDismissed(mViews.get(index).getWindowToken());
               }
           }
           boolean deferred = root.die(immediate);
       }

这里调用了ViewRootImpl.die():

       boolean die(boolean immediate) {
             //immediate代表是否需要立即执行,及ViewRootImpl是否在执行performTraversals
           if (immediate && !mIsInTraversal) {
               doDie();
               return false;
            }
           mHandler.sendEmptyMessage(MSG_DIE);
           return true;
       }

这里判断了是否需要立即执行,以及ViewRootImpl是否在执行performTraversals,当ViewRootImpl在执行performTraversals时mIsInTraversal为被置为true,所以doDie()执行的条件是ViewRootImpl不执行performTraversals()时,再看doDie():

         void doDie() {
             //检查线程,判断执行doDie()方法线程是否是创建view的原始线程,如果不是就抛出异常。只有创建view的原始线程才能操作view
           checkThread();
           synchronized (this) {
                    //是否有子view
               if (mAdded) {
                                 //如果有则销毁子view
                   dispatchDetachedFromWindow();
               }
               if (mAdded && !mFirst) {
                                //如果有子view并且不是第一次被添加
                   destroyHardwareRenderer();
               }
           }
    
                    
           WindowManagerGlobal.getInstance().doRemoveView(this);
       }

这里做了线程检查,只有创建view的原始线程才能操作view。然后判断是否有子 view有的话则调用dispatchDetachedFromWindow()去销毁,最后调用了WindowManagerGlobal.doRemoveView():

       void doRemoveView(ViewRootImpl root) {
           synchronized (mLock) {
               final int index = mRoots.indexOf(root);
               if (index >= 0) {
                   //
                   mRoots.remove(index);
                   mParams.remove(index);
                   final View view = mViews.remove(index);
                   mDyingViews.remove(view);
               }
           }
       }

将要删除的view从WindowManagerGlobal中维护的列表中移除。

再回头去看dispatchDetachedFromWindow():

       void dispatchDetachedFromWindow() {
               mWindowSession.remove(mWindow);
                }

这里调用了WindowSession.remove(),那么这里进行了进程间通讯,最终会调用到WMS的removeWindow():

       void removeWindow(Session session, IWindow client) {
           synchronized(mWindowMap) {
                 //获取WindowState
               WindowState win = windowForClientLocked(session, client, false);
               win.removeIfPossible();
           }
       }

这里线获取了WindowState,然后调用了WindowState.removeIfPossible():

       void removeIfPossible() {
           super.removeIfPossible();
           removeIfPossible(false /*keepVisibleDeadWindow*/);
       }

继续看removeIfPossible(false):

       private void removeIfPossible(boolean keepVisibleDeadWindow) {
            //上面省略了一部分代码,主要是进行条件判断,如果满足任何一条则会return,推迟删除操作
           removeImmediately();
       }

在removeIfPossible()中会进行一系列判断,如果满足任何一个条件都会推迟操作,如view在进行动画时等。

再看removeImmediately():

       void removeImmediately() {
           super.removeImmediately();
                     //如果当前要删除的是StatusBar或者NavigationBar,将这个window从对应的控制器中删除
           mPolicy.removeWindowLw(this);
                 //将对应的session删除
           mSession.windowRemovedLocked();
           //调用WMS进行一些清理工作
           mService.postWindowRemoveCleanupLocked(this);
       }

最后通过removeImmediately()完成了窗口的删除。

总结:window的删除可以总结为以下几点

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

推荐阅读更多精彩内容