【学习】Android的Window和WindowMnager

基本概念

Window表示一个窗口的概念,在日常开发中直接接触的机会不多,但是在某些特殊时候需要在桌面上显示一个类似悬浮窗的东西,就要用到Window实现。它是一个抽象类,具体实现是PhoneWindow。创建一个Window很简单,通过WindowManager即可。WindowManager是外界访问Window的入口,WindowManager和WindowManagerService的交互是一个IPC过程。Android中所有的视图都是通过Window呈现的,不管是Activity、Dialog等,都是附加在Window上的,所以Window是View的直接管理者。View事件分发机制中单击事件也是由Window传给DecorView再传给我们的View,就连setContentView在底层也是通过Window完成。

Window和WindowManager

实例演示通过WindowManager添加Window的过程

在这里插入图片描述

上述代码将一个Button添加到屏幕坐标为(100,300)的位置上

显示效果

在这里插入图片描述

注意

1.LayoutParams中的_type参数不能为0,是不合理的
type: 1-99 应用Window层
type: 1000=1999 子Window
type: 2000-2999 系统层 (最顶层)

2.Context对象不能使用ApplitionContext,不管是View还是WindowManager,都只能直接或间接地使用Context而不能使用ApplitionContext。

LayoutParams中的flags和type

FLAG

flags表示Window的属性,它的选项可以控制Window的显示特性(有很多类型,可以去官方API文档去看,这里只介绍常用的)

FLAG_NOT_FOCUSABLE
表示Window不需要获取焦点,也不需要接收各种输入事件,此标记会同时启用FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层的具有焦点的Window

FLAG_NOT_TOUCH_MODAL
系统会将当前Window区域以外的单击事件传递给底层的Window,当前Window区域以内的单击事件则自己处理。一般来说都需要开启此标记,否则其他Window无法收到单击事件

FLAG_SHOW_WHEN_LOCKED
开启此模式可以让Window显示在锁屏的界面上。

TYPE

type参数表示Window的类型,Window有三种类型,分别是应用Window、子Window、系统Window。
应用类Window对应着一个Activity

子Window不能单独存在,它需要附属在特定的父Window之中,比如一些常见的Dialog就是一个子Window

系统Window是需要声明权限才能创建的Window,比如Toast和系统状态栏都是系统View

Window是分层的,每个Window都有对应的z-ordered,层级大的会覆盖在层级小的Window上面,这个HTML中z-index概念一致。在三类Window中
type: 1-99 应用Window层
type: 1000 -1999 子Window
type: 2000-2999 系统层 (最顶层)

系统层级有很多值,一般可以选用TYPE_SYSTEM_OVERLAY或者TYPE_SYSTEM_ERROR,如果采用ERROR只需要为type参数指定这个层级即可,同时声明权限
< uses-permission android:name=“android.permission.SYSTEM_ALERT_WINDOW”/>
因为系统类型的Window是需要检查权限的。

WindowManager所提供的功能很简单,常用的只有三个方法,添加、更新、删除View,这三个方法定义在ViewManager中,而WindowManager继承了ViewManager。
它操作Window的过程更像是在操作其中的View,拖动Window效果只需要根据手指的位置来设定LayoutParams中的xy的值即可改变Window位置,给View设置onTouchListener,然后在onTouch更新View位置

Window的内部机制

它是一个抽象概念,每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系,因此Window不是实际存在的,它以View的形式存在。View才是Window存在的实体,对它的访问必须通过WindowManager。

从Window的添加、删除、更新分析内部机制

1.添加过程

添加通过WindowManager的addView实现,而manager是一个接口,它的真正实现是WindowManagerImpl类,在Impl类中三大操作实现如下

在这里插入图片描述

它把操作都交给了WindowManagerGlobal来处理,Global以工厂的形式向外提供自己的实例

在这里插入图片描述

Impl这种模式是典型的桥接模式,把所有操作委托给WindowManagerGlobal来实现,Global的addView方法主要分为以下几步
1.检查参数是否合法,如果是子View还需要调整一些布局参数

在这里插入图片描述

2.创建ViewRootImpl并将View添加到列表中
比较重要的列表

在这里插入图片描述

mViews存储的是所有Window所对应的View,mRoots存储的是所有Window所对应的ViewRootImpl,mParams存储所有Window所对应的布局参数,mDyingViews存储那些正被删除的View的对象,或者说是那些已经调用remoteView方法但是删除操作还未完成的Window对象。在addView中通过下图语句将Window的一系列对象添加到列表中

在这里插入图片描述

3.通过ViewRootImpl来更新界面并完成Window的添加过程
这个步骤由ViewRootImpl的setView方法来完成,在setView内部会通过requestLayout来完成异步刷新请求

