【问题分析】InputDispatcher无焦点窗口ANR问题【Android 14】

news2024/11/6 7:36:07

在这里插入图片描述

1 问题描述

Monkey跑出的无焦点窗口的ANR问题。

特点:

1)、上层WMS有焦点窗口,为Launcher。

2)、native层InputDispacher无焦点窗口,上层为”recents_animation_input_consumer“请求了焦点,但是”recents_animation_input_consumer“最终没有成为焦点窗口,原因是”NOT_VISIBLE“。

2 log分析

总共有两个项目报了类似的问题,log分析如下:

第一份log:

在这里插入图片描述

第2份log:

在这里插入图片描述

从log中能看到,这两份log的相同点都是在调起Recents界面的时间点的附近启动了几个“快速启动又销毁”的Activity,复现的场景比较相似,并且后续都是“recents_animation_input_consumer”取得了焦点后,又莫名的丢失了焦点:

在这里插入图片描述

在这里插入图片描述

只知道“recents_animation_input_consumer”丢失焦点的原因是“NOT_VISIBLE”,即它对应的Layer被认为是不可见的,但是具体的原因是什么呢?

3 复现ANR

这里经过多次尝试,终于根据第二份log的场景,复现了ANR:

为了模拟问题场景,这类我们总共需要启动4个Activity,并这为4个Activity设置:

android:screenOrientation="reversePortrait"

这个属性,用来模拟Monkey中出现ROTATION_180的场景。

具体场景为:

1)、MainActivity连续启动ActivityA、ActivityB、ActivityC,并且MainActivity调用finish:

        startActivity(new Intent(MainActivity.this, ActivityA.class));
        startActivity(new Intent(MainActivity.this, ActivityB.class));
        startActivity(new Intent(MainActivity.this, ActivityC.class));
        finish();

2)、接着输入KeyEvent.KEYCODE_RECENT_APPS,调起Recents界面,此时“recents_animation_input_consumer”会拿到焦点。

3)、让ActivityC在失去top resumed状态过后一段时间,调用finish:

    public void onTopResumedActivityChanged(boolean isTopResumedActivity) {
        super.onTopResumedActivityChanged(isTopResumedActivity);
        if (!isTopResumedActivity) {
            Handler handler = new Handler();
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    finish();
                }
            }, 2000);
        }
    }

4)、ActivityC销毁回到ActivityB、ActivityA后,让他们也调用finish:

    @Override
    protected void onStart() {
        super.onStart();
        finish();
    }

以上代码写好后,就可以复现ANR了,直接输入命令:

adb shell am start -n com.example.demoapp/.MainActivity && adb shell input keyevent 312

即可复现焦点从“recents_animation_input_consumer”离开的现象了:

在这里插入图片描述

再输入一个KeyEvent事件就可以触发ANR了。

同样在pixel上也能复现:

在这里插入图片描述

4 原因分析

从“NOT_VISIBLE”入手,去分析为什么“recents_animation_input_consumer”失去了焦点。

4.1 InputConsumerImpl.show

一开始最先想到的,就是没有为它的SurfaceControl在InputMonitor.UpdateInputForAllWindowsConsumer.updateInputWindows方法中调用show:

在这里插入图片描述

“recents_animation_input_consumer”会在resetInputConsumers方法中hide,然后如果下面的条件满足,就会调用show去显示。

但是分析log可以知道,此时的recents animation还没有结束,因此这里的activeRecents是不会为空的,因此应该是调用了show方法的,并且后续我们复现问题后看log也的确如此。

接下来还是只能靠多添加log去分析。

4.2 FocusResolver.isTokenFocusable

首先是FocusResolver.isTokenFocusable中:

在这里插入图片描述

能够返回Focusability::NOT_VISIBLE说明是WindowInfoHandle的WindowInfo包含了NOT_VISIBLE标志位,而WindowInfo是在SurfaceFlinger.buildWindowInfos处构建的:

在这里插入图片描述

并且能够看到,SurfaceFlinger.buildWindowInfos中调用了Layer.fillInputInfo来填充WindowInfo的信息,和本题相关的就是NOT_VISBLE这个标志的设置,是根据Layer.isVisibleForInput的结果决定是否设置该标志位的。

继续打印log,发现果然是“recents_animation_input_consumer”对应的Layer的isVisibleForInput返回了false,导致这里为其Layer添加了NOT_VISBLE标志位。

