记一个 Android 生命周期相关bug的解决过程
-
问题描述:
在播放器从全屏播放切换到列表播放器时会过一段时间才展示重新播放。
-
问题追踪:
通过打印视频状态,发现在全屏页返回 10s 后详情页才会有状态更新的 log。考虑是不是因为状态回调在
onDestroy()回调太晚才导致更新失败。改为在onPause()回调之后发现问题解决了。第二天……测试发现列表播放器在全屏返回之后又异常了。所以只能撤销在 1 中的改动,继续寻找解决方案。思考点在于为什么
onDestroy()会在 10s 之后才回调,谷歌之后找到了 这篇文章。(TL;DR 版本:主线程队列消息太多导致没有处理destroy()的时间,所以会走到系统兜底的 10s 后 destroy 流程)。继续问谷歌,找到了 这篇文章。用以下代码打印 looper 中的消息:
Looper.getMainLooper().setMessageLogging { Log.d("message", it) }结果是一串这样的消息:
Finished to Handler (android.view.ChoreographerFrameDisplayEventReceiver@91afab Dispatching to Handler (android.view.ChoreographerFrameDisplayEventReceiver@91afaba: 0
那么基本可以得出结论,是某个地方一直在渲染导致队列中消息太多。
将根布局替换为:
class DebugFrameLayout(context: Context, attributeSet: AttributeSet?) : FrameLayout(context, attributeSet) {
override fun requestLayout() {
Log.i(TAG, "requestLayout: ", Exception())
super.requestLayout()
}
override fun invalidateChildInParent(location: IntArray?, dirty: Rect?): ViewParent? {
Log.i(TAG, "invalidateChildInParent: ", Exception())
return super.invalidateChildInParent(location, dirty)
}
override fun onDescendantInvalidated(@NonNull child: View, @NonNull target: View) {
super.onDescendantInvalidated(child, target)
Log.i(TAG, "onDescendantInvalidated: ", Exception())
}
companion object {
private const val TAG = "FirstActivityRootLayout"
}
}
继续查看日志:
java.lang.Exception at com.*.DebugFrameLayout.invalidateChildInParent(*.kt:*) at android.view.ViewGroup.invalidateChild(ViewGroup.java:6221) at android.view.View.invalidateInternal(View.java:17779) at android.view.View.invalidate(View.java:17730) at android.view.View.invalidate(View.java:17712) at android.widget.TextView.spanChange(TextView.java:10762) at android.widget.TextViewSpannedEllipsizer.subSequence(Layout.java:2549) at com.*.draw(*.kt:*) at android.text.TextLine.handleReplacement(TextLine.java:1020) at android.text.TextLine.handleRun(TextLine.java:1167) at android.text.TextLine.drawRun(TextLine.java:500) at android.text.TextLine.draw(TextLine.java:289) at android.text.Layout.drawText(Layout.java:576) at android.text.Layout.draw(Layout.java:324) at android.widget.TextView.onDraw(TextView.java:8043)
最终定位到是 TextView 中的某个控件一直渲染,修改后问题解决。
- 问题总结:
Activity中onPause()之后的生命周期可能会间隔很久之后才会调用,如果遇到这种情况,可以通过观察messageQueue中的message来定位并解决问题。