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

文章讲述了在Launcher界面上,快速启动并销毁多个Activity导致的ANR问题,重点在于NotificationShade的隐藏时机和焦点窗口管理。通过log分析,作者发现关键在于NotificationShade的隐藏时机以及与SplashScreen相关的行为差异,这影响了焦点窗口的正确转移。
摘要由CSDN通过智能技术生成

在这里插入图片描述

Monkey跑出的Launcher ANR,场景为在Launcher界面,下拉状态栏,然后点击Notification,连续启动多个Activity,这些Activity均是启动后又快速销毁,导致的后续无焦点窗口问题。

1. log分析

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

值得关注的异常的点是,从NotificationShade上点击Notification启动“com.google.android.setupwizard/.predeferred.PreDeferredSetupWizardActivity”后,又连续启动了多个Activity,顺序分别为:

在这里插入图片描述

com.google.android.setupwizard/.predeferred.PreDeferredSetupWizardActivity

​ -> com.google.android.setupwizard/.predeferred.ConnectToWifiActivity

​ -> com.google.android.setupwizard/.WizardManagerActivity

​ -> com.google.android.setupwizard/.SetupWizardExitActivity

从log中能看到,这4个Activity均是在create之后马上就启动另外一个Activity,然后马上finish了,因此整个过程中实际上是没有任何“com.google.android.setupwizard”相关窗口添加的。并且最终的Activity是finish了整个Task从而回到了Launcher。

后续Launcher重新变为焦点Application的时候,应该是在WMS.relayoutWindow中,Launcher的相关WindowState可见性没有发生变化,从而无法去更新焦点窗口。

尝试写一个Demo去复现这个ANR,但是发现无法复现这个ANR,原因有两个:

1、我们的Demo在创建Task的时候会启动SplashScreen,这个SplashScreen会影响Launcher的可见性,后续Launcher走WMS.relayoutWindow的时候,WMS可以去更新焦点窗口,这一点可以通过修改代码去不让SplashScreen启动。

2、问题log中,看到NotificationShade在点击Notification后马上就隐藏了,而点击Demo App发起的Notification,NotificationShade过了很久才隐藏。

整个过程的流程为:

1)、点击Notification。

2)、启动多个“快速启动又快速销毁”的Activity。

3)、当这些Activity都销毁后,重新回到Launcher。

发生ANR的log:

在这里插入图片描述

能看到在点击Notification的时候,NotificationShade就走了WMS.relayoutWindow,此时才启动到第一个“com.google.android.setupwizard”的Activity。由于此时刚刚启动了“com.google.android.setupwizard”的Activity,因此Launcher的相关ActivityRecord可见性受到了影响变为了不可见,导致Launcher没有办法作为焦点窗口,因此此时Launcher没有办法作为焦点窗口,所以焦点窗口时从NotificationShade变为了null。

这个NotificationShade的WMS.relayoutWindow这么早就调用,应该是和NotificationShade的提前隐藏有关系的(NotificationShade下拉的时候去掉了FLAG_NOT_FOCUSABLE,隐藏的时候重新加上去),看问题log,从“notification_clicked”到“notification_panel_hidden”,只有52ms。

而我们本地的Demo情况为:

在这里插入图片描述

能看到点击了Notification后,NotificationShade没有马上隐藏,也就是说NotificationShade此时还没有去走relayoutWindow,而是等到四个“com.google.android.setupwizard”都启动完并且销毁后,NotificationShade才隐藏,而此时Launcher的ActivityRecord的可见性重新变成了可见,此时NotificationShade再去relayoutWindow,因此此时焦点窗口就可以从NotificationShade转移到Launcher。

因此整个过程可以总结为:

1)、点击Notification启动“com.google.android.setupwizard”的Activity,“ActivityRecord{Launcher}”变为不可见。

2)、“com.google.android.setupwizard”的所有Activity被销毁后,“ActivityRecord{Launcher}”重新变为可见。