4.3 Layer.isVisibleForInput

Layer.isVisibleForInput函数的定义为:

在这里插入图片描述

Layer.hasInputInfo函数的内容为:

在这里插入图片描述

判断Layer是否设置过WindowInfo。

而再次回顾InputConsumerImpl创建并且显示的逻辑:

在这里插入图片描述

可知两点:

1)、在构造方法中创建了InputWindowHandle对象,并且在SurfaceControl显示的时候进行了InputWindowHandle的设置。

2)、在SurfaceControl显示的时候一同设置的,还有相对Layer,并且该对象没有父Layer,只有相对Layer。

因此从上面的信息我们知道,“recents_animation_input_consumer”对应的Layer的函数hasInputInfo是会返回true的,接下来需要继续分析Layer.canReceiveInput为何返回了false。

4.4 Layer.canReceiveInput和Layer.isHiddenByPolicy

在这里插入图片描述

Layer.canReceiveInput首先是判断了Layer.isHiddenByPolicy,并且从log中看到,“recents_animation_input_consumer”的Layer的isHiddenByPolicy返回了false。

看Layer.isHiddenByPolicy的内容能很明显的看到,当前Layer是否可见,实现是看其父Layer和相对Layer是否可见的,如果父Layer或者相对Layer是不可见的,那么该Layer就被直接认为是不可见的,不需要再继续看该Layer自身的设置了。

从上面的分析中我们得知,在本题中,“recents_animation_input_consumer”的Layer是没有父Layer的,但是是有相对Layer的,即在recents动画中被设置为InputMonitor.mActiveRecentsLayerRef的那个WindowContainer的Layer(目前aosp14的dev分支还是ActivityRecord,我们的代码这里是Task,我这里继续分析我们的代码),并且复现问题的时候,该Task的isHiddenByPolicy返回了true:

在这里插入图片描述

这一般就是上层WMS处为Task调用了Transaction.hide。

4.5 Task.prepareSurfaces

最后最终到是在Task.prepareSurfaces中,认定该Task不在可见,所以调用Transaction.setVisibility -> Transaction.hide来为该Task的Layer设置了hidden的标志位:

在这里插入图片描述

1)、Task是否可见,看的是其isVisible方法,由于Task没有重写isVisible方法,因此这里调用的是WindowContainer.isVisible方法。

2)、WindowContainer.isVisible的逻辑是,如果子WindowContainer中有一个是可见的,那么当前WindowContainer也会被认为是可见的。

3)、Task的子容器都是ActivityRecord,所以最终是看该Task中的ActivityRecord中有没有一个ActivityRecord的成员变量mVisible为true。

很明显,在我们的复现问题的过程中,该Task中的所有Activity都被finish了,所以没有一个ActivityRecord是可见的,因此这个Task也被认为是不可见的,那么在Task.prepareSurfaces中,就会为该Task调用Transaction.hide设置hidden标志位,这导致在SurfaceFlinger处,该Task对应的Layer调用isHiddenByPolicy将返回false,那么以该Task为相对Layer的“recents_animation_input_consumer”调用isHiddenByPolicy也只会返回false,最终为“recents_animation_input_consumer”对应的WindowInfo设置了NOT_VISIBLE标志位,在InputDispatcher中被认为是不可见,不再满足作为焦点窗口的条件。

5 一点题外话

5.1 根本原因分析

这一题和我们之前解决的问题很像,同样是在InputDispatcher处,“recents_animation_input_consumer”丢失焦点后,没有再次获得焦点,导致InputDispatcher这一侧的焦点窗口一直为null,从而出现ANR:

1)、之前的那个问题是“recents_animation_input_consumer”的相对Layer的那个Task,整个被移除掉了,所以在SurfaceFlinger处,遍历不到“recents_animation_input_consumer”,因此那个问题,焦点从“recents_animation_input_consumer”离开的时候,原因是”NO_WINDOW“。

2)、本题中,“recents_animation_input_consumer”的相对Layer的那个Task,虽然还存在,但是其中的所有ActivityRecord,都调用了finish,因此所有的ActivityRecord都是不可见的,因此这个Task也不可见,因此“recents_animation_input_consumer”也被认为是不可见,所以焦点从“recents_animation_input_consumer”离开的时候,原因是”NOT_VISIBLE“。

所以根本原因都是一致的,即:

1)、在WMS的InputMonitor处,它为“recents_animation_input_consumer”请求焦点的时候,是不在乎其相对Layer是什么状态的,只要满足InputMonitor要求的的条件,InputMonitor就为“recents_animation_input_consumer”请求焦点。

2)、在SurfaceFlinger和InputDispatcher处,它们是会关心“recents_animation_input_consumer”的相对Layer是什么状态的,如果其相对Layer不可见,甚至不再存在了,那么就不会为“recents_animation_input_consumer”请求焦点。

正是这两侧为“recents_animation_input_consumer”请求焦点的相关逻辑的区别,导致了这类ANR问题的出现。

5.2 Task没有被移除

另外还有一点疑问就是,“recents_animation_input_consumer”的相对Layer的那个Task,虽然它里面的所有Activity都调用了finish了,但是还剩一个“com.example.demoapp/.MainActivity”没有被移除,还在Task里面:

在这里插入图片描述

看到复现问题时候的log:

在这里插入图片描述

该Activity调用了finish后实际上并没有移除,而是一直在Task中,状态则是STOPPING。

直到recents动画结束后,该Activity才被移除,然后是Task也被移除:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1549877.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

国赛大纲解读

1. 第一部分,是针对5G基础知识的掌握,第二部分是人工智能基本算法的掌握,就是人工智能的应用,用5G+人工智能(AI算法)进行网络优化的问题,要有网络优化的基础知识,比如说:某个区域的覆盖问题,覆盖特别差,但有数据,覆盖电频,srp值这些数据给你,根据数据来判断是…

OCP NVME SSD规范解读-15.DSSD set feature功能要求-1

4.15.2 DSSD Set Feature Requirements章节重点描述了针对数据中心NVMe SSD(DSSD)特有的设置特性要求。在NVMe SSD规范中,通过Set Feature命令可以对SSD进行各种高级功能的设置和配置,DSSD特有的Set Feature命令集中了一些特定于D…

【Linux进阶之路】理解UDP,成为TCP。

前言 学了TCP 和UDP之后,感觉UDP就像是初入职场的年轻人,两耳不闻 “窗外事”,只管尽力地把自己的事情做好,但收获的却是不可靠,而TCP更像是涉世极深的"职场老油条",给人的感觉就是 “城府极深&a…

被迫走上前端之路第四课之vue的表单双向绑定

在进行表单处理时,我们希望JavaScript中与表单绑定的变量能随着我们表单的输入或选择而动态的发生改变,或者说二者同步。这个时候就需要实现表单的双向绑定 使用v-on和v-bind实现 这里定义了一个输入框,v-bind实现了text的值到输入框的值的绑…

QT中的服务器与客户端

一、前言 本文主要讲讲QT中服务器与客户端的使用方法,QT已经封装好了,调用相应类直接访问即可。本文以QT中的QT中的TCP为例子,讲下使用方法以及线程中使用。 二、正文 2.1 Sever的使用方法 2.1.1 思路 QT中Sever使用的时候大致步骤为&…

Python抓取抖音直播间数据:技术探索与实践

目录 一、引言 二、技术准备 三、分析抖音直播间网页结构 四、编写爬虫代码 五、处理反爬虫机制 六、数据清洗与存储 七、总结 一、引言 随着互联网的快速发展,直播行业已成为当下的热门领域。抖音作为其中的佼佼者,吸引了大量的用户和主播。对于…

集合系列(十五) -CopyOnWriteArrayList详解

一、摘要 在介绍 CopyOnWriteArrayList 之前&#xff0c;我们一起先来看看如下方法执行结果&#xff0c;代码内容如下&#xff1a; public static void main(String[] args) {List<String> list new ArrayList<String>();list.add("1");list.add(&quo…

Flutter 常用插件Plugin整理并附带实例

最近有点空闲时间&#xff0c;正好写一篇文章&#xff0c;整理一下我们在Flutter开发中常用的插件Plugin使用并附带上实例。 在日常开发中&#xff0c;整个demo目前应该满足大家所有的开发需求&#xff0c;例如&#xff1a;http请求、列表刷新及加载、列表分组、轮播图、视频播…

如何使用Python结合Pillow、matplotlib和OpenCV实现图片读取

