WMS

FrameWork源码阅读之WMS源码与分析

提示:WMS是WindowManagerService缩写,后面都用WMS代替。
一. Window、WindowManager、WMS

在这里插入图片描述

Window: window 它是一个抽象类,具体实现类为 PhoneWindow ,它对 View 进行管理。Window是View的容器,View是Window的具体表现内容。

WindowManager 是一个接口类,继承自接口 ViewManager ,从它的名称就知道它是用来管理 Window 的,它的实现类为 WindowManagerImpl。

WMS 是窗口的管理者,它负责窗口的启动、添加和删除。另外窗口的大小和层级也是由 WMS 进行管理的。

二. WindowManager 的关联类

在这里插入图片描述

​ WindowManager 其实是一个接口,它继承自 ViewManager , ViewManager 中定义了 3 个抽象方法,分别是用来添加、更新、删除 View 的,WindowManager 继承了父类接口的方法,说明也具备了父类的能力。

​ WindowManagerImpl是WindowManager的实现类,但是具体的功能都会委托给WindowManagerGlobal来实现。

三. Window的类型

Window 的类型有很多种,比如应用程序窗口、系统错误窗口、输入法窗口、PopWindow、Toast、Dialog 等。总的来说 Window 分为三大类型,分别是 Application Window(应用程序窗口)、Sub Window(子窗口)、System Window (系统窗口),每个大类型中又包含了很多种类型,它们都定义在 WindowManager 的静态内部类 LayoutParams 中,接下来分别对这三大类型进行讲解。

3.1应用程序窗口
//WindowManager.java
        /**
         * 表示应用程序窗口类型初始值
         */
        public static final int FIRST_APPLICATION_WINDOW = 1;
         /**
          * 窗口的基础值,其它的窗口值要大于这个值
          */
        public static final int TYPE_BASE_APPLICATION   = 1;
        /**
         * 普通的应用程序窗口
         * 
         */
        public static final int TYPE_APPLICATION        = 2;
        /**
         * 应用程序启动窗口的类型,用于系统在应用程序窗口启动前显示的窗口
         */
        public static final int TYPE_APPLICATION_STARTING = 3;

        public static final int TYPE_DRAWN_APPLICATION = 4;
        /**
         * 表示应用程序窗口类型的结束值值的范围是 1~99
         */
        public static final int LAST_APPLICATION_WINDOW = 99;

​ 通过上面的常量我们知道,应用程序的窗口的 Type 值范围为 1~99,这个值的大小涉及窗口的层级。

3.2子窗口

​ 子窗口,顾名思义,他是一个必须依附在其它窗口之上,是不可以独立存在。子窗口的类型定义如下:

//WindowManager.java
        /**
         * 子类窗口初始化值
         */
        public static final int FIRST_SUB_WINDOW = 1000;

        public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
    
        public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
    
        public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
    
        public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
        public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW + 4;
    
        public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
        /**
         * 子类窗口类型结束值
         */
        public static final int LAST_SUB_WINDOW = 1999;

3.3系统窗口

​ 比如 Android 中的 Toast、输入法窗口、系统音量条窗口、系统错误窗口都是属于系统级别的 Window . 系统级别的 window 类型定义有如下几个:

//WindowManager.java

        /**
         * 系统类型窗口类型初始值
                  */public static final int FIRST_SYSTEM_WINDOW     = 2000;

        /**
         * 系统状态栏窗口
         */
        public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;
    
        /**
         * 搜索条窗口
         */
        public static final int TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1;
    
        /**
         * 通话窗口
         */
        @Deprecated
        public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;
    
        /**
         * 系统 alert 窗口
         */
        @Deprecated
        public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3;
    
        /**
         * 系统锁屏窗口
         */
        public static final int TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4;
    
        /**
         * Toast 窗口
         */
        @Deprecated
        public static final int TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5;
    
    			...
        /**
         * 系统窗口类型的结束值
         */
        public static final int LAST_SYSTEM_WINDOW      = 2999;

