开发艺术之旅 | 浅析Window和WindowManager

初识Window

Window

  • 表示一个“窗口”的概念,用于展示给用户,View是其具体实现,Window是View的实际管理者
  • 表示Window抽象类,具体实现是PhoneWindow,即Activity视图层级下我们看到的那个
    视图层级
    WindowManager

外界访问Window的入口,继承于ViewManager,常用三个方法:

  • public void addView(View view,ViewGroup.LayoutParams params);
  • public void updateViewLayout(View view,ViewGroup.LayoutParams params);
  • public void removeView(View view)

Window以及其内部机制

window有三类:

  • 应用Window(Activity)
  • 子Window(Dialog、Toast)
  • 系统Window

在addView(View view,ViewGroup.LayoutParams params)方法,需要传入一个LayoutParams,除了常见的gravity、坐标还有两个参数很重要:

flags:常用的有三种:

  1. FLAG_NOT_FOCUSABLE 标明Window不需要获取焦点,事件会向下传递
  2. FLAG_NOT_TOUCH_MODAL 会将当前Window区域以外的单击事件传递给底层的Window
  3. FLAG_SHOW_WHEN_LOCKED Window显示在锁屏的界面上

type:设定Window的类型(Window的层级)

  • 应用Window 1~99
  • 子Window 1000~1999
  • 系统Window 2000~2999

例如如果需要设置在所有Window的顶层可以设置 LayoutParams.TYPE_SYSTEM_ERROR 并且声明 SYSTEM_ALERT_WINDOW 权限

内部机制

每一个Window都对应一个View 和 ViewRootImpl,View是 Window存在的实体;
几个关键类关系如图:
在这里插入图片描述
WindowManagerGlobal
具体实现在这个类,当中维护了几个列表:

// 所有的View
private final ArrayList<View> mViews = new ArrayList<View>(); 
// 所有的Window对应的ViewRootImpl
private final ArrayList<ViewRootImpl> mRoots= new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams= new ArrayList<WindowManager.LayoutParams>();
// 正在被销毁的View
private final ArraySet<View> mDyingViews= new ArrayList<View>();

添加Window

Window的增删更新都是通过WindowManagerGlobal具体实现,在WindowManagerGlobal中添加View分为三步:

  1. 检查参数是否合法;调整子Window的布局参数
  2. 创建ViewRootImpl并将View添加到列表中
  3. 通过ViewRootImpl更新界面并完成Window的添加

流程如下图所示。可以看到最后是调用了WindowManagerService进行添加,也是进行一次IPC通信。
在这里插入图片描述

删除过程

也是通过WindowManagerGlobal进行具体的操作:
在这里插入图片描述
最终调用dispatchDetachedFromWindow方法,里面主要做了四件事:

  • 垃圾回收、清除数据、消息、移除回调等
  • 通过Session 的 remove方法删除Window:mWindowSession.remove(mWindow),同样也是一IPC过程,最后也会调用到WindowManagerService的removeWindow方法
  • 调用View的dispatchDetachedFromWindow方法,内部会调用View的onDetachedFromWindow 以及 onDetachedFromWindowInternal
  • 调用WindowManagerGlobal的doRemoveView方法刷新数据,包括删除mRoots、mParams、mDyingViews

更新过程

一样也是通过WindowManagerGlobal进行更新,调用他的updateViewLayout,主要逻辑

  1. 更新View的LayoutParams
  2. 更新ViewRootImpl中的LayoutParams(setLayoutParams)(在ViewRootImpl中会通过scheduleTraversals对View重新布局)
  3. 在ViewRootImpl通过WindowSession更新Window视图(IPC过程)

小结

  • 可以发现正如开头梳理的关系那样,具体的实现流程都在WindowManagerGlobal
  • 在WindowManagerGlobal中又通过ViewRootImpl操作View,因此ViewRootImpl是Window和View联系的桥梁

Window的创建

Activity的Window创建过程

  1. 需要了解Activity的启动流程,在ActivityThread中的performLaunchActivity创建Activity并关联上下文对象
  2. 通过PolicyManager(真正实现是Policy类)创建Window(实际创建的是PhoneWindow)
  3. 将Activity的视图和Window关联起来(从Activity.setContentView 到 PhoneWindow.setContentView)
  4. 经过 创建DecorView、添加contentView、通知Activity添加完成 三步完成DecorView的创建
  5. 通过Activity.makeVisiable 调用WindowManager将decorView添加并显示

具体流程图如下:
在这里插入图片描述

