【学习】Android的Window和WindowManager

81 篇文章 1 订阅
79 篇文章 0 订阅

基本概念

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的过程

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

显示效果

image.png

注意

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类中三大操作实现如下

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

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

image.png

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

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

image.png

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

image.png

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

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

2.删除过程

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

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

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

image.png
在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.更新过程

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

Activity的Window创建过程

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

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

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

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

image.png

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

setContentView方法步骤

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

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

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

image.png

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

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

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

image.png

Dialog的Window创建过程

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

image.png

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

image.png

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

image.png
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过程

image.png

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

enqueueToast方法
image.png
方法第一个参数表示当前应用包名,第三个参数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线程池中。

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

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

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

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

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

image.png

文末

我总结了一些Android核心知识点,以及一些最新的大厂面试题、知识脑图和视频资料解析。

需要的小伙伴直接点击文末小卡片免费领取哦,以后的路也希望我们能一起走下去。(谢谢大家一直以来的支持,需要的自己领取)

Android学习PDF+架构视频+面试文档+源码笔记

部分资料一览:

  • 330页PDF Android学习核心笔记(内含8大板块)

  • Android学习的系统对应视频

  • Android进阶的系统对应学习资料

  • Android BAT大厂面试题(有解析)

领取地址:可点击下方CSDN官方认证卡片免费获取。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Android中的WindowManagerAndroid系统中的一个关键类,它负责管理所有窗口的显示和布局。而悬浮球则是窗口中的一个小球,它可以随意拖动并悬浮在其他应用程序之上。 实现悬浮球的拖动可以通过以下步骤完成: 1. 创建一个悬浮球的View:首先需要创建一个自定义的View,用于显示悬浮球。这个View可以是一个小球的图片或者是一个自定义形状的View。 2. 添加悬浮球的权限:在AndroidManifest.xml文件中添加SYSTEM_ALERT_WINDOW权限,这是一项特殊权限,用于显示系统级别的悬浮窗口。 3. 创建悬浮球的WindowManager:通过WindowManager类的实例,可以获取到系统的WindowManager服务。 4. 将悬浮球的View添加到WindowManager中:使用WindowManager.LayoutParams参数来设置上述View的显示位置、大小、类型等属性,并通过WindowManager的addView方法将View添加到窗口中。 5. 监听悬浮球的触摸事件:为悬浮球的View设置触摸监听器,当用户按下悬浮球并移动手指时,通过更新悬浮球的LayoutParams参数的x和y属性来实现悬浮球的拖动操作。 6. 处理悬浮球的点击事件:为悬浮球的View设置点击监听器,当用户点击悬浮球时,可以执行相应的操作,如显示菜单、打开应用程序等。 7. 移除悬浮球的View:当不再需要显示悬浮球时,可以通过调用WindowManager的removeView方法将悬浮球的View从窗口中移除,并及时回收相关资源。 以上就是利用WindowManager实现Android悬浮球拖动的基本步骤。通过监听触摸事件和点击事件,可以实现悬浮球的拖动和响应用户操作的功能。同时,需要注意悬浮球的权限问题,以保证正确显示和操作悬浮球。 ### 回答2: Android WindowManager 悬浮球是一种常见的用户界面元素,常用于显示快捷方式、通知或其他常用功能。用户可以通过拖动悬浮球的方式来操作相关功能。 要实现悬浮球的拖动功能,首先需要创建一个悬浮球的视图,并将其添加到 WindowManager 中。可以通过创建一个继承自 View 的自定义悬浮球类来实现这一点。 在自定义悬浮球类中,需要重写 onTouchEvent() 方法以处理触摸事件。当用户按下悬浮球时,记录下手指按下的初始位置。当用户移动手指时,根据手指的移动距离来更新悬浮球的位置。可以使用 WindowManager.LayoutParams 的 x 和 y 属性来指定悬浮球的位置。 同时,还可以根据需要添加一些限制条件,如限制悬浮球的移动范围、限制悬浮球的吸附行为等。这些都可以通过对 MotionEvent 的处理来实现。 当用户松开手指时,悬浮球的拖动操作结束。此时,可以保存最后的位置信息以便下次使用。同时,还可以根据需要触发相应的功能操作,如打开一个快捷方式或显示一个通知等。 需要注意的是,要实现悬浮球的拖动功能,必须具有相应的权限。在 AndroidManifest.xml 文件中声明 SYSTEM_ALERT_WINDOW 权限,并在运行时请求该权限,以确保可以在其他应用程序上方显示悬浮球。 综上所述,通过自定义 View 类,重写 onTouchEvent() 方法,结合 WindowManager 的使用,可以实现 Android WindowManager 悬浮球的拖动功能。这样,用户就可以方便地通过拖动悬浮球来操作相关功能,并且可以根据实际需求对拖动行为进行定制。 ### 回答3: 在Android中,WindowManagerAndroid系统的一个重要组件,用于管理应用程序窗口的创建、显示和更新。悬浮球是一种常见的用户界面交互方式,它可以在屏幕上悬浮显示,并且可以通过拖动操作实现位置的改变。 实现悬浮球拖动的基本步骤如下: 1. 创建悬浮球视图:通过编程方式创建一个悬浮球视图,并设置其初始位置和样式。可以使用自定义的绘制工具或者使用现有的View类来实现悬浮球的外观。 2. 监听触摸事件:为悬浮球视图设置触摸事件的监听器,以便能够响应用户的触摸操作。可以通过重写onTouchEvent()方法来实现触摸事件的处理。 3. 处理触摸事件:在触摸事件的监听器中,根据不同的事件类型(如按下、移动、抬起),实现相应的逻辑。例如,当用户按下悬浮球时,记录下手指的初始位置;当用户移动手指时,根据手指的位置和初始位置计算悬浮球的新位置,并更新悬浮球的显示;当用户抬起手指时,完成悬浮球的拖动操作。 4. 更新悬浮球位置:在悬浮球拖动的过程中,需要不断更新悬浮球的位置。可以使用WindowManager的LayoutParams类来设置悬浮球的新位置,并通过WindowManager的updateViewLayout()方法来更新悬浮球的显示。 5. 处理边界情况:在处理悬浮球的拖动过程中,需要考虑到悬浮球是否超出屏幕边界的情况。可以通过判断悬浮球的位置,以及屏幕的大小来限制悬浮球的移动范围,避免悬浮球超出屏幕。 总之,通过使用WindowManager和触摸事件的监听器,可以实现Android中悬浮球的拖动功能。这个功能在许多应用中都很常见,例如一些工具类应用和游戏应用,可以增加用户的交互体验和便利性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值