Activity onDestroy延迟回调

前端时间工作的时候遇到了两个奇怪的问题:

  1. 使用百度步行导航的时候,开启导航后立即退出,再次进入的时候就会黑屏;
  2. 使用度小满支付的时候,当支付成功后页面一直显示loading,过了10s左右才恢复正常。

这看似两个不相干的问题,其实问题核心原因都是同一个,那就是上一个Activity在关闭的时候,onDestory没有及时的调用,过了10s左右才回调。

onDestory为什么会延迟10s呢?
要了解中国问题,首先要知道 onDestroy是什么时候被系统回调的。经过查阅各种资料之后了解到:

在下一个要显示的Activity的回调onResume之后,ActivityThread会注册一个主线程消息队列的一个IdleHandler,用于ActivityManagerService处理Activity#onStop()和Activity#onDestroy()

这里就会有一个问题,若主线程一直在循环处理消息队列中累积的Message,则上述的IdleHandler一直得不得调用,那onStop和onDestroy岂不是永远调用不到了?

当然不会,作为一个健壮的ROM,AMS会发送一个延时10s的消息,确保正常流程行不通的情况下也能销毁Activity,从而表现上便是onDestroy()延迟了10s调用。

这个10s正好应对了我们上面的问题,10s延迟。
现在我们知道了,onDestroy为什么会延迟调用了,那么是什么原因导致了Main Handler一直出于繁忙状态呢?Main Handler里面堆积的Message到底是什么呢?
了解Handler机制的同学都知道,我们一般可以通过向Looper里添加Printer来检测页面卡顿(BlockCanary的原理),核心源码如下:
Activity onDestroy延迟回调_第1张图片
可以看到,在pinter里可以把所有的message都打印出来,这样我们就可以知道Looper里到底都是些什么东东了。
在这里插入图片描述

通过日志我们可以看到,MainLooper里是大量的系统绘制的Message。但另外一个棘手的问题又来了,首页里那么多View,我们怎么知道到底是哪个View出现的问题呢 ?

通过学习、了解View的绘制原理,我们知道,一个Activity的页面就是一个View Tree,所有View的绘制都是从ViewRootImpl开始一层层绘制的。故我们可以在Activity的根布局上自定义一个ViewGroup,重写跟View绘制相关的方法来看看能否找到线索。

于是我定义了一个DebugLayout,继承自FrameLayout,让首页的根布局改为DebugLayout,然后重写了requestLayout和invalidate方法,发现日志都是正常的,基本都是只打印了一次就不会再执行了。仔细想想也是正常的,一把RequestLayout方法是在页面布局有变化的时候才会执行,invalidate只会刷新当前View所在的区域,一搬不会导致全屏View刷新。所以重写这两个方法是不行的。

正当我一筹莫展的时候,在网上看到了一篇文章,从中得到了启示。
Invalidate里会调用invalidateChild,看看ViewGroup里的invalidateChild是如何实现的 :
Activity onDestroy延迟回调_第2张图片
支持硬件加速的设备会走onDescendantInvalidated(),否则走invalidateChildInParent(),其最终都会调用到ViewRootImpl#scheduleTraversals(),触发View的绘制流程相关逻辑。

于是,在DebugLayout里再重写invalidateChildInParent、 onDescendantInvalidated ,终于看到了相关的日志:

找到问题所在的类,发现这是一个自定义的动画View,查看它对应的onDraw方法:
Activity onDestroy延迟回调_第3张图片
一顿绘制之后,无脑invidate。很多自定义View尤其是带动画的View都会这么写。

知道了问题所以,就好解决了。

总结:
当自定义View的时候,对于onDraw方法的复写尤其要注意,一味的调用invalidate刷新View如同一个没有出口的递归,很容易出现问题。

最后,再次感谢博文:https://juejin.cn/post/6847902216620425223 解决了我的问题。

你可能感兴趣的:(android,onDestroy,延迟回调)