Dialog的Window创建过程

  1. 创建Window
  2. 初始化DecorView并将Dialog的视图添加到DecorView
  3. 将DecorView添加到Window并显示
  1. 创建Window和Activity的类似,也是通过PolicyManager的makeNewWindow创建一个PhoneWindow对象。
  2. 和Activity的类似,也是通过Window去加载指定文件
  3. 在Dialog的show方法中,会通过WindowManager将DecorView添加到Window中

注意点:

  • 当关闭Dialog时会调用WindowManager来移除DecorView:mWindowManager.removeViewImmediate(mDecor)
  • 必须采用Activity的context,否则会报错,除非设定为系统的Window:dialog.getWindow().setType(LayoutParams.TYPE_SYSTEM_ERROR) 并且声明权限

Toast的Window创建

基础信息

  1. 属于系统Window
  2. 有定时消失功能所以内部包含一个Handler
  3. 两个IPC过程:Toast访问NMS(NotificationManagerService)和 NMS回调Toast里的TN接口

TN类

  • TN是个Binder类
  • 跨进程IPC,所以TN类的show、hide方法运行在Binder线程池中(因此通过Handler切换回调用线程)
  • Handler会绑定调用的线程,因此在非looper线程Toast不显示;TN回调show、hide方法会通过Handler发送一个消息切换线程,实际执行的是handleShowhandleHide方法

两个操作

  • show
  • cancel

均为IPC过程(与NMS通信,NMS是系统服务)

显示View

  • 默认样式
  • 通过setView设定

show显示过程

Toast.show方法

    public void show() {
        INotificationManager service = getService();
        String pkg = mContext.getOpPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;

        try {
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }

实际过程:

  1. 将TN对象、包名、显示时间传入 service.enqueueToast,enqueue会将这些信息封装成一个ToastRecord对象并添加到一个名为mToastQueue的队列中;
  2. NMS会通过showNextToastRecord显示当前的Toast,然后由ToastRecord.callback.show(callback就是TN的远程Binder)显示,最终会回调到TN中的show方法;
  3. 显示后会发送一个延时消息通知隐藏Toast,也是通过ToastRecord.callback完成(隐藏Toast并且从队列中移除,然后显示下一个Toast)
  4. 在TN类的实际实现方法handleShow会将Toast的视图添加到Window;handlerHide会将Toast视图从Window移除

过程示意图

在这里插入图片描述

小结

  • Toast.show方法会将信息交给NMS,NMS会封装成ToastRecord并管理一个ToastRecord队列,并进行对这个消息队列进行管理
  • Toast实际的显示会通过远程调用执行Toast里面的TN类中的方法

推荐参考:厘米姑娘的Window笔记 最后的Toast流程图总结得很好

总结

  • 任何一个View都是附属在Window上的,View的事件分发机制也是从Window分发到DecorView的,Window是View的实际管理者
  • Window是一个抽象概念,每个Window都有对应的View和ViewRootImpl
  • 我们对Window的访问只能通过WindowManager,而WindowManager最终会通过WindowSession调用WindowManagerService对Window进行处理

关于Window和WindowManager的内部机制

  • Window有三类:应用Window、子Window、系统Window,并且有对应的层级,可以设置
  • WindowManager具体实现是WindowManagerImpl,但是他把具体操作交给WindowManagerGlobal,WindowGlobal又会通过WindowSession调用WindowManagerService完成对Window(具体体现是View)的管理
  • Window的增、删、改都是:通过WindowManagerImpl获取到WindowManagerGlobal,然后WindowManagerGlobal又会 找到/创建 ViewRootImpl,再通过ViewRootImpl调用WindowManagerService对Window进行操作(IPC过程)

关于Window的创建

  • PolicyManager通过makeNewWindow创建一个PhoneWindow对象(Window的实现类)
  • Activity与Window关联:
  • 在Activity的setContentView会调用Window的setContentView;
  • 在这个方法会创建DecorView并添加ContentView;
  • 完成后会调用Activity的makeVisiable显示窗口
  • Toast:
  • 属于系统Window;
  • Toast内部有个TN类,是一个Binder类;
  • 当调用Toast的show方法,会将信息发送给NMS“入队”,NMS内部管理一个ToastRecord队列,按顺序显示,会回调到TN内的方法;
  • TN回调到的接口方法是在Binder线程池中,因此通过Handler切换到调用的线程,再调用WindowManager 添加/删除View

文章链接

深入了解WMS,推荐刘望舒的解析WindowManager系列

发布了27 篇原创文章 · 获赞 6 · 访问量 1638

猜你喜欢

转载自blog.csdn.net/weixin_41802023/article/details/103882026