专栏首页恩蓝脚本Android中Window的管理深入讲解

Android中Window的管理深入讲解

一、理解 Android 的 Window

Window 表示一个窗口的概念,是一个抽象的概念,每一个 Window 都对应一个 View 和一个 ViewRootImpl,Window 和 View 通过 ViewRootImpl 来建立联系,因此 Window 并不是实际存在的,它是以 View 的形式存在。

Android 中的每个窗口 View 都有一个对应的 Window,例如 Activity、Dialog,在他们初始化的时候就会为其创建对应的PhoneWindow 并赋值到其内部的一个引用

window 的层级

WindowLayoutParams.setType 设置

每个 window 都有其对应的层级,应用 window 在 1-99,子 window 在 1000-1999,系统 window 在 2000-2999 ,层级高的会覆盖层级低的

子 window 必须依赖于父 window 存在,例如 Dialog 必须在 Activity 中弹出,Dialog 中的 window 为子 window ,Activity 中的 window 为父 window

显示系统级别的 window 需要权限 <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /

WindowLayoutparams 的 flags

FLAG_NOT_FOUCSABLE window 不需要获取焦点,也不需要接收各种输入事件,回同时启用 FLAG_NOT_TOUCH_MODAL FLAG_NOT_TOUCH_MODAL 系统会将当前 Window 区域外的单击事件传递给底层的 Window,在当前 Window 区域内的事件则自己处理

FLAG_SHOW_WHEN_LOCKED 开启此模式让 window 显示在锁屏界面上

二、理解 Android 中的 WindowManager

Android 中对 Window 的管理都是通过 WindowManager 来完成的,创建 PhoneWindow 之后还会为该 Window 对象设置 WindowManager ,WindowManager 是一个接口继承 ViewManager 接口,从这里也能看出对 Window 的操作其实就是对 View 的操作,WindowManager 的实现类是 WindowMangerImpl ,WindowMangerImpl 通过 new 创建。

三、Window 与 WindowManagerImpl 的关联

通过 ContextImpl 的 getSystemService 可以得到 WindowManagerImpl 实例,同一 ContextImpl 得到的是同一个WindowManagerImpl对象,得到 WindowMangerImpl 之后,调用 Window 的 setWindowManager 方法建立 Window 与 WindowManagerImpl 之间的联系。

setWindowManager 中主要完成在 WindowManagerImpl 实例的基础上重新创建一个与当前 Window 绑定的 WindowManagerImpl,并为 Window 中的属性 mWindowManager 赋值

也就是说在 Java 层上 Window 与 WindowManager 建立了第一步联系,并将 Activity、Dialog 等中的 WindowManager 赋值为新的 WindowManagerImpl 对象。

注意:这里是使用单例的 WindowManagerImpl ,结合不同的 Window ,最后构建了与 Window 有关联的非单例的 WindowManagerImpl 对象

四、对 Window 的操作

1. 添加操作 WindowManagerImpl.addView,注意,是添加一个新的 Window ,不是对一个 Window 中的 view 做操作

Android 中每显示一个窗口,其实就是将 View 显示到屏幕的过程,如果我们自定义一个要显示的布局,拿到 View 对象,这时候只要调用 WindowManagerImpl 对象的 addView 方法就行了,通过 ContextImpl 的 getSystemService 可以得到 WindowManagerImpl 实例

WindowManagerImpl 对象,在 WindowManagerImpl 中存在一个单例存在的 WindowManagerGlobal 对象,在 WindowManagerImpl 的各个方法中,将任务的执行过程传递到了 WindowManagerGlobal 中,在传递过程中除了将 View、LayoutParams 传递,还将 WindowManagerImpl 中关联的 window 对象也一起传递

WindowManagerGlobal 的 addView 方法

WindowMAnagerGlobal 中判断 view、LayoutParams 等参数合法性,创建 ViewRootImpl ,将 ViewRootImpl、View、LayoutParams 添加到 WindowMAnagerGlobal 中对应的 ArayyList 集合中,再调用 ViewRootImpl 的 setView 方法