在这里插入图片描述

在这里插入图片描述

接着会通过WindowSession最终来完成Window的添加过程

在这里插入图片描述

此时Window的添加请求就交给了WindowManagerService去处理了,在Service内部会为每一个应用保留一个单独的Session。

2.删除过程

和添加一样都是先通过WindowManagerImpl后再进一步通过Global来实现

在这里插入图片描述

首先通过findViewLocked来查找待删除的View的索引,来查找待删除的View的索引,这个过程就是数组遍历,然后调用remoteViewLocked来进一步删除

在这里插入图片描述

通过ViewRootImpl来完成删除操作。在WindowManager中提供了两种删除接口remoteView和remoteViewImmediate,它们分别表示异步删除和同步删除,其中remoteViewImmediate一般不用它来删除Window以免发生以外的错误。在异步删除的情况下,die方法只是发送了一个请求删除的消息后就立刻返回了,这个时候View没有完成删除操作,最后会将其添加到mDyingViews中,mDyingViews表示待删除的View列表。

在这里插入图片描述

在die方法中只做了简单的判断,如果是异步删除就发送一个MSG_DIE的消息,ViewRootImpl的Handler会处理此消息并调用doDie方法,如果是同步删除,就直接调用doDie,这就是同步异步删除的区别。真正删除View的逻辑在dispatchDetachedFromWindow方法的内部实现。

它主要做四件事

1.垃圾回收相关工作:清除数据、移除回调

2.通过Session的remove方法删除Window:mWindowSession.remove(mWindow),一个IPC过程,最终调用WindowManagerService的removeWindow方法

3.调用View的onDetatchedFromWindow()和onDetatchedFromWindowInternal(),当View从Window中移除时onDetatchedFromWindow()就会被调用,可作资源回收工作

4.调用WindowManagerGlobal的doRemoveView方法刷新数据,包括mRoots、mParams、mDyingViews,需要将Window关联的三类对象从列表中移除

3.更新过程

在这里插入图片描述

首先更新View的LayoutParams并替换掉老的LayoutParams,接着再更新ViewRootImpl的LayoutParams。在ViewRootImpl中会通过scheduleTraversals方法对View重新布局,包括测量、布局、重绘这三个过程。除了View本身的重绘以外,它还会通过WindowSession来更新Window的视图,这个过程最终由WindowManagerService的relayoutWindow()来具体实现的,它同样是一个IPC过程。

Activity的Window创建过程

Activity的启动过程后续会讲到,这里先知道最终会由ActivityThread中的performLaunchActivity()来完成整个启动过程,在这个方法内部会通过类加载器创建Activity的实例对象,并调用attach方法为其关联运行过程中所依赖的一系列上下文环境变量

在这里插入图片描述

系统会创建Activity所属的Window对象并为其设置回调接口,Window对象的创建是通过PolicyManager的makeNewWindow方法实现的。由于Activity实现了Window的Callback接口,因此当Window接收到外界的状态改变时就会回调活动中的方法。Callback接口中方法很多,我们熟悉的有onAttachToWindow onDetachedFromWindow dispatchTouchEvent

在这里插入图片描述

Activity的Window是通过PolicyManager的一个工厂方法来创建的,但是从PolicyManager的类名可以看出它是一个策略类,PolicyManager中实现的几个工厂方法全都在策略接口IPolicy声明了

在这里插入图片描述

在实际的调用中,PolicyManager的真正实现是Policy类,类中的makeNewWindow实现如下

在这里插入图片描述

在这里插入图片描述

活动把具体实现交给了Window处理,而Window的具体实现是PhoneWindow。

setContentView方法步骤

1.如果没有DecorView,就创建它
DecorView是一个FrameLayout,是活动的顶级View,一般来说内部包含标题栏和内部栏,但会随着主题改变而改变。而内容栏是一定要存在的,并且内容来具体固定的id,那就是android.R.content。DecorView的创建过程由installDecor完成,在方法内部会通过generateDecor方法来直接穿件DecorView,此时DecorView还只是一个空白FrameLayout

在这里插入图片描述

为了初始化DecorView结构,PhoneWindow还要通过generateLayout方法来加载具体的布局文件到DecorView中,具体的布局文件和系统版本以及主题有关

在这里插入图片描述

其中ID_ANDROID_CONTENT的定义如下,这个id所对应的ViewGroup是mContentParent

在这里插入图片描述

2.将View添加到DecorView的mContentParent中
mLayoutInflater.inflate(layoutResID,mContentParent)将活动的视图添加到DecorView的mContentParent中。

