如何从ANR日志中获取有用信息

10

我已经几个星期在与ANRs斗争,但是像这个日志这样的信息让我感到毫无头绪。它太长了,无法在stackoverflow上发布,并且我也不知道哪一部分可能有用。

通常情况下,在初始同步时会发生这种情况,后台正在处理大量的网络请求(我几乎可以确定这些请求都不在主线程中),同时我还在通过RxJava观察SharedPreferences的大量变化并使用sample方法来处理可能的背压,例如从共享首选项中填充recyclerviews等UI操作。谢谢您的任何提示,我完全迷失了方向。


2
这个日志太难看了。我看到一半就开始流血了,CTRL+F 搜索也没有找到任何“异常”... 你部署应用程序了吗?我的应用程序崩溃了,我收到了一个崩溃日志,日志非常直白,像“Null Pointer Exception 在第20123行,你这个蠢货”,然后我修复了它 :D - Vucko
这是你的应用程序吗?cz.vcelka.androidapp? - guipivoto
@GuilhermeP 是的,那是我的软件包名称。 - Semanticer
1个回答

25
你有多个进程的线程转储。为了找到有用的部分,您可以搜索“Cmd line”,直到找到您的进程(“cz.vcelka.androidapp”,pid为21574)。
如果您遇到ANR,这意味着您的主线程被阻塞了,因此您应该查看其堆栈跟踪。下面是它:
"main" prio=5 tid=1 Waiting
  | group="main" sCount=1 dsCount=0 obj=0x74bc2fa0 self=0xb4db6500
  | sysTid=21574 nice=0 cgrp=default sched=0/0 handle=0xb6fc1b34
  | state=S schedstat=( 0 0 0 ) utm=785 stm=88 core=1 HZ=100
  | stack=0xbe29a000-0xbe29c000 stackSize=8MB
  | held mutexes=
  at java.lang.Object.wait!(Native method)
  - waiting on <0x05853836> (a java.lang.Object)
  at java.lang.Thread.parkFor$(Thread.java:1220)
  - locked <0x05853836> (a java.lang.Object)
  at sun.misc.Unsafe.park(Unsafe.java:299)
  at java.util.concurrent.locks.LockSupport.park(LockSupport.java:158)
  at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:810)
  at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:971)
  at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1278)
  at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:203)
  at android.app.SharedPreferencesImpl$EditorImpl$1.run(SharedPreferencesImpl.java:366)
  at android.app.QueuedWork.waitToFinish(QueuedWork.java:88)
  at android.app.ActivityThread.handleStopActivity(ActivityThread.java:3560)
  at android.app.ActivityThread.-wrap20(ActivityThread.java:-1)
  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1373)
  at android.os.Handler.dispatchMessage(Handler.java:102)
  at android.os.Looper.loop(Looper.java:148)
  at android.app.ActivityThread.main(ActivityThread.java:5417)
  at java.lang.reflect.Method.invoke!(Native method)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

所以你的主线程在等待 SharedPreferences 代码中的一个 CountDownLatch。我们可以查看 SharedPreferences 的源代码以了解更多信息。在某个时刻,当你调用 SharedPreferences.Editor.apply() 时,SharedPreferences 代码将写入磁盘的操作排队到工作线程中。它还调用了 QueuedWork.add(awaitCommit),其中 awaitCommit 是一个等待写操作完成的 Runnable(通过 CountDownLatch 实现),而 QueuedWork.add() 是一个方法,当活动的 onPause 方法被调用时,将工作排队在主线程上执行。这就是发生的情况:调用了 onPause,现在主线程被卡在等待工作线程完成其写入操作上了。
现在的问题是您发布的日志不完整。缺少多个线程,包括从未调用CountDownLatch.countDown()的工作线程,因此无法确定死锁的原因。如果您发布整个日志(针对您的进程,我认为其他日志不会有用),我们可能可以提供更多帮助。
编辑:我注意到这里有其他人也遇到了同样的问题。对于他们来说,工作线程在fsync(2)中被卡住了。如果文件很大和/或磁盘很忙,fsync可能会非常慢(多达几秒钟)。我想这可能会导致ANR。我不确定这是否属于SharedPreferences中的错误...即使从onPause调用,似乎在主线程上触发可能长时间阻塞的操作也有点奇怪...如果确实是您的问题,我能想到的唯一解决方法就是使用commit()而不是apply(),因为它将同步进行写入。鉴于在您的特定设置中似乎需要花费相当长的时间才能刷新到磁盘,您应该从后台线程中执行此操作!

或者,也许您的SharedPreferences文件太大了,您可以尝试缩小它(例如使用数据库)。


谢谢!听起来很有前途,我会进一步调查这个方向并回来更新。Google Play开发者控制台由于某种原因修剪了日志,有什么技巧可以获取完整的日志吗? - Semanticer
另外,我正在使用prefser库来进行sharedPreferences的写入和观察,因此存在大量的gson序列化/反序列化。 - Semanticer
2
看起来从 apply()commit() 的转变真的显著地改善了情况,因为我已经在后台线程中调用了它。虽然我仍然可以通过非常努力的尝试获取 ARN,但我猜下一个逻辑步骤现在是简化 SharedPreferences 的工作。 - Semanticer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接