前往小程序,Get更优阅读体验!
立即前往
社区首页 >专栏 >如何判断 Activity 上是否有弹窗

如何判断 Activity 上是否有弹窗

作者头像
codelang
发布于 2022-11-30 06:50:39
发布于 2022-11-30 06:50:39
3.3K00
代码可运行
举报
文章被收录于专栏:codelangcodelang
运行总次数:0
代码可运行

今天来看个需求,如何判断 Activity 上面是否有弹窗,当然,简单的方式肯定有,例如在 Dialog show 的时候记录一下,但这种方式不够优雅,我们需要一款更通用的判断方式。

Android 目前的弹窗有如下几种:

  1. 普通的应用窗口,如 Dialog
  2. 附加与普通窗口的子窗口,如 PopWindow
  3. 系统窗口,如 WindowManager type 在 FIRST_SYSTEM_WINDOW 与 LAST_SYSTEM_WINDOW 之间

通过图来简单来了解下 Window 和 View 的关系:

  • Activity 在 attach 阶段创建了 PhoneWindow,并将 AppToken 存储到 PhoneWindow 中,然后通过 createLocalWindowManager 创建了一个本地的 WindowManager,该实例是 WindowManagerImpl,构造传入的 parentWindow 为 PhoneWindow。在 onResume 阶段时,从 PhoneWindow 中获取 WindowManager 来 addView
  • Dialog 有自己的 PhoneWindow,但 Dialog 并没有从 PhoneWindow 中去 get WindowManager,而是直接使用 getSystemService 拿到 Activity 的 WindowManager 来 addView
  • PopWindow 内部是通过 getSystemService 来拿到 Activity WindowManager + 内置子窗口 type 来实现的弹框

方案 1、通过 mView 集合中的 Activity 区间来判断

从上面我们可以简单了解到,当前进程所有窗口 View,最终都会被存储到 WindowManagerGlobal 单例的 mViews 集合中,那我们是不是可以从 mView 这个集合入手?我们来简单画个 mView 的存储图:

WindowManager addView 时,都会往 mView 这个集合中进行添加。所以,我们只需要判断在 mView 集合中,两个 activity 之间是否有存在其他的 View,如果有,那就是有弹窗,开发步骤为:

  1. registerActivityLifecycleCallbacks 获取所有 Activity 的实例
  2. 传入想判断是否有弹窗的目标 Activity 实例,并获取该实例的 DecorView
  3. 拿到所有 Activity 实例的 DecorView 集合
  4. 遍历 mView 集合,并判断 mView 中的 View 是否与目标 Activity 的 DecorView 一致,是的话,说明找到了activity 的 index 位置
  5. 接下来从 index +1 的位置开始继续遍历 mView,判断 mView 中的 View 是否是 DecorView 集合中的实例,如果没有,则说明不是 Activity 的 View,继续遍历,直到 View 为 DecorView 集合中的实例为止

部分代码实现如下:

代码语言:javascript
代码运行次数:0
复制
fun hasFloatingWindowByView(activity: Activity): Boolean {
    return getFloatWindowView(activity).isNotEmpty()
}

fun getFloatWindowByView(activity: Activity): List<View> {
    // 对应步骤 2
    val targetDecorView = activity.window.decorView
    // 对应步骤 3
    val acDecorViews = lifecycle.getActivities().map { it.window.decorView }.toList()
    // 对应步骤 4
    val mView = Window.getViews().map { it }.toList()
    val targetIndex = mView.first { it == targetDecorView }
    // 对应步骤 5
    val index = mView.indexOf(targetIndex)
    val floatView = arrayListOf<View>()
    for (i in index + 1 until mView.size) {
        if (acDecorViews.contains(mView[i])) {
            break
        }
        floatView.add(mView[i])
    }
    return floatView
}

具体演示可以参考 Demo,这里说个该方案的缺点,由于 mView 是个 List 集合,每次有新的 View add 进来,都是按 ArrayList.add 来添加 View 的,如果我们在启动第二个 Activity 的时候,触发第一个 Activity 来展示 Dialog,这时候的展示效果如下:

这时候如果拿第一个 Activity 来判断是否有弹窗的话,是存在误判的,因为这时候的两个 Activity 之间没有其他 View。