​ 系统窗口的类型值有接近 40 多个,这里只列举了部分,系统窗口的 Type 值范围为 2000 - 2999.

四. Window的标志

​ Window 的标志也就是 Flag, 用于控制 Window 的现实,同样被定义在 WindowManager 的内部类 LayoutParams 中,一共有 20 多个,这里给出几个比较常用的,如下:

Window Flag说明
FLAG_ALLOW_LOCK_WHILE_SCREEN_ON只要窗口可见,就允许在开启状态的屏幕上锁屏
FLAG_NOT_FOCUSABLE窗口不能获得输入焦点,设置该标志的同时,FLAG_NOT_TOUCH_MODAL 也会被设置
FLAG_NOT_TOUCHABLE窗口不接收任何触摸事件
FLAG_NOT_TOUCH_MODAL将该窗口区域外的触摸事件传递给其它的 Window,而自己只会处理窗口区域内的触摸事件
FLAG_KEEP_SCREEN_NO只要窗口可见,屏幕就会一直常亮
FLAG_LAYOUT_NO_LIMITS允许窗口超过屏幕之外
FLAG_FULISCREEN隐藏所有的屏幕装饰窗口,比如在游戏、播放器中的全屏显示
FLAG_SHOW_WHEN_LOCKED窗口可以在锁屏的窗口之上显示
FLAG_IGNORE_CHEEK_PRESSES当用户的脸贴近屏幕时(比如打电话),不会去响应事件
FLAG_TURN_SCREEN_NO窗口显示时将屏幕点亮

设置 Window 的 Flag 有 3 种方法

  1. 通过 Window 的 addFlag 方法
   Window mWindow = getWindow();
   mWindow.addFlag(WindowManager.LayoutParams.FLAG_FULLSCREEN);
   
  1. 通过 Window 的 setFlags 方法

    Window mWindow = getWindow();
    mWindow.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN)
    
    
  2. 给 LayoutParams 设置 Flag, 并通过 WindowManager 的 addView 方法进行添加

    WindowManager.LayoutParams mWindowLayoutParams = new WindowManager.LayoutParams();
    mWindowLayoutParams.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN;
    WindowManager mWindowManager = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
    Text mText = new Text(this);
    mWindowManager.addView(mTextView,mWindowLayoutParams);
    
五. 软件盘相关模式

​ 窗口与窗口的叠加是十分常见的场景,但是如果其中的窗口是软件盘的窗口,可能就会出现一些问题,比如典型的用户登录页面,默认的情况弹出软件盘窗口可能遮挡输入框下方的按钮,这样用户体验非常糟糕。为了使得软键盘窗口能够按照期望来显示, WindowManager 的静态内部类 LayoutParams 中定义了软件盘相关模式,这里给出常用的几个:

SoftInputMode描述
SOFT_INPUT_STATE_UNSPECIFIED没有设定状态,系统会选择一个合适的状态或依赖于主题的设置
SOFT_INPUT_STATE_UNCHANGED不会改变软键盘状态
SOFT_INPUT_STATE_ALWAYS_HIDDEN当窗口获取焦点时,软键盘总是被隐藏
SOFT_INPUT_ADJUST_RESIZE当软键盘弹出时,窗口会调整大小
SOFT_INPUT_ADJUST_PAN当软键盘弹出时,窗口不需要调整大小,要确保输入焦点是可见的
SOFT_INPUT_STATE_HIDDEN当用户进入该窗口时,软键盘默认隐蔽

从上面给出的 SoftInputMode ,可以发现,它们与 AndroidManifest.xml 中 Activity 的属性 android:windowsoftInputMode 是对应的。因此,除了在 AndroidManifest.xml 中为 Activity 配置还可以通过代码动态配置,如下所示:

geWindow().setSoftInputMode(MindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
六. Window的添加

在这里插入图片描述

​ Activity#attach()方法之内PhoneWindow被创建,并同时创建一WindowManagerImpl负责维护PhoneWindow内的内容。

​ 在Activity#onCreate()中调用setContentView()方法,这个方法内部创建一个DecorView实例作为PhoneWindow的实体内容。

​ WindowManagerImpl决定管理DecorView,并创建一个ViewRootImpl实例,将ViewRootImpl与View树进行关联,这样ViewRootImpl就可以指挥View树的具体工作。

七. DecorView

在这里插入图片描述

	在一个Activity或者Dialog中,是包含一个Window窗口的对象,Window并不是真正的实体内容,通过View来表达真正的页面内容,这个View就是DecorView。  

​ DecorView是FrameLayout的子类,它可以被认为是Android视图树的根节点视图。

​ 在Activity的生命周期onCreate方法里面,是会设置好布局内容通过setContentView(布局id)的方式,这里是通过xml解析器转化为一个View,这个View会被添加到ContentView中去,成为唯一的子View

八. WindowManagerGlobal

在这里插入图片描述

WindowManagerGlobal是一个单例类,一个进程只有一个实例。它管理者所有Window的ViewRootImpl、DecorView、LayoutParams。

// Window的DecorView集合
private final ArrayList<View> mViews = new ArrayList<View>();
// DecorView的管理者ViewRootImpl的集合
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
// Window布局参数的集合
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
十. ViewRootImpl

ViewRootImpl是View树的树根,但它却又不是View,实现了View与WindowManager之间的通信协议,在WindowManagerGloble中的addView中被建立,是顶层DecorView的ViewParent

  • View树的树根并管理View树

  • 触发View的测量、布局和绘制

  • 输入响应的中转站

  • 负责与WMS进行进程间通信

10.1触发View的测量、布局和绘制

在这里插入图片描述

上图中就是一个完成的绘制流程,对应上了第一节中提到的三个步骤:

1)performMeasure():从根节点向下遍历View树,完成所有ViewGroup和View的测量工作,计算出所有ViewGroup和View显示出来需要的高度和宽度;

2)performLayout():从根节点向下遍历View树,完成所有ViewGroup和View的布局计算工作,根据测量出来的宽高及自身属性,计算出所有ViewGroup和View显示在屏幕上的区域;

3)performDraw():从根节点向下遍历View树,完成所有ViewGroup和View的绘制工作,根据布局过程计算出的显示区域,将所有View的当前需显示的内容画到屏幕上。

10.2与WMS通信

在这里插入图片描述

​ 在ViewRootImpl中mSession是IWindowSession类型,它是Binder对象,用于进行线程间通信,IWindowSession是Client端的代理,它的Server端的实现为Session。从上图可以看出,本地进程的ViewRootImpl是想和WMS进行通信需要经过Session。

十一. Window的更新

在这里插入图片描述

​ 我们在使用App应用的时候,如果需要输入内容,会需要键盘窗口,键盘窗口的弹出会引起之前窗口大小变化,这里就会发生Activity对应的窗口的更新。还有在横竖屏切换的时候,附生在当前Activity上面的PopupWindow、Dialog是会发生窗体变换的,这里面也是调用了窗口的更新。

​ 窗口的更新是一个非常常见的事情,现在开始了解它的执行流程,上图中WMImpl指的是WindowManagerImpl,它是WindowManager的实现类,但是似乎没有做过多的事情,直接委托给了WMGlobal(WindowManagerGlobal)。

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
		//  判断参数是否合法
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
    	//  设置参数到View身上
        view.setLayoutParams(wparams);
    
        synchronized (mLock) {
            // 通过View找到对应DecorView中的索引值
            int index = findViewLocked(view, true);
            // 通过索引值找到对应的ViewRootImpl对象
            ViewRootImpl root = mRoots.get(index);
            // 移除旧的参数
            mParams.remove(index);
            // 添加新的参数
            mParams.add(index, wparams);
            // 调用ViewRootImpl的函数
            root.setLayoutParams(wparams, false);
        }
    }