其中NotificationShade只会relayout一次:

如果是在“com.google.android.setupwizard”的所有Activity被销毁前就执行,那么此时“ActivityRecord{Launcher}”还是不可见的,因此此时更新焦点窗口,“WindowState{Launcher}”不满足canReceiveKeys的条件,因此此时找不到任何窗口可以满足作为焦点窗口的条件,焦点窗口就从“NotificationShade”变为了null,并且后续即使“com.google.android.setupwizard”的所有Activity被销毁后Launcher满足了条件,后续也没有再去更新焦点窗口了,焦点窗口一直都是null,这也是问题log中的情况

如果是在com.google.android.setupwizard”的所有Activity被销毁后再去执行,那么Launcher就可以作为焦点窗口,焦点窗口就从“WindowState{NotificationShade}”变为WindowState{Launcher}”,正常,这就是我写的Demo App的情况。

所以重要的就是NotificationShade走WMS.relayoutWindow的时机,下一步需要看下为什么我们的Demo会和发生问题的“com.google.android.setupwizard”有这种差异,如果我们的Demo和“com.google.android.setupwizard”在这个点上行为一致,那么应该就可以复现这个ANR。

2. notification_clicked和notification_panel_hidden

上一节知道了问题的关键在于NotificationShade走WMS.relayoutWindow的时机,而点击Notification后,NotificationShade是会收起的,应该就是这个收起的动作触发了WMS.relayoutWindow,从log上看就是“notification_clicked”和“notification_panel_hidden”。

这一节分析为什么问题log中的“notification_clicked”到“notification_panel_hidden”,只有52ms,而我写的“notification_clicked”到“notification_panel_hidden”至少都有400ms。

首先看下这几个log打印的地方分别在:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

都是SystemUI那边调用过来的,因此继续在SystemUI打堆栈,主要是隐藏NotificationShade的这块:

在这里插入图片描述

这里我看到点击“USB debugging connected”这个Notification的log和问题发生时的log时一样的,因此拿这个Notification和我们的Demo App发起的Notification进行对比,该Notification在AdbNotifications中创建。

很快发现了差异点:

AdbNotifications的情况是:

在这里插入图片描述

可知,一点击相关View,就进行了折叠下拉状态栏的操作。

同时onNotificationClicked也是在这里:

在这里插入图片描述

这两个几乎是同时的。

而我们的Demo App下,折叠下拉状态栏则是在很后面的:

在这里插入图片描述

并没有说一点击Notification就进行折叠。

继续打印相关log,发现原来是点击Notification后,SystemUI会继续判断需不需要为折叠下拉状态栏这个过程加一段动画,如果不用,那么就直接隐藏,就像AdbNotifications那样,如果需要动画,那么就会等到动画结束后再去隐藏,也就是我们Demo App的情况。

区别在于:

在这里插入图片描述

这里的PendingIntent.queryIntentComponents最终调用了ComputerEngine.queryIntentActivitiesInternal,那么继续去这个方法里面加log,发现了差异:

在这里插入图片描述

在这里插入图片描述

AdbNotifications的情况下,传入的userid为-2,而Demo App传入的userId为0,因此有了差异。

在这里插入图片描述

3. 复现ANR

首先PendingIntent.getActivityAsUser是hide的api,我们只能用反射去调用这个api。

其次我在使用UserHandle.CURRENT的时候,还会报以下错:

在这里插入图片描述

缺少了android.permission.INTERACT_ACROSS_USERS_FULL或者 android.permission.INTERACT_ACROSS_USERS权限,添加了这个权限后又提示这个权限只能给系统App用:

在这里插入图片描述

后面在网上又找了找资料,发现可以使用以下命令来给App赋予权限,试了一下果然有用:

adb shell pm grant --user -2 com.example.demoapp android.permission.INTERACT_ACROSS_USERS

最终,我可以在我们的机器上复现这个ANR了。

