Android-Window和WindowManager
Posted SyubanLiu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android-Window和WindowManager相关的知识,希望对你有一定的参考价值。
1. 前言
Window是一个抽象类,它的具体实现是PhoneWindow,WindowManager是外界访问Window的入口,Window的具体实现位于WindowManagerService中,WindowManagerService和WindowManager的交互是一个IPC的过程。Window实际是View的管理者。
2. Window和WindowManager的使用
WindowManager.LayoutParams中的flags与type是两个重要的参数。
2.2 flags参数
表示Window的属性,可以控制Window的显示特性,比较常用的参数如下:
- FLAG_NOT_FOCUSABLE: 表示Window不需要获取焦点,也不需要接收任何输入事件,此标记同时也启用FLAG_NOT_TOUCH_MODEL,最终事件会直接传递给下层的具有焦点的Window;
- FLAG_NOT_TOUCH_MODEL: 系统会将当前区域以外的事件传递的给底层Window,区域以内的事件由自己处理,一般都需要开启此标记,否则其他Window无法接收到事件;
- FLAG_SHOW_WHEN_LOCKED: 让Window在锁屏的界面上显示;
2.3 type参数
表示Window的类型,主要有三种类型:应用Window、子Window、系统Window;
- 应用类Window:对应着一个Activity;
- 子Window:需要附属在父Window之中,例如:常见的Dialog;
- 系统Window:需要声明权限在能创建的Window,例如:Toast和系统状态栏这些都是系统Window;
Window是分层的,每个Window都有对应的z-ordered,层级大的覆盖在层级小的Window上面。在这三类type的Window中,层级关系:系统Window > 子Window > 应用Window。
如果想要Window位于所有Window的最顶层,那么采用较大的层级即可。显然系统Window的层级是最大的,系统层级有很多值,一般我们选用TYPE_SYSTEM_OVERLAY或者TYPE_SYSTEM_ERROR,使用的时候给type参数指定这个值,并声明权限,因为系统类型的Window是需要检查权限的。
2.4 WindowManager使用
WindowManager提供的功能很简单,即常用的三个方法:添加View、更新View、删除View,这三个方法定义在ViewManager中,而WindowManager继承自ViewManager。
应用场景:Window的拖动。根据手指位置,不断更新LayoutParams中的 x 和 y 的值即可改变Window的位置。
3. Window的内部机制
Window是一个抽象的概念,每个Window都对应着一个View和一个ViewRootImpl,Window和View之间通过ViewRootImpl来建立联系,因此Window并不存在,它是以View的形式存在的,即View才是Window的实体。在实际使用中无法直接访问Window,仅能通过WindowManager来进行对Window的访问。
3.1 Window的添加过程
Window的添加过程是通过WindowManager的addView来实现的,WindowManager是一个接口,真正实现的是WindowManagerImpl。
在WindowManagerImpl中,并没有直接实现Window的三个操作,而是交由WindowManagerGlobal来处理。WindowManagerGlobal以工厂形式想外提供自己的单例。
WindowManagerImpl的这种工作模式就是桥接模式,将所有的操作交由WindowManagerGlobal来处理。
WindowManagerGlobal的addView的方法主要分为如下步骤:
-
检查参数(view、display、params)是否合法,如果是子Window那么还需要调整一些布局参数
-
创建ViewRootImpl并将View添加到列表中,其中有 4 个存储列表:
- mViews:存储所有Window对应的View
- mRoots:存储所有Window对应的ViewRootImpl
- mParams:存储所有Window对应的布局参数
- mDyingViews:存储正在被删除的View对象,或者是已经调用了removeView方法但是删除操作还没完成的Window对象
-
通过ViewRootImpl来更新界面并完成Window的添加过程:该步骤通过调用ViewRootImpl的setView方法来完成,在setView内部会通过requestLayout操作来完成异步刷新请求。scheduleTraversals实际是View绘制的入口。
-
接着通过WindowSession来完成Window的添加过程,其类型是IWindowSession,它是一个Binder对象,真正的实现类是Session,也就是Window的添加过程实际上是一次IPC调用。其在Session内部会通过WindowManagerService来实现Window的添加。
-
最终Window的添加请求交由WindowManagerService完成,在WindowManagerService内部会为每一个应用保留单独的Session。
3.2 Window的删除过程
- 先通过WindowManagerImpl,随后通过WindowManagerGlobal来实现,调用removeView完成Window的删除操作
- 在removeView中,通过findViewLocked来查找待删除的View的索引,这个查找过程就是数组遍历,然后再调用removeViewLocked来做进一步删除,其是通过ViewRootImpl来完成删除操作的。在WindowManager 中有两个删除接口 removeView 和 removeViewImmediate,分别表示 同步删除 和 异步删除。
- 通过使用异步删除Window的方式,removeView内部由ViewRootImpl的 die 方法来完成,die方法发送一个删除请求的消息后就立马返回了,这时View并还没有删除,所以最后会将其添加到 mDyingViews 列表中
- 在die方法中,如果是异步删除,则发送一个MSG_DIE的消息,ViewRootImpl中的Handler会处理此消息并调用die方法;若是同步删除,则会直接调用doDie方法,不会发送消息。在 doDie 方法内部会调用 dispatchDetachedFromWindow,真正的删除操作再其内部实现。
- 在dispatchDetachedFromWindow中,主要做了如下操作:
- 垃圾回收工作。如清除数据和消息、移除回调;
- 通过Session的remove方法删除Window:通过mWindowSession.remove,随后通过IPC方式,调用WindowManagerService的removeView方法
- 调用View的 dispatchDetachedFromWindow 方法,在内部会调用View的 onDetachedFromWindow 和 onDetachFromWindowInternal。对于onDetachFromWindow方法,当View从Window中移除时,该方法一定会被调用,其内部做一些资源回收的工作,如终止动画,停止线程等
- 调用WindowManagerGlobal的doRemoveView方法刷新数据,包括mRoots、mParams及mDyingViews,将当前Window所关联的这三类对象移除
3.3 Window的更新过程
- 跟addView、removeView类似,最终调用WindowManagerGlobal的updateViewLayout方法
- updateViewLayout的方法做的事情相对简单,首先更新View的LayoutParams,然后通过findViewLocked找到View的索引,删除mParams中就有的LayoutParams,在索引上添加新的layoutParams,接着更行ViewRootImpl中的LayoutParams。
- 在ViewRootImpl中会通过调用scheduleTraversals方法来对View重新绘制,包括测量、布局、绘制。除了View自身重绘,还会通过WindowSession来更新Window的视图,最终依旧交由WindowManagerService的relayoutWindow方法来实现。
4. Window的创建过程
View不能单独存在,它需要附着在Window上面,因此有视图的地方就有Window。
4.1 Activity的Window创建过程
Activity的启动过程很复杂,最终会由ActivityThread中的performLaunchActivity来完成整个启动过程,在这个方法内部会通过类加载器来创建Activity的实例对象,并调用attach方法来为其关联运行中所依赖的一系列上下文环境变量。
在Activity的attach方法中,系统会为其创建Activity所属的Window对象并为其设置回调接口,Window对象的创建是通过PolicyManager的makeNewWindow来实现的。
由于Activity实现了Window的Callback接口,因此当Window接收到外界的状态改变时就会回调Activity的方法。Callback接口中有很多方法,常用的是:onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent等等
Activity的Window由PolicyManager创建的,在实际的调用中,PolicyManager的真正实现是Policy类,在Policy类的makeNewWindow中,创建了一个PhoneWindow。
Window的创建已经完成了,接下来,分析下Activity的视图是如何附属在Window上的,首先将分析Activity的setContentView方法。在Activity的setContentView方法中,Activity将具体的实现交由Window处理,而Window的具体实现是PhoneWindow,所以接下来看下PhoneWindow中的相关逻辑。
- 如果没有DecorView,就去创建它:DecorView是Activity中的顶级View,内部包含标题栏和内容栏。DecorView的创建过程由installDecor来完成,在方法内部通过generateDecor方法来直接创建DecorView,这个时候的DecorView还是一个空白的FrameLayout。除此之外,还将会调用generateDecor方法将布局文件加载到DecorView中。
- 将View添加到DecorView的mContentParent中:通过inflate将Activity的视图直接添加到DecorView的mContentParent中。
- 回调Activity的onContentChanged方法通知Activity视图已经发生改变:由于Activity实现了Window的Callback接口,这里表示Activity的视图已经被添加到DecorView的mContentParent中,于是通知Activity。
- 经过了上述的三个步骤后,此时的DecorView已经被创建且初始化完成,Activity的视图文件也添加到DecorView的mContentParent中,但是这个时候DecorView还没被WindowManager正式添加到Window中。虽然在Activity的attach方法中创建了Window,但是这个时候DecorView还没有被WindowManager识别,所以此时的Window无法提供具体功能,无法接受外界的信息。在ActivityThread的handleResumeActivity中,首先会调用Activity的onResume方法,随后会调用Activity的makeVisible方法,在makeVisible中,DecorView完成真正的添加和显示过程,至此Activity的视图才可被用户所见。
4.2 Dialog的Window创建过程
- 创建Window:同样通过 PolicyManager 的 makeNewWindow 来创建 Window,创建后的对象依旧是PhoneWindow,与 Activity 的创建 Window 过程一样。
- 初始化 DecorView 并将 Dialog 的视图添加到DecorView中。
- 将DecorView添加到Window中并显示。
注意:应用token一般只有Activity拥有,系统Window比较特殊,可以不需要token。因此当应用Window使用ApplicationContext时,运行会报错。所以Dialog的Window创建默认需闯入Activity的Context。
4.3 Toast的Window创建过程
- Toast的具体定时功能,所以系统才用了Handler。
- 在Toast里由两类IPC过程,一个是Toast访问的 NotificationManagerService ,另一个是NotificationManagerService 回调的Toast里的 TN 接口。
- Toast属于系统 Window,其内部有两种方式指定视图,一种是系统默认的方式,另一种是通过setView方式来指定,不管哪种方式,都对应着Toast的mNextView。Toast的 show 和 cancel 方法内部都是IPC过程。
- 在Toast显示过程中,调用 NMS 的 enqueueToast 方法,将Toast请求封装成 ToastRecord 添加到mToastQueue中,其容量为50,为了防止恶意的DOS攻击。
service.enqueueToast(pkg, tn, mDuration)
- Toast的显示与隐藏分别调用 handleShow 和 handleHide 方法。
注意:由于Toast的显示与隐藏涉及到 IPC 过程,所以涉及到 Binder 来跨进程通信。TN 是一个 Binder 类,当Toast处理显示或者隐藏的时候,都会回调TN这个接口。这时TN运行在Binder线程池中,需要通过Handler来切换到当前线程,因此Toast无法在没有Looper的线程中弹出。
以上是关于Android-Window和WindowManager的主要内容,如果未能解决你的问题,请参考以下文章
如何在 TabLayout 中找到由 RecyclerView 打开的对话框的上下文?
Android7.1.1系统,Toast的Exception: android.view.WindowManager$BadTokenException解决
Android7.1.1系统,Toast的Exception: android.view.WindowManager$BadTokenException解决
Android7.1.1系统,Toast的Exception: android.view.WindowManager$BadTokenException解决
Android7.1.1系统,Toast的Exception: android.view.WindowManager$BadTokenException解决