ViewRootImpl

继承自 Handler 类,是作为 native 层和 Java 层 View 系统通信的桥梁

ViewRootImpl 创建时保存了创建其的线程的引用,开发过程中更新 View 时会判断当前线程是否是创建 ViewRootImpl 的线程,如果不是会抛出异常。

一般都是在主线程中创建 ViewRootImpl ,所以在子线程更新 UI 会抛出异常,是因为 ViewRootImpl 是 UI 线程中创建的,并不是因为只有 UI 线程才可以更新 UI

在 Activity 的 onResume 之前如果在子线程中修改 UI 是不会抛出异常的,因为在 onResume 之后才创建 ViewRootImpl,这时更新 UI 需要经过 ViewRootImpl 来更新,在 onResume 之前 Activity 的屏幕并没有显示,修改 UI 操作只是会修改 layout 中的 UI,并不会调用 ViewRootImpl 的方法显示到屏幕上。

所以得出结论,只有 UI 显示到屏幕上之后,在更新 UI 时就会判断线程是否为创建 UI 的线程,如果不匹配则抛出异常,在 UI 没有显示到屏幕上时更新 UI 是不会进行线程判断的

ViewRootImpl 的 setView 方法:

  1. setView 方法中会首先调用 requestLayout() 方法,在这里进行线程判断,如果线程匹配则调用 scheduleTraversals() 完成 View 的测量、布局、绘制过程
  2. setView 中接下来远程调用 IWindowSession 对象中的 addToDisplay 方法,将 window 等信息传递到 NMS 中调用 NMS 的 addWindow 方法完成最后window 在屏幕上的展示。

IWindowSession WindowManagerService

这里是将 View 显示到屏幕上的关键,是将 View 从应用进程传递到系统进程然后完成显示的地方。 ViewRootImpl 中的 IWindowSession 对象是通过 WindowManagerGlobal 的静态方法 getWindowSession() 得到的,该方法中首先通过 ServiceManager 通过 Binder 进行 IPC 得到系统服务 WindowsManagerService 在应用进程的远程代理,然后通过 AIDL 通信的方式调用 WMS 的 openSession() 方法得到 IWindowSession 接口的实现类 Session 类远程代理对象,Session 类也是一个 Binder 所以可以跨进程传递

在 Session 的远程代理的 addToDisplay 方法中通过 AIDL 调用 Session 的 addToDisplay 方法将 window 信息传递到系统进程,然后调用 AMS 的 addWindow 方法,AMS 最后将 Window 中的 View 显示到屏幕上

2. 删除操作,是删除一个屏幕上已有的 Window

WindowManagerImpl.removeView 操作中,先通过 findViewLocked 查找要删除的 View,再通过 View 找到对应的 Window 的 ViewRootImpl ,将 View、LayoutParams、ViewRootImpl 从相对应的 ArrayList 中删除,再通过 IPC 调用 Session 的 remove 其中调用 WMS 的 removeWindow 方法,在屏幕上移除该 Window 对应的 View

3. 更新操作

WindowManagerImpl.updateViewLayout ,为 view 设置新的 LayoutParams ,通过 findViewLocked 找到对应 ViewRootImpl,删除 LayoutParams 集合中旧的 LayoutParams,在集合原位置加入 新的 LayoutParams,调用 ViewRootImpl 的 setLayoutParams 完成 View 的重新测量,布局,绘制,最后通过 IPC 调用 Session 再调用 WMS 完成 window 的更新。

4. 添加 Window 代码

自定义的 Window 在创建过程中并没有主动的创建 Window,而是在显示的时候由系统维护,这里也体现了 Window 是一个抽象的概念,最终需要处理的还是 View