root.setLayoutParams会向系统请求UI刷新,其中过程和下面的UI刷新类似,等在下一小节中讲到,系统同意UI刷新会给ViewRootImpl发送信号,最终会转入ViewRootImpl的performTraversals,开始View的测量、布局、绘制的流程,其实就是本文10.1的流程内容。

十一. UI刷新
11.1 帧率

​ 帧率是以帧称为单位的位图图像连续出现在显示器上的频率(速率)FPS,以电影为例,动画至少要达到24FPS,才能保证画面的流畅性,低于这个值,肉眼会感觉到卡顿。在手机上,这个值被调整到60FPS,增加丝滑度,这也是为什么有个(1000/60)16ms的指标,一般而言目前的Android系统FPS也就是60,它是通过了一个VSYNC来保证每16ms最多绘制一帧。

11.2 请求刷新

​ UI的刷新并不是APP自己就可以单独决定了,它需要向同步信号服务申请同步信号,做之前先要打个报告,系统同意了接收到了指示才能开始行动。APP端持有回调,保证了信号服务中心派发信号的时候能接收到,之后进行真正的重绘动作,其实就是本文10.1的流程内容。

11.3 整体流程

在这里插入图片描述

setText最终调用invalidate申请重绘,最后走到ViewRootImpl的invalidate,请求VSYNC,在请求VSYNC的时候,会添加一个同步栅栏,防止UI线程中同步消息执行,这样做为了加快VSYNC的响应速度,如果不设置,VSYNC到来的时候,正在执行一个同步消息,那么UI更新的Task就会被延迟执行,这是Android的Looper跟MessageQueue决定的。

APP端触发重绘,申请VSYNC流程示意

在这里插入图片描述

等到VSYNC到来后,会移除同步栅栏,并率先开始执行当前帧的处理,调用逻辑如下

VSYNC回来流程示意

在这里插入图片描述

doFrame执行UI绘制的示意图

在这里插入图片描述

11.4 源码刷新跟踪

同TextView类似,View内容改变一般都会调用invalidate触发视图重绘,这中间经历了什么呢?View会递归的调用父容器的invalidateChild,逐级回溯,最终走到ViewRootImpl的invalidate,如下:

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
            // Propagate the damage rectangle to the parent view.
            final AttachInfo ai = mAttachInfo;
            final ViewParent p = mParent;
            if (p != null && ai != null && l < r && t < b) {
                final Rect damage = ai.mTmpInvalRect;
                damage.set(l, t, r, b);
                p.invalidateChild(this, damage);
            }

ViewRootImpl.java

void invalidate() {
    mDirty.set(0, 0, mWidth, mHeight);
    if (!mWillDrawSoon) {
        scheduleTraversals();
    }
}

ViewRootImpl会调用scheduleTraversals准备重绘,但是,重绘一般不会立即执行,而是往Choreographer的Choreographer.CALLBACK_TRAVERSAL队列中添加了一个mTraversalRunnable,同时申请VSYNC,这个mTraversalRunnable要一直等到申请的VSYNC到来后才会被执行,如下:

// 将UI绘制的mTraversalRunnable加入到下次垂直同步信号到来的等待callback中去
 // mTraversalScheduled用来保证本次Traversals未执行前,不会要求遍历两边,浪费16ms内,不需要绘制两次
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        // 防止同步栅栏,同步栅栏的意思就是拦截同步消息
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // postCallback的时候,顺便请求vnsc垂直同步信号scheduleVsyncLocked
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
         <!--添加一个处理触摸事件的回调,防止中间有Touch事件过来-->
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

Choreographer编舞者postCallBack中的mTraversalRunnable是TraversalRunnable对象, 内容如下:

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

doTraversal是调用的ViewRootImpl的doTraversal,最后会调用到PerformTraversal,开始View的测量、布局、绘制的流程,其实就是本文10.1的流程内容。

11.5 UI局部刷新

某一个View重绘刷新,并不会导致所有View都进行一次measure、layout、draw,只是这个待刷新View链路需要调整,剩余的View可能不需要浪费精力再来一遍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值