WMS(一)

作者: 涛涛123759 | 来源:发表于2021-12-25 21:35 被阅读0次

    Android知识总结

    一、WMS简介

    由图可见WindowManger是对Window进行管理,然后交给WMS进行处理。View是由抽象出来的Window进行管理的,我们所看到的是View。

    常见的Window有:DialogPopupWindow应用程序窗口输入法窗口系统错误窗口

    1.1、Activity与Window相关概念

    • 1、Activity只负责生命周期和事件处理
    • 2、Window只控制视图
    • 3、一个Activity包含一个Window,如果Activity没有Window,那就相当于Service
    • 4、AMS统一调度所有应用程序的Activity
    • 5、WMS控制所有Window的显示与隐藏以及要显示的位置

    1.2、Window

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

    Window表明它是和窗口相关的,窗口是一个抽象的概念,从用户的角度来讲,它是一个界面;从SurfaceFlinger的角度来看,它是一个Layer,承载着和界面有关的数据和属性;从WMS角度来看,它是一个WIndowState,用于管理和界面有关的状态。

    • 1、表示一个窗口的概念,是所有View的直接管理者,任何视图都通过 Window 呈现(点击事件由Window->DecorView->View; Activity的setContentView底层通过Window完成)。
    • 2、Window是一个抽象类,具体实现是 PhoneWindow
    • 3、创建Window需要通过WindowManager创建
    • 4、WindowManager是外界访问Window的入口
    • 5、Window具体实现位于WindowManagerService中
    • 6、WindowManager 和 WindowManagerService 的交互是通过IPC完成
    • 7、定义窗口样式和行为的抽象基类,用于作为顶层的 view 加到WindowManager 中,其实现类是 PhoneWindow。
    • 8、每个Window都需要指定一个Type(应用窗口、子窗口、系统窗口)。Activity对应的窗口是应用窗口;PopupWindow,ContextMenu,OptionMenu是常用的子窗口;像Toast和系统警告提示框(如ANR)就是系窗口,还有很多应用的悬浮框也属于系统窗口类型。

    1.3、WindowManager

    WindowManager:继承 ViewManager,用来在应用与window之间的管理接口,管理窗口顺序,消息等,用于沟通WMS和Window的桥梁。它的实现类为 WindowManagerImpl。(相当于一个代理)

    1.4、WindowManagerService

    简称WmsWindowManagerService 管理窗口的创建、更新和删除,显示顺序等,另外窗口的大小和层级也是由WMS进行管理,是WindowManager这个管理接品的真正的实现类。WindowManagerService 运行在System_server进程,作为服务端客户端(应用程序)通过IPC调用和它进行交互。

    1.5、Token

    这里提到的Token主是指窗口令牌(Window Token),是一种特殊的Binder令牌,Wms用它唯一标识系统中的一个窗口。

    1.6、Window的type

    • 1、应用窗口:层级范围是1~99
    • 2、子窗口:层级范围是1000~1999
    • 3、系统窗口:层级范围是2000~2999

    各级别type值在WindowManager中的定义分别为

    • 应用窗口(1~99)
    //第一个应用窗口
    public static final int FIRST_APPLICATION_WINDOW = 1;
    //所有程序窗口的base窗口,其他应用程序窗口都显示在它上面
    public static final int TYPE_BASE_APPLICATION  = 1;
    //所有Activity的窗口,只能配合Activity在当前APP使用
    public static final int TYPE_APPLICATION  = 2;
    //目标应用窗口未启动之前的那个窗口
    public static final int TYPE_APPLICATION_STARTING = 3;
    //最后一个应用窗口
    public static final int LAST_APPLICATION_WINDOW = 99;
    
    • 子窗口(1000~1999)
    //第一个子窗口
    public static final int FIRST_SUB_WINDOW  = 1000;
    // 面板窗口,显示于宿主窗口的上层,只能配合Activity在当前APP使用
    public static final int TYPE_APPLICATION_PANEL  = FIRST_SUB_WINDOW;
    // 媒体窗口(例如视频),显示于宿主窗口下层
    public static final int TYPE_APPLICATION_MEDIA  = FIRST_SUB_WINDOW+1;
    // 应用程序窗口的子面板,只能配合Activity在当前APP使用(PopupWindow默认就是这个Type)
    public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2;
    //对话框窗口,只能配合Activity在当前APP使用
    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 LAST_SUB_WINDOW = 1999;
    
    • 系统窗口(2000~2999)
    //系统窗口,非应用程序创建
    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;
    //电话窗口,它用于电话交互(特别是呼入),置于所有应用程序之上,状态栏之下,
    public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW + 2;
    //系统警告提示窗口,出现在应用程序窗口之上,属于悬浮窗, 但是会被禁止
    public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW + 3;
    //信息窗口,用于显示Toast, 不属于悬浮窗, 但有悬浮窗的功能,缺点是在Android2.3上无法接收点击事件
    public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW + 5;
    public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW + 4;
    //锁屏窗口
    public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW + 4;
    //系统顶层窗口,显示在其他一切内容之上,此窗口不能获得输入焦点,否则影响锁屏
    public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW + 6;
    //电话优先,当锁屏时显示,此窗口不能获得输入焦点,否则影响锁屏
    public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW + 7;
    //系统对话框窗口
    public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW + 8;
    //锁屏时显示的对话框
    public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW + 9;
    //系统内部错误提示,显示在任何窗口之上
    public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW + 10;
    //内部输入法窗口,显示于普通UI之上,应用程序可重新布局以免被此窗口覆盖
    public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW + 11;
    //内部输入法对话框,显示于当前输入法窗口之上
    public static final int TYPE_INPUT_METHOD_DIALOG = FIRST_SYSTEM_WINDOW + 12;
    //墙纸窗口
    public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW + 13;
    //状态栏的滑动面板
    public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW + 14;
    //安全系统覆盖窗口,这些窗户必须不带输入焦点,否则会干扰键盘
    public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW + 15;
    //最后一个系统窗口
    public static final int LAST_SYSTEM_WINDOW = 2999;  
    

    窗口flags显示属性在WindowManager中也有定义:

    //窗口特征标记
    public int flags;
    //当该window对用户可见的时候,允许锁屏
    public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001;
    //窗口后面的所有内容都变暗
    public static final int FLAG_DIM_BEHIND = 0x00000002;
    //Flag:窗口后面的所有内容都变模糊
    public static final int FLAG_BLUR_BEHIND = 0x00000004;
    //窗口不能获得焦点
    public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
    //窗口不接受触摸屏事件
    public static final int FLAG_NOT_TOUCHABLE = 0x00000010;
    //即使在该window在可获得焦点情况下,允许该窗口之外的点击事件传递到当前窗口后面的的窗口去
    public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020;
    //当手机处于睡眠状态时,如果屏幕被按下,那么该window将第一个收到触摸事件
    public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;
    //当该window对用户可见时,屏幕出于常亮状态
    public static final int FLAG_KEEP_SCREEN_ON = 0x00000080;
    //:让window占满整个手机屏幕,不留任何边界
    public static final int FLAG_LAYOUT_IN_SCREEN = 0x00000100;
    //允许窗口超出整个手机屏幕
    public static final int FLAG_LAYOUT_NO_LIMITS = 0x00000200;
    //window全屏显示
    public static final int FLAG_FULLSCREEN = 0x00000400;
    //恢复window非全屏显示
    public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800;
    //开启窗口抖动
    public static final int FLAG_DITHER = 0x00001000;
    //安全内容窗口,该窗口显示时不允许截屏
    public static final int FLAG_SECURE = 0x00002000;
    //锁屏时显示该窗口
    public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
    //系统的墙纸显示在该窗口之后
    public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
    //当window被显示的时候,系统将把它当做一个用户活动事件,以点亮手机屏幕
    public static final int FLAG_TURN_SCREEN_ON = 0x00200000;
    //该窗口显示,消失键盘
    public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;
    //当该window在可以接受触摸屏情况下,让因在该window之外,而发送到后面的window的触摸屏可以支持split touch
    public static final int FLAG_SPLIT_TOUCH = 0x00800000;
    //对该window进行硬件加速,该flag必须在Activity或Dialog的Content View之前进行设置
    public static final int FLAG_HARDWARE_ACCELERATED = 0x01000000;
    //让window占满整个手机屏幕,不留任何边界
    public static final int FLAG_LAYOUT_IN_OVERSCAN = 0x02000000;
    //透明状态栏
    public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;
    //透明导航栏
    public static final int FLAG_TRANSLUCENT_NAVIGATION = 0x08000000;
    

    二、Window分类

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

    • Application Window:Activity就是一个典型的应用程序窗口。
    • Sub Window:子窗口,顾名思义,它不能独立存在,需要附着在其他窗口才可以,PopupWindow就属于子窗口。
    • System Window:输入法窗口、系统音量条窗口、系统错误窗口都属于系统窗口。

    三、WindowManager

    public interface ViewManager{
        public void addView(View view, ViewGroup.LayoutParams params);
        public void updateViewLayout(View view, ViewGroup.LayoutParams params);
        public void removeView(View view);
    }
    

    WindowManager 其实是一个接口,它继承自 ViewManager , ViewManager 中定义了 3 个抽象方法,分别是用来添加、更新、删除 View 的,WindowManager 继承了父类接口的方法,说明也具备了父类的能力。WindowManagerImpl是WindowManager的实现类,但是具体的功能都会委托给WindowManagerGlobal来实现。

    此从运用桥接模式,继承 ViewManager 接口的类有WindowManagerViewGroup

    3.1、窗口的标志

    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)
    
      1. 给 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);
    

    3.2、软件盘相关模式

    窗口与窗口的叠加是十分常见的场景,但是如果其中的窗口是软件盘的窗口,可能就会出现一些问题,比如典型的用户登录页面,默认的情况弹出软件盘窗口可能遮挡输入框下方的按钮,这样用户体验非常糟糕。为了使得软键盘窗口能够按照期望来显示, 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

    添加Vindow流程图
    • Activity#attach()方法之内PhoneWindow被创建,并同时创建一WindowManagerImpl负责维护PhoneWindow内的内容。
    • 在Activity#onCreate()中调用setContentView()方法,这个方法内部创建一个DecorView实例作为PhoneWindow的实体内容。
    • WindowManagerImpl决定管理DecorView,并创建一个ViewRootImpl实例,将ViewRootImpl与View树进行关联,这样ViewRootImpl就可以指挥View树的具体工作。

    添加过程如下文章:
    View 绘制流程(一)
    View 绘制流程(二)

    下面我们看下mWindowSession.addToDisplayAsUser(...)流程。
    Session 实现了IWindowSession.Stub,我们看Session@addToDisplayAsUser 方法

    public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs,
        int viewVisibility, int displayId, int userId, Rect outFrame,
        Rect outContentInsets, Rect outStableInsets,
        DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
        InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
                outInsetsState, outActiveControls, userId);
    }
    

    然后回到WindowManagerService@addWindow(...) 中。

    4.1、Session

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

    WindowManagerGlobal@addView()中会初始化 Session

    public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow, int userId) {
        root = new ViewRootImpl(view.getContext(), display);
    }
    

    ViewRootImpl.java

    public ViewRootImpl(Context context, Display display) {
        this(context, display, WindowManagerGlobal.getWindowSession(),
                false /* useSfChoreographer */);
    }
    

    我们看WindowManagerGlobal.getWindowSession()方法

    private static IWindowSession sWindowSession;
    
    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
                    IWindowManager windowManager = getWindowManagerService();
                    //获取 Session
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            });
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }
    

    会进入WindowManagerService@openSession

    public IWindowSession openSession(IWindowSessionCallback callback) {
          return new Session(this, callback);
    }
    
    与WMS通信

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

    4.2、WindowManagerImpl、WindowManagerGlobal、ViewRootImpl

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

    WindowManagerGlobal
    • WindowManagerImpl:确定 View 属于哪个屏幕,哪个父窗口
    • WindowManagerGlobal:管理整个进程,所有的窗口信息
    • ViewRootImpl:WindowManagerGlobal 实际操作者,操作自己的窗口和WMS进行交互
    // 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>();
    

    4.3、activity与 PhoneWindow与DecorView关系

    4.4、ViewRootImpl

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

    • 作用
    • 1、View树的树根并管理View树
    • 2、触发View的测量、布局和绘制
    • 3、输入响应的中转站
    • 4、负责与WMS进行进程间通信

    4.5、DecorView

    DecorView
    • 在一个Activity或者Dialog中,是包含一个Window窗口的对象,Window并不是真正的实体内容,通过View来表达真正的页面内容,这个View就是DecorView。
    • DecorView是FrameLayout的子类,它可以被认为是Android视图树的根节点视图。
    • 在Activity的生命周期onCreate方法里面,是会设置好布局内容通过setContentView(布局id)的方式,这里是通过xml解析器转化为一个View,这个View会被添加到ContentView中去,成为唯一的子View。

    五、事件分发

    DecorView虽然是ViewGroup,但它并不会直接分发给它的child,也不会传递给它的父View,而是先分发给Activity,为什么要这么做呢?

    六、刷新流程

    6.1、更新Window

    更新Window

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

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

    WindowManagerImpl@updateViewLayout开始流程:

    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
            //
        mGlobal.updateViewLayout(view, params);
    }
    

    进入WindowManagerGlobal@updateViewLayout方法

    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;
        //设置 lp
        view.setLayoutParams(wparams);
    
        synchronized (mLock) {
            int index = findViewLocked(view, true);  //通过View找到DecorView 的索引值
            //通过索引值找到 ViewRootImpl 数组中的对象
            ViewRootImpl root = mRoots.get(index); 
            mParams.remove(index); //移除旧的 params
            mParams.add(index, wparams); //重新设置 params
            root.setLayoutParams(wparams, false);
        }
    }
    
    private int findViewLocked(View view, boolean required) {
        final int index = mViews.indexOf(view); //获取 View 在 DecorView 的位置
        if (required && index < 0) {
            throw new IllegalArgumentException("View=" + view + " not attached to window manager");
        }
        return index;
    }
    

    ViewRootImpl@setLayoutParams

    void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) 
        ...
         scheduleTraversals();
        ...
    }
    

    执行scheduleTraversals()流程见:
    View 绘制流程(一)

    6.2、UI刷新

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

    简而言之:UI必须至少等待16ms的间隔才会绘制下一帧,所以连续两次setTextView只会触发一次重绘。

    UI刷新.png

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

    6.3、刷新流程

    申请Vsync流程 VSync回来流程

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

    执行流程
    • 代码讲解
      同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的测量、布局、绘制的流程。

    6.4、View 绘制流程

    绘制流程

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

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

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

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

    绘制过程如下文章:
    View 绘制流程(一)
    View 绘制流程(二)

    6.5、UI局部重绘

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

    总结

    • 1、android一般60FPS,是VSYNC及决定的,每16ms最多一帧
    • 2、VSYNC要客户端主动申请,才会有有VSYNC到来才会刷新
    • 3、UI没更改,不会请求VSYNC也就不会刷新
    • 3、UI局部重绘其实只会去重绘带刷新的View

    面试题

    • 我们都知道Android的刷新频率是60帧/秒,这是不是意味着每隔16ms就会调用一次onDraw方法?
      这里60帧/秒是屏幕刷新频率,但是是否会调用onDraw()方法要看应用是否调用requestLayout()进行注册监听。

    • 如果界面不需要重绘,那么还16ms到后还会刷新屏幕吗?
      如果不需要重绘,那么应用就不会受到Vsync信号,但是还是会进行刷新,只不过绘制的数据不变而已;

    • 我们调用invalidate()之后会马上进行屏幕刷新吗?
      不会,到等到下一个Vsync信号到来

    • 我们说丢帧是因为主线程做了耗时操作,为什么主线程做了耗时操作就会引起丢帧?
      原因是,如果在主线程做了耗时操作,就会影响下一帧的绘制,导致界面无法在这个Vsync时间进行刷新,导致丢帧了。

    • 如果在屏幕快要刷新的时候才去OnDraw()绘制,会丢帧吗?
      这个没有太大关系,因为Vsync信号是周期的,我们什么时候发起onDraw()不会影响界面刷新;

    七、SurfaceFlinger

    SurfaceFlinger 是整个Android系统渲染的核心进程。所有应用的渲染逻辑最终都会来到SF中进行处理,最终会把处理后的图像数据交给CPU或者GPU进行绘制。

    在每一个应用中都以Surface作为一个图元传递单元,向 SurfaceFlinger 这个服务端传递图元数据。

    7.1、SurfaceFlinger 整体流程

    SurfaceFlinger 是以生产者以及消费者为核心设计思想,把每一个应用进程作为生产者生产图元保存到SF的图元队列中,SurfaceFlinger 则作为消费者依照一定的规则把生产者存放到SF中的队列一一处理。

    相关文章

      网友评论

          本文标题:WMS(一)

          本文链接:https://www.haomeiwen.com/subject/hgwlqrtx.html