但是还有一个问题,之前提到,最初尝试复现这个ANR的时候,其实是有两个困难的,第二个困难即点击Notification的时候让下拉状态栏能够快速收起这个问题我们已经解决了,但是第一个问题,点击Notification的时候启动新的Task会创建SplashScreen,这个我们之前是修改framework代码强制不让SplashScreen启动,但是如果我们想要在pixel上复现这个ANR,又该如何去做?

这个其实也是我的一个困惑,之前遇到过多次在Launcher界面,一个临时Activity启动又马上销毁的情况,这种情况下:

1、临时Activity启动,影响了“ActivityRecord{Launcher}”的可见性,导致“WindowState{Launcher}”不再满足WindowState.canReceiveKeys的条件,焦点窗口从“WindowState{Launcher}”转移到了null。

2、接着临时Activity又马上销毁,比如在其onCreate方法中就调用finish方法去销毁,此时这个Activity的窗口还没有添加,因此“WindowState{Launcher}”的mViewVisibility成员变量其实是没有受到影响的,仍然是可见的,View.VISIBLE。

3、临时Activity销毁后,在我复现的大部分ANR的问题中,“WindowState{Launcher}”此时都会去走WMS.relayoutWindow流程的,该流程中有机会去更新焦点窗口,但是条件是focusMayChange要为true:

在这里插入图片描述

从以上代码可知,focusMayChange要为true,需要满足以下三个条件之一:

1)、“WindowState{Launcher}”的mViewVisibility要发生改变。如果临时Activity在启动的时候,创建了SplashScreen,那么SplashScreen的窗口是会被添加到屏幕上且显示的,这会影响到WindowState{Launcher}”的可见性,也就是说在创建SplashScreen的情况下,当“WindowState{Launcher}”走WMS.relayoutWindow的时候,focusMayChange会被置为true,后续会去更新焦点窗口,让“WindowState{Launcher}”重新变为焦点窗口。但是如果没有创建SplashScreen,那么大概率,这里不会去更新焦点窗口,导致后续焦点窗口一直为null。

2)、窗口的flag中的FLAG_NOT_FOCUSABLE发生变化,这种情况一般见于NotificationShade,下拉状态栏的时候去掉这个flag,上滑隐藏的时候重新加上,对于Launcher来说一般不会。

3)、WindowState的成员变量mRelayoutCalled为false,即这个WindowState之前还没有调用过WMS.relayoutWindow方法,注意我们的情况是之前Launcher已经显示了,所以肯定也是调用过这个方法了,因此这种情况也不太会出现。

综上分析,大部分这种情况下的ANR,出现的原因就是“WindowState{Launcher}”的可见性,即WindowState.mViewVisibility,没有发生变化,导致后续的更新焦点窗口的方法没有被调用。并且如我们上面所说,如果启动了临时Activity的时候创建了SplashScreen,是可以让“WindowState{Launcher}”的可见性发生变化的,但是大部分ANR发生的情况,即使是新建了一个Task,也没有创建SplashScreen,从而导致了后续焦点窗口缺失的情况。因此问题的关键在于,为什么发生ANR的时候,新建了一个Task,却没有创建SplashScreen?如果搞清楚了这个问题,应该对我们解决这种类型的ANR很有帮助。

4 在pixel上复现ANR

在网上查询了一下去掉SplashScreen的方式,并且使用了一下果然可以:

在这里插入图片描述

为Activity设置Theme.NoDisplay的主题。

Theme.NoDisplay的定义为:

在这里插入图片描述

关键应该就是这个windowDisablePreview:

在这里插入图片描述

这个属性使用的地方在ActivityRecord.validateStartingWindowTheme:

在这里插入图片描述

如果ActivityRecord.validateStartingWindowTheme返回false,说明不希望添加启动窗口。

这里我没有再打log去跟,但是看了下调用关系,这里的prev应该是null,所以在windowDisableStarting为true的情况下,ActivityRecord.validateStartingWindowTheme就返回了null。

