Android中Window的管理深入讲解
作者:renxuelong 发布时间:2022-03-18 17:48:28
一、理解 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 方法:
setView 方法中会首先调用 requestLayout() 方法,在这里进行线程判断,如果线程匹配则调用 scheduleTraversals() 完成 View 的测量、布局、绘制过程
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 的监听事件不能获取,显示都是可以的。
来源:https://www.jianshu.com/p/a7e9797094eb


猜你喜欢
- yml文件参数的读取附上一个较为常见的application.yml文件示例server: port: 9999 u
- SpringBoot JPA分页查询指定列并返回指定实体用习惯Mybatis,没用过jpa 真是各种踩坑了脑壳疼,一个分页弄老半天,原来就一
- 基于项目需求,想要实现Post消息推送,故采用HttpClient组件进行实现,相关代码如下(注:程序采用的httpclient和httpc
- 一、ServletConfig讲解1.1、配置Servlet初始化参数在Servlet的配置文件web.xml中,可以使用一个或多个<
- 一、C语言关键字详解1. sizeof sizeof相信大
- 一、顺序结构程序的执行和代码的执行顺序有关,如果调整代码的书写顺序, 则执行顺序也发生变化二、分支结构基本语法形式1:if(布尔表达式){
- 1 前言前文已经讲述了Spring BeanFactory 与 FactoryBean 的区别详情,
- ListJava 的list又分为 ArrayList 和 LinkedListArrayListprivate class Itr imp
- 最近项目中需要用到IO流来读取图片以提供前台页面展示,由于以前一直是用url路径的方式进行图片展示,一听说要项目要用IO流读取图片感觉好复杂
- 背景:以前学的Java进行开发,多用到Mybatis,Hiberante等ORM框架,最近需要上手一个C#的项目,由于不是特别难,也不想再去
- 一、开发前准备1. Docker的安装可以参考https://docs.docker.com/install/2. 配置docker远程连接
- /// <summary> /// 删除掉空
- 添加依赖添加generatorConfig.xml文件在maven的plugins中运行mybatis-generator插件注意事项:(1
- 平时项目中只要涉及表,那么一定能接触到众多各式各样的ID编号,博主整理一些常用的ID格式,整合一个ID生成工具类,供大家参考,如果有什么不足
- 1.map遍历快速实现边距,文字自适应改变大小Container( // padding: EdgeI
- 本文实例讲述了C#实现带阴历显示的日期代码,分享给大家供大家参考。具体方法如下:这是一个用于酒店预定功能的带日期控件,类似去哪网酒店预定,由
- 先给大家展示下效果图,喜欢的朋友可以下载源码哦。完成这个效果的是使用了 IOS_Dialog_Library下载地址:http://xiaz
- 关于隐藏和覆盖的区别,要提到RTTI(run-time type identification)(运行期类型检查),也就是运行期的多态,当一
- springboot 2.0 mybatis mapper-locations扫描多个路径mapper-locations扫描多个路径,中间
- 问题背景昨晚同事找我帮他看一个问题,他使用mybatis-plus中提供的updateById方法,想将查询结果中某个字段原本不为null的