private void addWindow() {
  TextView view = new TextView(this);
  view.setText("Text");
  view.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
      Log.i("renxl", "onClick");
    }
  });
  view.setBackgroundColor(Color.RED); // 要显示的 View 可以是新创建的,也可以是 LayoutInflater 从 xml 布局中获取的

  WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams(
      WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT, 0, 0,
      PixelFormat.TRANSPARENT); // 必须是 WindowManager.LayoutParams
  mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; // 三种 flag 从中选一
  mLayoutParams.type = WindowManager.LayoutParams.TYPE_TOAST; // type 表示优先级
  mLayoutParams.gravity = Gravity.START | Gravity.TOP; 
  mLayoutParams.x = 100; // 在屏幕上 X 轴位置
  mLayoutParams.y = 300; // 在屏幕上 Y 轴位置

  WindowManager manager = (WindowManager) getSystemService(WINDOW_SERVICE);
  manager.addView(view, mLayoutParams); // 将 View 添加到界面上
}

**注意,如果是系统级别的 Window 也就是优先级超过 1999 的,需要声明权限**
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" / 

五、用户触摸屏幕事件处理流程

WMS 将事件 IPC 传递到 Window,Window 中调用其内部的 CallBack 对象也就是 Activity 或者 Dialog 对象或者的方法。最终将事件传递到 View,再通过 ViewRootImpl 将响应以后的 View 和对应 window IPC 提交到 WMS 完成响应后的展示。

六、常见 Window 的创建

1. Activity 的 Window 创建过程

Activity 启动流程中,Activity 的 attach 方法中为 window 赋值为一个新的 PhoneWindow ,setContentView 将 layout 传递到 PhoneWindow 中,PhoneWindow 中通过 LayoutInflater 的 inflate 方法加载布局 View,并添加到 PhoneWindow 内部的 DecorView 中,在 onResume 的时候,调用 WindowManager 的 addView 方法将 Window 中的 DecorView 通过 WMS 显示到屏幕上

Activity 在创建 Window 的时候,实现了 Window 的 Callback 接口中的方法,在 Window 收到触摸时,则会回调 Callback 中的方法将事件传递到 Activity 中,Activity 中会调对应 PhoneWindow 中的分发方法,PhoneWindow 中会调用 DecordView 中的方法, 最终将事件传递到 View 中。

猜测事件由 WMS 传递到 Window 再到 Activity 再到 Window 这样多一层 Activity 的原因是,开发者可以在 Activity 中处理事件,不一定非要传递到 View

2. Dialog 的 Window 创建过程

同 Activity,实例化 Dialog 对象时创建 PhoneWindow ,show 方法调用时通过 AIDL 调用 WMS 的 addView 方法将 View 添加到屏幕

3. Toast 的 Window 创建过程

Toast 在创建过程中并没有主动的创建 Window,而是在显示的时候由系统维护 Toast 的 window,这里也体现了 Window 是一个抽象的概念,最终需要处理的还是 View

Toast 的工作工程需要 TN NMS WMS 三个部分协同完成,IN 也是一个 Binder,NMS 中调用 TN 是远程访问,TN 调用 WMS 也是远程调用

NMS 即 NotificationManagerService

Toast 的工作过程分为两步

  • 第一步是当前线程中要先生成一个 Binder 类型的 TN 的对象,远程调用 NMS 中的 enquneue 方法将 TN 传到 NMS 中,NMS 远程调用 TN 的 show 方法,TN 中 show 方法运行在当前应用的 Binder 线程池中,通过 Handler 的 post 系列方法将进程切换到主线程,主线程再通过 WindowManager 来调用 WMS 中的方法完成 show 过程
  • NMS 中调用了 TN 的 show 方法之后,会通过自己内部的 Handler 延时发送一个时间为 Toast 展示时间的消息,NMS 中的 Handler 收到消息之后,再调用 TN 的 hide 方法(远程调用过程),TN 中的 hide 方法又会通过 WindowManager 远程调用 WMS 中的 hide 方法,将 Toast 隐藏。完成整个过程。

七、总结

屏幕展示的每一个 window,都需要 window 和 View 两个相互结合,屏幕中可以有多个 Window。以下所说的 View 都是一个 Window 中包含的根 View