后续在ActivityRecord.addStartingWindow中,在以下代码处返回了:

在这里插入图片描述

从而没有添加StartingWindow。

另外正常添加StartingWindow的堆栈调用如下:

在这里插入图片描述

最终我们就可以用Demo App在pixel上复现ANR了:

在这里插入图片描述

另外和复现log中“com.google.android.setupwizard”的行为不太一样的是,我反编译了“com.google.android.setupwizard”这个apk,发现它的activity似乎不是通过设置Theme.NoDisplay的方式来不让SplashScreen显示的,具体是怎么做的还不清楚,但是应该关系不大,我们只要能够复现这种场景就行了,具体怎么实现的可以有多种方式。

最后还有一点是,点击Notification后,下拉状态栏,也即NotificationShade会收起,此时NotificationShade会有两个变化,一是为其窗口设置FLAG_NOT_FOCUSABLE这个flag,第二个是其窗口的可见性,WindowState.mViewVisibility会发生改变,任意这两项改变,都会触发WMS.relayoutWindow,且都会在WMS.relayoutWindow中触发焦点窗口的更新,即:

在这里插入图片描述

这个我们之前也分析过了。

后面经过我们在pixel上的测试,发现这两个动作是有先后的顺序的,并不是一起改变的,从复现的情况来看,FLAG_NOT_FOCUSABLE这个flag的添加要先一点。

原生机的情况为:

1、点击Notification。

2、FLAG_NOT_FOCUSABLE被添加,触发第一次NotificationShade的WMS.relayoutWindow。

3、大概过了400ms,NotificationShade从可见变为了不可见,触发了第二次NotificationShade的WMS.relayoutWindow。

那么我们如果要复现ANR,就要保证在第二次NotificationShade走WMS.relayoutWindow的时候,Launcher仍然是不可见的,即仍然有我们的Demo App的Activity在启动。

这一点导致的区别是:

像我们的手机,这两次relayoutWindow相距不到200ms,所以我连续启动4个Activity就可以保证在第二次relayoutWindow的时候Launcher是不可见的。

但是在pixel上,我要启动十几个Activity才能保证第二次relayoutWindow的时候Launcher仍然是不可见的,后续才可以复现这个ANR。

这些可能是性能方面的影响或者SystemUI有差异,但是仍然可以说明这个ANR是个原生bug。

  • 16
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WMS(Warehouse Management System)仓库管理系统是一种用于管理仓库操作和库存流动的软件系统。它可以帮助企业实现对仓库内物品的管理、库存的监控、出入库的记录、订单的处理等功能。下面是针对WMS仓库库存系统的一些常见问题的回答: 1. WMS系统有哪些主要功能模块? WMS系统的主要功能模块包括:采购管理、销售管理、仓库管理、报表查询、系统管理等。其中,采购管理模块主要负责采购计划、采购订单、采购入库等操作;销售管理模块主要负责销售计划、销售订单、销售出库等操作;仓库管理模块主要负责库存管理、出入库管理、盘点管理等操作;报表查询模块主要负责各种报表的生成和查询;系统管理模块主要负责用户管理、角色管理、权限管理等操作。 2. WMS系统如何实现对库存的监控? WMS系统可以通过仓库管理模块来实现对库存的监控。在该模块中,可以对每个仓库的库存进行管理,包括库存的入库、出库、移库、盘点等操作。同时,WMS系统还可以通过库存状况、出入库统计等报表来实现对库存的监控。 3. WMS系统如何处理订单? WMS系统可以通过采购管理模块和销售管理模块来处理订单。在采购管理模块中,可以创建采购计划、采购订单,并对采购入库进行管理;在销售管理模块中,可以创建销售计划、销售订单,并对销售出库进行管理。通过WMS系统,可以实现对订单的处理、库存的管理和出入库的记录等功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值