使用Pillow库 matplotlib是一个绘图库&#xff0c;经常用于数据可视化&#xff0c;但它也可以用来展示图片。 from PIL import Image# 读取图片 image Image.open(.jpg)# 展示图片 image.show()使用OpenCV库 OpenCV是一个强大的计算机视觉和机器学习库。它不仅提供了大量的图像…

记录关于智能家居的路程的一个bug___Segmentation fault(段错误)

前言 其实发生段错误的情况有很多&#xff1a; 其实在项目的开发中最有可能的错误就是①和②&#xff0c;考虑到本项目数组用的比较少&#xff0c;所以主要是考虑错误①指针的误用。 有时候错误就是那么离谱&#xff0c;声音也算是一种设备&#xff1f;&#xff1f;&#xff…

Vue使用el-statistic和el-card显示大屏中的统计数据

​ 一、页面内容&#xff1a; <el-row :gutter"20"><el-col :span"6"><el-card class"box-card"><div><el-statisticgroup-separator",":precision"2":value"value2":title"tit…

机器人寻路算法双向A*(Bidirectional A*)算法的实现C++、Python、Matlab语言

机器人寻路算法双向A*&#xff08;Bidirectional A*&#xff09;算法的实现C、Python、Matlab语言 最近好久没更新&#xff0c;在搞华为的软件挑战赛&#xff08;软挑&#xff09;&#xff0c;好卷只能说。去年还能混进32强&#xff0c;今年就比较迷糊了&#xff0c;这东西对我…

JavaEE:网络原理——协议(应用层+传输层)

应用层 协议就是一种约定 应用层&#xff1a;对应应用程序&#xff0c;是程序员打交道最多的一层&#xff0c;调用系统提供的网络api写出的代码都是属于应用层的。应用层有很多现成的协议&#xff0c;但程序员一般用的还是自定义协议 自定义协议要约定好哪些内容&#xff1f…

【pytest、playwright】多账号同时操作

目录 方案实现思路&#xff1a; 方案一&#xff1a; 方案二&#xff1a; 方案实现思路&#xff1a; 依照上图所见&#xff0c;就知道&#xff0c;一个账号是pytest-playwright默认的环境&#xff0c;一个是 账号登录的环境 方案一&#xff1a; 直接上代码&#xff1a; imp…

Unity学习笔记 9.2D射线

下载源码 UnityPackage 1.Ray2D 让小球向右发射射线&#xff1a; Ray2D ray;void Start() {// Ray2D(起点&#xff0c;终点)ray new Ray2D(this.transform.position, Vector2.right);// Debug.DrawLine(起点&#xff0c;终点&#xff0c;颜色&#xff0c;显示时间)Debug.DrawL…

视图的作用

目录 视图的作用 创建视图 为 scott 分配创建视图的权限 查询视图 复杂视图的创建 视图更新的限制问题 更新视图中数据的部门编号&#xff08;视图的存在条件&#xff09; 限制通过视图修改数据表内容 创建只读的视图 复杂视图创建 oracle从入门到总裁:​​​​​​h…

阿里云ECS选型推荐配置

本文介绍构建Kubernetes集群时该如何选择ECS类型以及选型的注意事项。 集群规格规划 目前在创建Kubernetes集群时&#xff0c;存在着使用很多小规格ECS的现象&#xff0c;这样做有以下弊端&#xff1a; 网络问题&#xff1a;小规格Worker ECS的网络资源受限。 容量问题&…

网络链路层之(1)基础概念

网络链路层之(1)基础概念 Author: Once Day Date: 2024年3月27日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: 通信网络技术_Once-Day的博客-CSD…

Fastjson配置消息转换器(时间格式问题)

问题&#xff1a; 我们可以看见&#xff0c;日期的格式有点问题。 由于ArticleListVO类的createTime成员变量是Date类型&#xff0c;默认是由java的Jackson来处理&#xff0c;使用 ISO-8601 规范来处理日期时间格式。ISO-8601 是一种国际标准的日期时间表示法&#xff0c;例如&…

『Apisix安全篇』探索Apache APISIX身份认证插件:从基础到实战

&#x1f680;『Apisix系列文章』探索新一代微服务体系下的API管理新范式与最佳实践 【点击此跳转】 &#x1f4e3;读完这篇文章里你能收获到 &#x1f6e0;️ 了解APISIX身份认证的重要性和基本概念&#xff0c;以及如何在微服务架构中实施API安全。&#x1f511; 学习如何使…