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

在这里插入图片描述

用户操作出的 Launcher ANR,场景为在Launcher界面一个Activity启动又快速销毁导致的无焦点窗口问题。

1 log分析1

在这里插入图片描述

最近分析ANR问题的时候,遇到多次在Launcher界面一个Activity启动又快速销毁导致无焦点窗口从而引起ANR的现象,并且本次ANR是人为操作出来的,并非Monkey,因此需要继续根据上下文去看看问题场景,以及是否能够复现。

2 log分析2

因为无法让微信App去按照问题发生时候的情况去行为,本地通过写一个DemoApp模仿微信的App的行为,初步稳定复现了该ANR,且在pixel上也能稳定复现,先总结一下要点。

在这里插入图片描述

本地通过DemoApp复现问题的时候,发现通过点击App图标的方式启动DemoApp无法复现问题,而通过adb命令去启动DemoApp可以稳定复现,需要看下这里为什么会呈现差异,这个ANR毕竟是用户操作复现的,而非Monkey。

3 分析两种启动方式的不同点

3.1 adb命令启动Demo App

先尝试通过adb命令启动Demo App的方式去复现问题,发现当MainActivity对应的窗口挂掉后,是去更新了一次焦点窗口的,即走到了DisplayContent.updateFocusedWindowLocked,就在窗口挂掉的流程中:

在这里插入图片描述

WindowState.DeathRecipient.binderDied

-> WindowState.removeIfPossible

-> WMS.updateFocusedWindowLocked

log如下:

在这里插入图片描述

但是这次发现所有窗口都不符合作为焦点窗口的条件,所以此次没有更新成功,并且只更新了这一次,因此后续DisplayContent.mCurrentFocus就始终为空了。

3.2 点击App图标启动Demo App

如果是通过点击App图标的方式去启动Demo App,发现有两次更新窗口焦点的操作。

第一次就是窗口挂掉的流程,这一次和上面分析的一样,也是没有找到合适的窗口。

接着第二次更新的时候,找到了符合条件的窗口,即Launcher,并且将DisplayContent.mCurrentFocus设置为了Launcher:

在这里插入图片描述

首先看到这次更新是因为移除SnapshotStartingWindow窗口触发的,而通过adb命令启动Demo App的时候并没有添加SnapshotStartingWindow窗口,因此通过adb命令的方式少了一次更新焦点窗口的操作,所以复现了问题。

另外查看问题log,发现问题发生那次启动微信App的时候,是没有启动SnapshotStartingWindow的;

在这里插入图片描述

因此后续我们只要找到方法,使得通过点击App图标启动Demo App的时候,不要启动SnapshotStartingWindow,就可以复现用户操作出的那种ANR了。

4 log分析3

继续往log前面看,看下上一次启动微信App是什么时候,以及发生了什么。

在这里插入图片描述

至此,我们已经找到复现该ANR所需要的所有操作,同样Pixel也可以复现:

1、从Launcher启动Demo App,然后点击Home键回到Launcher(这里不是像问题发生的时候那样点击Back键,后面会解释),让Demo App处于后台。

2、让Demo App自己启动自己一次,此时会因为BAL_BLOCK无法启动成功(如果上一步是点击点击Back键回到Launcher,那么这里可能会成功启动且显示BAL_ALLOW_GRACE_PERIOD,如果是这样那么需要将这次启动Demo App的时间距离第一步启动Demo App的时间间隔大于10s,所以为了省事,第一步直接通过点击Home键回到Launcher,而非Back键),但是无所谓,这一步主要是为了让后续从Launcher启动Demo App的时候不会启动SnapshotStartingWindow。

3、在Launcher界面点击App图标启动Demo App,Demo App启动后需要首先finish,接着crash,此时可以复现DisplayContent.mCurrentFocus为null,即无焦点窗口的情况。

4、在无焦点窗口的情况下,输入一个KeyEvent,我们这里模仿用户输入了一个keycode为98的KeyEvent.KEYCODE_BUTTON_C,即可发生ANR:

在这里插入图片描述
在这里插入图片描述

这里为复现问题的Demo App代码:

public class MainActivity extends Activity {
    int countDown = 1;
    private Activity test = new Activity();
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (countDown == 0) {
            finish();
            test = null;
            test.finish();
        }
        if (countDown == 1) {
            Handler handler = new Handler();
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    Intent intent = new Intent(MainActivity.this, MainActivity.class);
                    startActivity(intent);
                }
            }, 5000);
        }
        countDown--;
    }
}

5 ANR原因分析

最后分析为什么会出现ANR,这里根据我本地复现的log进行分析,从窗口挂掉后,有3个时间点都是有机会更新焦点窗口的。

5.1 关键点1

首先是Demo App挂掉后,移除WindowState的流程:

在这里插入图片描述

从log能看到这里其实是去更新了一次焦点窗口的,但是此次由于Launcher对应的ActivityRecord.mVisibleRequested为false,所以不满足作为焦点窗口的条件,因此这次更新没有找到合适的窗口作为焦点窗口。

在这里插入图片描述

5.2 关键点2

接着是移除ActivityRecord的流程:

在这里插入图片描述

在这里我们才看到将launcher的ActivityRecord.mVisibleRequested可见性改为true,此流程下有一次更新焦点窗口的机会:

ActivityRecord.setVisibility

​ -> ActivityRecord.commitVisibility

​ -> WMS.updateFocusedWindowLocked

在这里插入图片描述

但是由于此时由于处于transition collecting的阶段,因此最终提前返回了,没有继续调用ActivityRecord.commitVisibility,ActivityRecord.commitVisibility中是有机会继续调用WMS.updateFocusedWindowLocked来更新焦点窗口的。

5.3 关键点3

最后是Launcher对应的WindowState走WMS.relayoutWindow:

在这里插入图片描述

本来也是有机会调用WMS.updateFocusedWindowLocked:

在这里插入图片描述

但是由于Launcher对应的窗口在relayoutWindow前后WindowState.mViewVisibility都是View.VISIBLE,原因则是WindowState.mViewVisibility改变的地方只有一处,在WindowState.setViewVisibility:

    void setViewVisibility(int viewVisibility) {
        mViewVisibility = viewVisibility;
    }

该方法只在WMS.relayoutWindow中调用:

            win.setViewVisibility(viewVisibility);
            ProtoLog.i(WM_DEBUG_SCREEN_ON,
                    "Relayout %s: oldVis=%d newVis=%d. %s", win, oldVisibility,
                            viewVisibility, new RuntimeException().fillInStackTrace());

而”com.example.demoapp.MainActivity“启动后又迅速挂掉了,没来得及改变Launcher对应的窗口WindowState.mViewVisibility,因此认为不存在焦点的切换,最后也是没有去更新焦点窗口。

从log上也能看到Launcher对应的窗口只在Demo App挂掉后relayout了两次,且每次传入的viewVisibility都是View.VISIBLE:

在这里插入图片描述

因此在Launcher的窗口走WMS.relayoutWindow流程的时候,也错失了更新焦点窗口的机会,导致后续焦点窗口一直都是空了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值