所以,通过区间来判断还是有缺点的。那有没有一种方法,可以直接遍历 mView 集合就能找到目标 Activity 是否有弹窗呢?还真有,那就是 AppToken。

方案二:通过 AppToken 来判断

在文章开头的概念中,我们了解到,PopWindow、Dialog 使用的都是 Activity 的 WindowManager,并且,该WindowManager 在初次创建时,构造函数传入的 parentWindow 为 PhoneWindow,这个 parentWindow 很重要,因为在 WindowManagerGlobal 的 addView 方法中,他会通过 parentWindow 来拿到 AppToken,然后设置到 WindowManager.LayoutParams 中,并参与最终的界面展示。我们来看下设置 AppToken 的代码:

parentWindow 为 PhoneWindow,不为空,所以会进入到 PhoneWindow 父类 Window 的adjustLayoutParamsForSubWindow 方法:

  1. 子窗口判断:取 DecorView 里面的 WindowToken 设置到 wp 参数中。该 DecorView 为 Activity PhoneWindow 里的 DecorView,所以,该 windowToken 可以通过 Activity 的 DecorView 中拿到
  2. 系统弹窗判断:不设置 token,wp 中的 token 参数为 null
  3. 普通弹窗判断:将 AppToken 直接设置到 wp 参数中。该 AppToken 为 Activity PhoneWindow 里的 AppToken

通过这个三个判断我们了解到,子窗口的 windowToken 与普通弹窗的 AppToken 都可以与 Activity 挂钩了,这下,通过目标 Activity 就可以找到他们。至于系统弹窗,我们只需要 token 为 null 时即可。

wp 最终会被添加到 mParams 集合中,他与 mView 和 mRoot 的索引是一一对应的:

画个简单的图来概括下:

然后再结合 adjustLayoutParamsForSubWindow 对 token 的设置来描述下开发步骤:

  1. 传入想判断是否有弹窗的目标 Activity 实例,并获取该实例的 DecorView 与 windowToken
  2. 拿到 mView 集合,根据目标 Activity 的 DecorView 找到 index 位置
  3. 由于 mView 与mParams 集合是一一对应的,所以,可以根据该 index 位置去 mParams 集合里面找到目标 Activity 的 AppToken
  4. 遍历 mParams 集合中的所有 token,判断该 token 是否为目标 windowToken,目标 AppToken 或者是 null,只要能命中,则说明有弹窗

部分代码实现如下:

代码语言:javascript
代码运行次数:0
复制
fun hasFloatWindowByToken(activity: Activity): Boolean {
    // 获取目标 Activity 的 decorView
    val targetDecorView = activity.window.decorView
    // 获取目标 Activity 的 windowToken
    val targetSubToken = targetDecorView.windowToken

    //  拿到 mView 集合,找到目标 Activity 所在的 index 位置
    val mView = Window.getViews().map { it }.toList()
    val targetIndex = mView.indexOfFirst { it == targetDecorView }

    // 获取 mParams 集合
    val mParams = Window.getParams()
    // 根据目标 index 从 mParams 集合中找到目标 token
    val targetToken = mParams[targetIndex].token

    // 遍历判断时,目标 Activity 自己不能包括,所以 size 需要大于 1
    return mParams
        .map { it.token }
        .filter { it == targetSubToken || it == null || it == targetToken }
        .size > 1
}

演示步骤:

在第一个 Activity 打开系统弹窗,然后进入第二个 Activity,调用两种方式来获取当前是否有弹窗的结果如下

  • 第一种方案会判断失败,因为这时候的弹窗 View 在第一个 Activity 与 第二个 Activity 之间,所以,第二个 Activity 无法通过区间的方式判断到是否有弹窗
  • 第二种方案判断成功,因为这时候的弹窗 token 为 null,并通过 getFloatWindowViewByToken 方法,拿到了弹窗 View 对象

总结

本期通过提出需求的方式来探索方案的可行性,对于枯燥的源码来说,针对性的去看确实是个不错的主意

附上 demo 源码:https://github.com/MRwangqi/FloatingWindow[1]

参考资料

[1]