3.回调Activity的onContentChanged方法通知Activity视图已经发生改变
活动的onContentChanged方法是个空实现,我们可以在子活动处理这个回调

在这里插入图片描述

此时DecorView还没有被WindowManager正式添加到Window中。在ActivityThread的handleResumeActivity方法中,首先会调用活动的onResume方法,接着调用活动的makeVisible,在该方法中DecorView真正完成添加和显示的过程,此时活动的视图才能被看到

在这里插入图片描述

Dialog的Window创建过程

1.创建Window
同样是通过PolicyManager的makeNewWindow方法完成,创建的对象实际上就是PhoneWindow,和活动创建Window过程一致

在这里插入图片描述

2.初始化DecorView并将Dialog的视图添加到DecorView中
也和活动的类似,通过Window去添加指定的布局文件

在这里插入图片描述

3.将DecorView添加到Window中并显示
在Dialog的show方法中,会通过WindowManager将DecorView添加到Window中

在这里插入图片描述

Dialog和Activity的Window创建过程很类似,当Dialog被关闭时,他会通过WindowManager来移除DecorView:mWindowManager.remoteViewImmediate(mDecor)

普通的Dialog有一个特殊之处,必须采用Activity的Context,如果采用Application的Context就会报错,因为没有应用token,它一般只有Activity才拥有。系统Window可以不需要token

Toast的Window创建过程

它也是基于Window实现,由于Toast具有定时取消的功能,所以系统采用Handler。在Toast内部由两类IPC过程
1.Toast访问NotificationManagerService
1.NotificationManagerService回调Toast里的TN接口

Toast属于系统Window,内部视图由两种方式指定
1.系统默认
2.通过setView方法指定一个自定义View
不管如何它们都对应Toast的一个View类型的内部成员mNextView。Toast提供了show和cancel分别用于显示和隐藏Toast,它们的内部是一个IPC过程

在这里插入图片描述

在这里插入图片描述

可以看到显示和隐藏Toast都需要NotificationManagerService来实现,NMS运行在系统进程中,所以只能通过远程调用的方法来显示和隐藏Toast,需要注意TN这个类,它是一个Binder类,在Toast和NMS进行IPC过程中,当NMS处理Toast的显示或隐藏请求时会跨进程回调TN中的方法,这个时候由于TN运行在Binder线程池中,所以需要通过Handler将其切换到当前进程中。当前线程指发送Toast请求所在的线程。由于这里使用了Hnadler,所以意味着Toast无法再没有Looper的线程中弹出,因为Hanlder需要Looper才能完成切换线程的功能

enqueueToast方法
在这里插入图片描述

方法第一个参数表示当前应用包名,第三个参数tn表示远程回调,第四个参数表示Toast的时长。enqueueToast先将Toast请求封装为ToastRecord对象并将其添加到一个名为mToastQueue的队列中,他其实是一个ArrayList。对于非系统应用来说,mToastQueue中最多能同时存在50个ToastRecord,这样做是为了方式Denial of Service。不这么做,如果我们通过大量循环去连续弹出Toast,会导致其他应用没机会弹出Toast,对于其他应用的Toast请求,系统的行为就是DOS。

正常情况下一个应用不可能达到上限,当ToastRecord被添加到队列后,NMS会通过showNextToastLocked来显示当前Toast。Toast的显示由ToastRecord的callback来完成的,这个callback实际上就是Toast中的TN对象的远程Binder,通过callback来访问TN中的方法是需要跨进程来完成的,最终被调用的TN的方法会运行在发起Toast请求的应用的Binder线程池中。

在这里插入图片描述

Toast显示以后,NMS还会通过seheduleTimeoutLocked方法来发送一个延时消息,具体的延时取决于Toast的时长

在这里插入图片描述

LONG_DELAY 3.5S SHORT_DELAY 2S 延迟响应时间后,NMS会通过cancelToastLocked方法隐藏Toast并将其从mToastQueue移除,如果队列中还有其他Toast那么NMS就继续显示其他Toast。

Toast的隐藏也是通过ToastRecord的callback来完成的,这同样也是一次IPC过程

在这里插入图片描述

Toast的显示和影响过程实际上是通过Toast中的TN这个类实现的。show和cancel是被NMS以跨进程的方式调用的,因此它们运行在Binder线程池中。为了将执行环境切换到Toast请求所在的线程,在它们内部使用了Handler。

在这里插入图片描述

mShow和mHide是两个Runnable,它们内部分别调用了handleShow和handleHide方法。他们才是真正完成显示和隐藏Toast的地方。TN的handleShow会将Toast视图添加到Window中

在这里插入图片描述

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值