热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

Android中Window的管理深入讲解

这篇文章主要给大家介绍了关于Android中Window管理的相关资料,文中通过示例代码介绍的非常详细,对各位Android开发者们具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧

一、理解 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 需要权限

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 的,需要声明权限**

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

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 的监听事件不能获取,显示都是可以的。

总结

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


推荐阅读
  • 2023 ARM嵌入式系统全国技术巡讲旨在分享ARM公司在半导体知识产权(IP)领域的最新进展。作为全球领先的IP提供商,ARM在嵌入式处理器市场占据主导地位,其产品广泛应用于90%以上的嵌入式设备中。此次巡讲将邀请来自ARM、飞思卡尔以及华清远见教育集团的行业专家,共同探讨当前嵌入式系统的前沿技术和应用。 ... [详细]
  • Android 九宫格布局详解及实现:人人网应用示例
    本文深入探讨了人人网Android应用中独特的九宫格布局设计,解析其背后的GridView实现原理,并提供详细的代码示例。这种布局方式不仅美观大方,而且在现代Android应用中较为少见,值得开发者借鉴。 ... [详细]
  • 国内BI工具迎战国际巨头Tableau,稳步崛起
    尽管商业智能(BI)工具在中国的普及程度尚不及国际市场,但近年来,随着本土企业的持续创新和市场推广,国内主流BI工具正逐渐崭露头角。面对国际品牌如Tableau的强大竞争,国内BI工具通过不断优化产品和技术,赢得了越来越多用户的认可。 ... [详细]
  • 本文详细分析了JSP(JavaServer Pages)技术的主要优点和缺点,帮助开发者更好地理解其适用场景及潜在挑战。JSP作为一种服务器端技术,广泛应用于Web开发中。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 理解存储器的层次结构有助于程序员优化程序性能,通过合理安排数据在不同层级的存储位置,提升CPU的数据访问速度。本文详细探讨了静态随机访问存储器(SRAM)和动态随机访问存储器(DRAM)的工作原理及其应用场景,并介绍了存储器模块中的数据存取过程及局部性原理。 ... [详细]
  • 几何画板展示电场线与等势面的交互关系
    几何画板是一款功能强大的物理教学软件,具备丰富的绘图和度量工具。它不仅能够模拟物理实验过程,还能通过定量分析揭示物理现象背后的规律,尤其适用于难以在实际实验中展示的内容。本文将介绍如何使用几何画板演示电场线与等势面之间的关系。 ... [详细]
  • 本文介绍如何通过Windows批处理脚本定期检查并重启Java应用程序,确保其持续稳定运行。脚本每30分钟检查一次,并在需要时重启Java程序。同时,它会将任务结果发送到Redis。 ... [详细]
  • MySQL中枚举类型的所有可能值获取方法
    本文介绍了一种在MySQL数据库中查询枚举(ENUM)类型字段所有可能取值的方法,帮助开发者更好地理解和利用这一数据类型。 ... [详细]
  • Android LED 数字字体的应用与实现
    本文介绍了一种适用于 Android 应用的 LED 数字字体(digital font),并详细描述了其在 UI 设计中的应用场景及其实现方法。这种字体常用于视频、广告倒计时等场景,能够增强视觉效果。 ... [详细]
  • 本文详细探讨了在Android 8.0设备上使用ChinaCock的TCCBarcodeScanner进行扫码时出现的应用闪退问题,并提供了解决方案。通过调整配置文件,可以有效避免这一问题。 ... [详细]
  • 本章将深入探讨移动 UI 设计的核心原则,帮助开发者构建简洁、高效且用户友好的界面。通过学习设计规则和用户体验优化技巧,您将能够创建出既美观又实用的移动应用。 ... [详细]
  • 本文介绍如何在应用程序中使用文本输入框创建密码输入框,并通过设置掩码来隐藏用户输入的内容。我们将详细解释代码实现,并提供专业的补充说明。 ... [详细]
  • 本文介绍如何通过SQL查询从JDE(JD Edwards)系统中提取所有字典数据,涵盖关键表的关联和字段选择。具体包括F0004和F0005系列表的数据提取方法。 ... [详细]
  • RecyclerView初步学习(一)
    RecyclerView初步学习(一)ReCyclerView提供了一种插件式的编程模式,除了提供ViewHolder缓存模式,还可以自定义动画,分割符,布局样式,相比于传统的ListVi ... [详细]
author-avatar
mobiledu2502899727
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有