如何判断 Activity 上是否有弹窗

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

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

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

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

如何判断 Activity 上是否有弹窗
  • 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 的存储图:

如何判断 Activity 上是否有弹窗

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 集合中的实例为止

部分代码实现如下:

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 来判断是否有弹窗的话,是存在误判的,因为这时候的两个 Activity 之间没有其他 View。

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

方案二:通过 AppToken 来判断

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

如何判断 Activity 上是否有弹窗

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

如何判断 Activity 上是否有弹窗
  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 的索引是一一对应的:

如何判断 Activity 上是否有弹窗

画个简单的图来概括下:

如何判断 Activity 上是否有弹窗

然后再结合 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,只要能命中,则说明有弹窗

部分代码实现如下:

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,调用两种方式来获取当前是否有弹窗的结果如下

如何判断 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


原文始发于微信公众号(扣浪):如何判断 Activity 上是否有弹窗

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/57318.html

(0)

及时掌握行业动态,欢迎加入几百人的后端技术交流群:


相关推荐

  • Compose要我们remember,到底怕我们忘了啥?

    导读:本篇文章讲解 Compose要我们remember,到底怕我们忘了啥?,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

    2023年2月26日
    0028
  • 千峰商城-springboot项目搭建-52-基于token实现前后端分离用户流程及用户认证的实现…

    追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

    导读:本篇文章讲解 千峰商城-springboot项目搭建-52-基于token实现前后端分离用户流程及用户认证的实现…,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

    Android 2023年3月27日
    0040
  • Android Studio代码运行正常,但是代码savedInstanceState爆红解决方式

    追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

    导读:本篇文章讲解 Android Studio代码运行正常,但是代码savedInstanceState爆红解决方式,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

    Android 2023年3月27日
    0044
  • C/C++什么是字面值常量

    命运对每个人都是一样的,不一样的是各自的努力和付出不同,付出的越多,努力的越多,得到的回报也越多,在你累的时候请看一下身边比你成功却还比你更努力的人,这样,你就会更有动力。

    导读:本篇文章讲解 C/C++什么是字面值常量,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

    Android 2023年5月10日
    0072
  • 千峰商城-springboot项目搭建-43-axios箭头函数

    追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

    导读:本篇文章讲解 千峰商城-springboot项目搭建-43-axios箭头函数,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

    Android 2023年3月27日
    0038
  • 设计模式-原型模式实例-02-深克隆

    追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

    导读:本篇文章讲解 设计模式-原型模式实例-02-深克隆,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

    Android 2023年3月27日
    0036
  • 【android开发-03】android中Intent的用法介绍

    不管现实多么惨不忍睹,都要持之以恒地相信,这只是黎明前短暂的黑暗而已。不要惶恐眼前的难关迈不过去,不要担心此刻的付出没有回报,别再花时间等待天降好运。真诚做人,努力做事!你想要的,岁月都会给你。【android开发-03】android中Intent的用法介绍,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

    Android 2023年12月13日
    0062
  • Android笔记(十二):结合Compose实现Handler机制处理多线程的通信

    如果你不相信努力和时光,那么成果就会是第一个选择辜负你的。不要去否定你自己的过去,也不要用你的过去牵扯你现在的努力和对未来的展望。不是因为拥有希望你才去努力,而是去努力了,你才有可能看到希望的光芒。Android笔记(十二):结合Compose实现Handler机制处理多线程的通信,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

    Android 2023年12月25日
    0034
  • Html与CSS基础知识篇(HTML标签语法篇)

    导读:本篇文章讲解 Html与CSS基础知识篇(HTML标签语法篇),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

    Android 2023年2月27日
    0046
  • View绘制系列(6)-Canvas其他图形绘制

    Canvas其他图形绘制 通过上篇的学习,相信同学们已经对Canvas基础图形的绘制有了了解和掌握,那么同学们有没有什么疑惑呢?上篇中是不是缺了什么? 在上篇中我们介绍了点,线,矩…

    2023年1月2日
    0057

发表回复

登录后才能评论