window 的创建以及对 View 的添加,删除、更新是由 WindowManager 来实现的,而 WindowManager 中对 window 的操作通过 每个 window 对应的 ViewRootImpl 中通过 IPC 远程请求 IWindowSession 中的方法再调用 WMS 的对应方法将对当前 window 操作的实现到屏幕上。

每一个 Window 都对应一个 ViewRootImpl ,window 通过对应的 ViewRootImpl 来完成对 view 的管理

在屏幕有用户交互的时候,WMS 又会将事件传递到相应界面的 Window,Window 会调用当前界面的对应的 CallBack 来处理事件

WindowManager 是接口,实现类是 WindowManagerImpl,WindowManagerImpl 中又通过 WindowMAnagerGlobal 来完成操作。典型的桥接模式

添加 Window 显示不出来问题

由于国内对于 ROM 的定制,多种机型会默认禁止应用对悬浮窗的创建,所以如果是没有显示,检查是否关闭了应用的权限。

  • 安卓 6.0 添加了对权限的开关设置,悬浮窗权限默认是关闭的
  • 一些国内定制的 Rom 6.0 之前就可以设置权限的开关,悬浮窗权限默认关闭

问题解决

mLayoutParams.type = WindowManager.LayoutParams.TYPE_TOAST;

将 type 设置为 TYPE_TOAST , 源码中对 TYPE_TOAST 是没有任何限制的。

在国内定制的 Rom 上,只有少数机型会在设置 TYPE_TOAST 的时候,View 的监听事件不能获取,显示都是可以的。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对ZaLou.Cn的支持。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • flutter窗口初始和绘制流程详析

    这里关注的是C++层面的绘制流程,平台怎样驱动和响应绘制与渲染的过程,并不是Dart部分的渲染。

    砸漏
  • Android使用Circular Reveal动画让页面跳转更炫酷

    Android 5.0中引入了很多炫酷的动画效果,Circular Reveal便是其中一种。使用起来很简单,但效果却是意想不到的炫酷,让你的app更有逼格。

    砸漏
  • Android使用ViewDragHelper实现图片下拽返回示例

    微信的图片下拽返回功能在日常使用中非常方便,同时在很多 App 中都见到了类似的设计,可以说一旦习惯这种操作再也回不去了。

    砸漏
  • [虾扯蛋] android界面框架-Window

    从纯sdk及framwork的角度看,android中界面框架相关的类型有:Window,WindowManager,View等。下面就以这几个类为出发点来概览...

    用户1172465
  • Android自定义View基础:ViewRoot、DecorView & Window的简介

    顶层View,即 Android 视图树的根节点;同时也是 FrameLayout 的子类

    Carson.Ho
  • 从源码出发浅析 Android TV 的焦点移动原理 (上篇)

    焦点(Focus)可以理解为选中态,在 Android TV上起很重要的作用。一个视图控件只有在获得焦点的状态下,才能响应按键的 Click 事件。

    QQ音乐技术团队
  • Android编程实现画板功能的方法总结【附源码下载】

    本文实例讲述了Android编程实现画板功能的方法。分享给大家供大家参考,具体如下:

    砸漏
  • findById引发的Java泛型思考

    在Android中最头痛的就是不停的findById需要不停的进行类型转换,那么怎么实现在findById的时候直接设置转换后的类型呢?

    大话swift
  • 国际设计大咖亲临古都洛阳 传统文化与国际潮流擦出火花

    ? 9月3日,2019“牡丹奖·全球文化创意设计大赛(洛阳)”已经拉开帷幕。 本次大赛,腾讯公司特邀欧洲著名国际家居设计师、丘吉尔纪念基金会优胜奖获得者Hug...

    腾讯文旅
  • 我想给你介绍一个假的苹果网站

    网站的 URL 地址显示的是苹果官网,网址旁边是安全字样和绿色小锁,表示网站信息基于 https 加密传输,完全没什么问题,然而它就是一个钓鱼网站(演示网站)。

    疯狂的技术宅

扫码关注云+社区

领取腾讯云代金券