当前位置: 首页 > news >正文

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

在这里插入图片描述

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() {@Overridepublic void run() {finish();}}, 2000);}}

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

    @Overrideprotected 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也被移除:

在这里插入图片描述

相关文章:

  • 探索SOCKS5代理、代理IP、HTTP与网络安全
  • C++:sizeof关键字(7)
  • 【论文阅读】ELA: Efficient Local Attention for Deep Convolutional Neural Networks
  • Linux基础篇:解析Linux命令执行的基本原理
  • 淘宝商品采集API商品详情数据接口商品搜索列表API接口
  • 括号生成(回溯+剪枝)
  • ip地址改变导致nacos无法登录的解决方法
  • 查询优化-提升子查询-UNION类型
  • 国内IP切换软件:解锁网络世界的新钥匙
  • 【八大排序】一篇文章搞定所有排序
  • 企业系统对接必知事项-请您查收
  • vmware,linux,centos7,NAT模式下的网络配置
  • 定义类强化——移动的圆
  • Composer常见错误解决
  • “直播曝光“有哪些媒体直播分流资源?
  • JavaScript 如何正确处理 Unicode 编码问题!
  • JavaScript-如何实现克隆(clone)函数
  • CentOS从零开始部署Nodejs项目
  • java第三方包学习之lombok
  • Java新版本的开发已正式进入轨道,版本号18.3
  • JDK 6和JDK 7中的substring()方法
  • select2 取值 遍历 设置默认值
  • vue 配置sass、scss全局变量
  • vue2.0开发聊天程序(四) 完整体验一次Vue开发(下)
  • Web标准制定过程
  • 测试开发系类之接口自动化测试
  • 搞机器学习要哪些技能
  • 基于web的全景—— Pannellum小试
  • 强力优化Rancher k8s中国区的使用体验
  • 入手阿里云新服务器的部署NODE
  • 智能合约Solidity教程-事件和日志(一)
  • - 转 Ext2.0 form使用实例
  • kubernetes资源对象--ingress
  • 格斗健身潮牌24KiCK获近千万Pre-A轮融资,用户留存高达9个月 ...
  • ​水经微图Web1.5.0版即将上线
  • # 透过事物看本质的能力怎么培养?
  • #大学#套接字
  • (4)通过调用hadoop的java api实现本地文件上传到hadoop文件系统上
  • (附源码)spring boot校园拼车微信小程序 毕业设计 091617
  • (附源码)计算机毕业设计SSM教师教学质量评价系统
  • (强烈推荐)移动端音视频从零到上手(上)
  • (自用)learnOpenGL学习总结-高级OpenGL-抗锯齿
  • .NET 中 GetProcess 相关方法的性能
  • .NET/C# 判断某个类是否是泛型类型或泛型接口的子类型
  • .NET框架
  • .Net小白的大学四年,内含面经
  • @RestControllerAdvice异常统一处理类失效原因
  • [ CTF ]【天格】战队WriteUp- 2022年第三届“网鼎杯”网络安全大赛(青龙组)
  • [2010-8-30]
  • [30期] 我的学习方法
  • [c++] 什么是平凡类型,标准布局类型,POD类型,聚合体
  • [CF226E]Noble Knight's Path
  • [C进阶] 数据在内存中的存储——浮点型篇
  • [Electron]ipcMain.on和ipcMain.handle的区别
  • [ERROR]-Error: failure: repodata/filelists.xml.gz from addons: [Errno 256] No more mirrors to try.