https://github.com/MRwangqi/FloatingWindow: https://github.com/MRwangqi/FloatingWindow

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-08-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 扣浪 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Android窗口管理分析(3):窗口分组及Z-order的确定总结
在Android系统中,窗口是有分组概念的,例如,Activity中弹出的所有PopupWindow会随着Activity的隐藏而隐藏,可以说这些都附属于Actvity的子窗口分组,对于Dialog也同样如此,只不过Dialog与Activity属于同一个分组。之间已经简单介绍了窗口类型划分:应用窗口、子窗口、系统窗口,Activity与Dialog都属于应用窗口,而PopupWindow属于子窗口,Toast、输入法等属于系统窗口。只有应用窗口与系统窗口可以作为父窗口,子窗口不能作为子窗口的父窗口,也就说
看书的小蜗牛
2018/06/29
2.7K0
浅析 Android 的窗口
一、窗口的概念 在开发过程中,我们经常会遇到,各种跟窗口相关的类,或者方法。但是,在 Android 的框架设计中,到底什么是窗口?窗口跟 Android Framework 中的 Window 类又是什么关系?以手机QQ 的主界面为例,如下图所示,上面的状态栏是一个窗口,手机QQ 的主界面自然是一个窗口,而弹出的 PopupWindow 也是一个窗口,我们经常使用的 Toast 也是一个窗口。像 Dialog,ContextMenu,以及 OptionMenu 等等这些都是窗口。这些窗口跟 Window
腾讯Bugly
2018/03/23
3.8K0
浅析 Android 的窗口
Android | 理解 Window 和 WindowManager
Window 是一个窗口的概念,是所有视图的载体,不管是 Activity,Dialog,还是 Toast,他们的视图都是附加在 Window 上面的。例如在桌面显示一个悬浮窗,就需要用到 Window 来实现。WindowManager 是访问 Window 的入口。
345
2022/03/25
9370
Android | 理解 Window 和 WindowManager
Window源码分析之Activity篇
Window表示一个窗口的概念,在日常开发中直接接触Window的机会并不多,但是在某些特殊时候我们需要在桌面上显示一个类似悬浮窗的东西,那么这种效果就需要用到Window来实现。Window是一个抽象类,它的具体实现是PhoneWindow。本文主要整理了Window有关源码的部分分析记录, 可能有差错的地方,请批评指正。
open
2020/03/19
6870
Window源码分析之Activity篇
Window十二问(快扶我起来,我还能问)
窗口。你可以理解为手机上的整个画面,所有的视图都是通过Window呈现的,比如Activity、dialog都是附加在Window上的。Window类的唯一实现是PhoneWindow,这个名字就更加好记了吧,手机窗口呗。
码上积木
2021/02/12
6040
Window十二问(快扶我起来,我还能问)
Window刨根问底
上篇文章中有说到Actiivity中window的一些知识,今天就和大家一起好好捋捋这个window。
码上积木
2020/12/11
4950
Window刨根问底
必要掌握!Window、WindowManager !
Window是View的管理者,当我们说创建Window时,一方面指实例化这个管理者,一方面指 用WindowManager.addView()添加view,以view的形式来呈现Window这个概念。
胡飞洋
2020/07/23
1.7K0
必要掌握!Window、WindowManager !
Android之WindowManager介绍
activity在android中所其的作用主要是处理一些逻辑问题,比如生命周期的管理、建立窗口等。
李小白是一只喵
2021/04/20
9290
Android之WindowManager介绍
Window, WindowManager和WindowManagerService
Window在Android开发中是一个窗口的概念,它是一个抽象类,具体的实现类是PhoneWindow,在PhoneWindow中有一个顶级View—DecorView,继承自FrameLayout,我们可以通过getDecorView()获得它,当我们调用Activity的setContentView时,其实最终会调用Window的setContentView,当我们调用Activity的findViewById时,其实最终调用的是Window的findViewById,这也间接的说明了Window是View的直接管理者。但是Window并不是真实存在的,它更多的表示一种抽象的功能集合,View才是Android中的视图呈现形式,绘制到屏幕上的是View不是Window,但是View不能单独存在,它必需依附在Window这个抽象的概念上面,Android中需要依赖Window提供视图的有Activity,Dialog,Toast,PopupWindow,StatusBarWindow(系统状态栏),输入法窗口等,因此Activity,Dialog等视图都对应着一个Window。
提莫队长
2020/06/03
8200
Window, WindowManager和WindowManagerService
Android模拟面试,解锁大厂——从Activity创建到View呈现中间发生了什么?
前段时间公司招人,作为面试官,我经常让面试者简述View的绘制流程。他们基本都能讲明白View的测量(measure)、布局(layout)、绘制(draw)等过程。还有少数人会提到DecorView和ViewRootImp的作用。但是,当我继续追问关于Window的内容时,几乎没有人回答上来。而本章将会带你深入理解Window、DecorView、ViewRootImp。除此之外,你还能在本章找到以下问题的答案:
Android技术干货分享
2020/09/22
8110
Android模拟面试,解锁大厂——从Activity创建到View呈现中间发生了什么?
Android窗口管理分析(2):WindowManagerService窗口管理之Window添加流程
之前分析说过,WindowManagerService只负责窗口管理,并不负责View的绘制跟图层混合,本文就来分析WMS到底是怎么管理窗口的。初接触Android时感觉:Activity似乎就是Google封装好的窗口,APP只要合理的启动新的Activity就打开了新窗口,这样理解没什么不对,Activity确实可以看做一种窗口及View的封装,不过从源码来看,Activity跟Window还是存在不同。本文主要从窗口的添加流程来将APP端、WMS端、SurfaceFlinger端三块串联起来,主要说一
看书的小蜗牛
2018/06/29
3K0
为什么不能使用 Application Context 显示 Dialog?
看到报错信息中的 token ,不知道你还记不记得上篇文章中介绍过的 Activity.attach() 方法:
字节流动
2020/11/30
2K0
为什么不能使用 Application Context 显示 Dialog?
Android高频面试专题 - 提升篇(一)Window、View、Activity
表示一个窗口的概念,是所有View的直接管理者,任何视图都通过Window呈现(点击事件由Window->DecorView->View; Activity的setContentView底层通过Window完成)
Android扫地僧
2020/03/19
2.5K0
Android高频面试专题 - 提升篇(一)Window、View、Activity
Window源码解析(二):Window的添加机制
注:本文解析的源码基于 API 25,部分内容来自于《Android开发艺术探索》。
俞其荣
2022/07/28
7990
Android源码分析之理解Window和WindowManager
Window是一个抽象类,它的具体实现是PhoneWindow,创建一个Window通过WindowManager 就可以完成。WindowManager是外界访问Window的入口,它的具体实现在WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC的过程。Android中所有的视图都是通过Window来呈现的,无论是Activity,Dialog还是Toast,Window实际上是View的直接管理者。单击时间由Window传递给DecorView,然后DecorView再传递给我们的View,就连Activity设置视图的方法setContentView底层也是通过Window来完成的。
老马的编程之旅
2022/06/22
1K0
Activity 启动过程的简单分析
我们都知道,Activity 是有生命周期的,onCreate()、onStart() 、onResume 等等那么这些方法是如何调用的呢?它不会平白无故的自己调用。其实当我们开启 APP 的时候会创建一个叫做 ActivityThread 的类,我们可以认为这个类是主类,就和 Java 程序中的启动类一样,ActivityThread 类有一个 main 函数,这个函数就是我们的程序入口!
开发者
2019/12/26
4630
Activity 启动过程的简单分析
[虾扯蛋] android界面框架-Window
用户1172465
2018/01/08
1.4K0
Android View和 Window 的关系
serena
2017/12/05
4.2K0
Android View和 Window 的关系
Window 机制源码分析
Window 是一个抽象的基类,表示一个窗口,包含一个View tree和layout参数。
Yif
2019/12/26
5890
自定义View(七)-View的工作原理- Activity的布局加载
前面几篇对动画可以说是做了非常全面的总结了(上篇文章最后的4种ViewGroup相关动画相信在了解基础后看些文章也不会太难理解)。在View的工作原理 这一部分我们将对View做全面深入的解析。由于本人是菜鸟,其实无法直接看源码,也都是通过书籍与文章反复阅读,然后才去看的源码。由于怕忘记写成博客。希望和我一样不了解的朋友能在自定义View中不那么迷茫。如果那里有错误大家一定指出我将不胜感激。
g小志
2018/09/11
8770
自定义View(七)-View的工作原理- Activity的布局加载
相关推荐
Android窗口管理分析(3):窗口分组及Z-order的确定总结
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验