Android-如何调查ANR?


152

有没有一种方法可以找出我的应用程序在哪里抛出了ANR(应用程序无响应)。我查看了/ data中的traces.txt文件,并看到了我的应用程序的跟踪。这就是我在跟踪中看到的。

DALVIK THREADS:
"main" prio=5 tid=3 TIMED_WAIT
  | group="main" sCount=1 dsCount=0 s=0 obj=0x400143a8
  | sysTid=691 nice=0 sched=0/0 handle=-1091117924
  at java.lang.Object.wait(Native Method)
  - waiting on <0x1cd570> (a android.os.MessageQueue)
  at java.lang.Object.wait(Object.java:195)
  at android.os.MessageQueue.next(MessageQueue.java:144)
  at android.os.Looper.loop(Looper.java:110)
  at android.app.ActivityThread.main(ActivityThread.java:3742)
  at java.lang.reflect.Method.invokeNative(Native Method)
  at java.lang.reflect.Method.invoke(Method.java:515)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)
  at dalvik.system.NativeStart.main(Native Method)

"Binder Thread #3" prio=5 tid=15 NATIVE
  | group="main" sCount=1 dsCount=0 s=0 obj=0x434e7758
  | sysTid=734 nice=0 sched=0/0 handle=1733632
  at dalvik.system.NativeStart.run(Native Method)

"Binder Thread #2" prio=5 tid=13 NATIVE
  | group="main" sCount=1 dsCount=0 s=0 obj=0x433af808
  | sysTid=696 nice=0 sched=0/0 handle=1369840
  at dalvik.system.NativeStart.run(Native Method)

"Binder Thread #1" prio=5 tid=11 NATIVE
  | group="main" sCount=1 dsCount=0 s=0 obj=0x433aca10
  | sysTid=695 nice=0 sched=0/0 handle=1367448
  at dalvik.system.NativeStart.run(Native Method)

"JDWP" daemon prio=5 tid=9 VMWAIT
  | group="system" sCount=1 dsCount=0 s=0 obj=0x433ac2a0
  | sysTid=694 nice=0 sched=0/0 handle=1367136
  at dalvik.system.NativeStart.run(Native Method)

"Signal Catcher" daemon prio=5 tid=7 RUNNABLE
  | group="system" sCount=0 dsCount=0 s=0 obj=0x433ac1e8
  | sysTid=693 nice=0 sched=0/0 handle=1366712
  at dalvik.system.NativeStart.run(Native Method)

"HeapWorker" daemon prio=5 tid=5 VMWAIT
  | group="system" sCount=1 dsCount=0 s=0 obj=0x4253ef88
  | sysTid=692 nice=0 sched=0/0 handle=1366472
  at dalvik.system.NativeStart.run(Native Method)

----- end 691 -----

我如何找出问题所在?跟踪中的方法都是SDK方法。

谢谢。


2
我有一份这样的报告,也发生在android.os.MessageQueue.nativePollOnce(Native Method)。我可以放心地忽略它吗?
rds 2012年

Answers:


124

在“主”线程中进行一些长时间的操作时,会发生ANR。这是事件循环线程,如果繁忙,则Android无法处理应用程序中的任何其他GUI事件,从而引发ANR对话框。

现在,在您发布的跟踪中,主线程似乎运行良好,没有问题。它在MessageQueue中处于空闲状态,等待其他消息进入。在您的情况下,ANR可能是一个较长的操作,而不是永久性地阻塞线程的操作,因此事件线程在操作完成后恢复,并且您的跟踪得以通过在ANR之后。

如果它是一个永久性的块(例如,死锁获取了一些锁),则检测ANR发生的位置很容易,但如果只是一个暂时的延迟,则很难检测。首先,检查您的代码,寻找脆弱的地方和长期运行的操作。示例可能包括使用事件线程内部的套接字,锁,线程睡眠和其他阻止操作。您应该确保所有这些操作都在单独的线程中发生。如果似乎没有问题,请使用DDMS并启用线程视图。这显示了应用程序中的所有线程,类似于您拥有的跟踪。再现ANR,并同时刷新主线程。那应该准确地告诉您ANR发生了什么


6
唯一的问题是“复制ANR” :-)。您能解释一下堆栈跟踪显示的主线程如何“空闲”吗,那会很棒。
布隆德尔

20
堆栈跟踪显示主线程位于Looper(消息循环实现)中,并通过Object.wait进行定时等待。这意味着消息循环当前没有任何消息要分发,并且正在等待新消息进入。当系统意识到消息循环将大量时间用于处理消息,而不处理消息中的其他消息时,就会发生ANR。队列。如果循环正在等待消息,则显然不会发生。
最早

3
@Soonil您好,您知道本节其余部分的含义是什么,例如Binder线程3,Binder线程2 JDWP恶魔prio5。sCount,dsCount,obj,sysTid是什么,sched是好的意思。它还具有VMWAIT,RUNNABLE,NATIVE等信息-minhaz 2012
19:39

1
我的应用程序基于NDK,我看到相同的ANR。另外,主线程很好。我尝试了DDMS,并在冻结时刷新了工作线程。不幸的是,我得到的只是一行NativeStart :: run。DDMS线程视图是否甚至可以检查本机NDK线程?另外:StrictMode什么也没发现。
Bram 2012年

6
有关输出的详细说明,请参见elliotth.blogspot.com/2012/08/…
2013年

96

您可以在API级别9和更高级别中启用StrictMode

StrictMode最常用于在应用程序的主线程上捕获意外的磁盘或网络访问,在该线程上接收UI操作并进行动画处理。通过使应用程序的主线程保持响应状态,还可以防止向用户显示ANR对话框

public void onCreate() {
    StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                           .detectAll()
                           .penaltyLog()
                           .penaltyDeath()
                           .build());
    super.onCreate();
}

使用时,penaltyLog()您可以在使用应用程序查看违规情况时观看adb logcat的输出。


StrictMode无法解析为类型。我需要先导入什么吗?按Ctrl + Shift + O无效。
kuchi 2012年

23
小技巧-使用if(BuildConfig.DEBUG)...以防止包含在生产中
Amir Uval

@uval“防止在生产中包含”是什么意思?!
Muhammed Refaat

2
@MuhammedRefaat它不会阻止任何ANR。它将立即使该应用程序崩溃,而不是5秒后崩溃。例如,如果您在主线程上访问数据库并且花费2秒钟,则不会收到ANR,但是StrictMode将使应用程序崩溃。StrictMode严格用于调试阶段,而不是生产阶段。
阿米尔·乌瓦尔2015年

1
@MuhammedRefaat添加了我对您问题的回复。
阿米尔·乌瓦尔2015年

80

您想知道哪个任务包含一个UI线程。跟踪文件为您提供了查找任务的提示。您需要调查每个线程的状态

线程状态

  • 运行-执行应用程序代码
  • 睡眠-称为Thread.sleep()
  • 监视器-等待获取监视器锁定
  • 等待-在Object.wait()中
  • 本机-执行本机代码
  • vmwait-等待VM资源
  • 僵尸-线程正在死亡
  • init-线程正在初始化(您不应看到此信息)
  • starting-线程即将开始(您也不应该看到此内容)

专注于“暂停”,“监视”状态。监视器状态指示正在调查哪个线程,并且线程的“挂起”状态可能是死锁的主要原因。

基本调查步骤

  1. 找到“等待锁定”
    • 您可以找到监视器状态“活页夹线程#15” prio = 5 tid = 75 MONITOR
    • 如果发现“等待锁定”,您将很幸运
    • 示例:等待锁定threadid = 74持有的<0xblahblah>(com.foo.A)
  2. 您会注意到“ tid = 74”现在正在执行任务。所以去tid = 74
  3. tid = 74可能是挂起状态!找到主要原因!

跟踪并不总是包含“等待锁定”。在这种情况下,很难找到主要原因。


1
很好的解释。现在,对我而言,更容易理解ANR日志。但是我仍然有一个原因要理解,因为在步骤1中我可以轻松找到线程ID,但是在步骤2中,当我尝试去哪里时,要检查状态,我找不到它。知道如何进行吗?
THZ

1
我在- waiting to lock an unknown object里面"HeapTaskDaemon" daemon prio=5 tid=8 Blocked 。有人可以帮助什么意思?
希拉尔

13

在过去的几个月里,我一直在学习android,所以我离专家还很远,但是我对ANR的文档感到非常失望。

大多数建议似乎都旨在避免盲目浏览您的代码来避免或修复它们,这很棒,但是我在分析跟踪时找不到任何东西。

使用ANR日志,您真正需要寻找三件事。

1)死锁:当线程处于WAIT状态时,您可以浏览详细信息以查找谁是“ holdby =”。在大多数情况下,它会自己保存,但是如果它被另一个线程保存,则很可能是危险的信号。去看看那个线程,看看它持有什么。您可能会发现一个循环,这清楚地表明出了点问题。这是非常罕见的,但这是第一点,因为当它发生时,这是一场噩梦

2)主线程等待中:如果您的主线程处于WAIT状态,请检查它是否被另一个线程持有。这不应该发生,因为您的UI线程不应由后台线程持有。

这两种情况都意味着您需要大量重新编写代码。

3)主线程上繁重的操作:这是ANR的最常见原因,但有时是更难找到和修复的原因之一。查看主线程详细信息。向下滚动堆栈跟踪,直到看到(从您的应用程序中)识别出的类。查看跟踪中的方法,找出是否在这些地方进行网络调用,数据库调用等。

最后,我为无耻地插入自己的代码表示歉意,您可以使用我在https://github.com/HarshEvilGeek/Android-Log-Analyzer上编写的python日志分析器。这将遍历您的日志文件,打开ANR文件,查找死锁,查找等待的主线程,在代理日志中查找未捕获的异常,并以相对易于阅读的方式将其全部打印在屏幕上。阅读自述文件(我将要添加)以了解如何使用它。上周帮助了我很多!


4

每当您分析时序问题时,调试通常都无济于事,因为将应用程序冻结在断点将使问题消除。

最好的选择是将许多日志记录调用(Log.XXX())插入应用程序的不同线程和回调中,并查看延迟在哪里。如果需要堆栈跟踪,请创建一个新的Exception(只需实例化一个)并记录它。


2
感谢您在需要堆栈跟踪时创建新异常的建议。这在调试时非常有帮助:)
kuchi 2012年

3

是什么触发ANR?

通常,如果应用程序无法响应用户输入,则系统会显示ANR。

在任何情况下,您的应用程序都可能执行冗长的操作,您不应在UI线程上执行工作,而应创建工作线程并在其中执行大部分工作。这样可以保持UI线程(驱动用户界面事件循环)运行,并防止系统断定您的代码已冻结。

如何避免ANR

Android应用程序通常默认情况下完全在单个线程(“ UI线程”或“主线程”)上运行。这意味着您的应用程序在UI线程中需要花费很长时间才能完成的所有操作都可能触发ANR对话框,因为您的应用程序没有给自己提供处理输入事件或意图广播的机会。

因此,在UI线程中运行的任何方法都应在该线程上做尽可能少的工作。特别是,活动应尽可能少地在关键生命周期方法(例如onCreate()和onResume())中进行设置。潜在的长时间运行的操作(例如网络或数据库操作)或计算量大的计算(例如调整位图的大小)应在工作线程中进行(或在数据库操作的情况下,通过异步请求进行)。

代码:具有AsyncTask类的辅助线程

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
    // Do the long-running work in here
    protected Long doInBackground(URL... urls) {
        int count = urls.length;
        long totalSize = 0;
        for (int i = 0; i < count; i++) {
            totalSize += Downloader.downloadFile(urls[i]);
            publishProgress((int) ((i / (float) count) * 100));
            // Escape early if cancel() is called
            if (isCancelled()) break;
        }
        return totalSize;
    }

    // This is called each time you call publishProgress()
    protected void onProgressUpdate(Integer... progress) {
        setProgressPercent(progress[0]);
    }

    // This is called when doInBackground() is finished
    protected void onPostExecute(Long result) {
        showNotification("Downloaded " + result + " bytes");
    }
}

代码:执行工作线程

要执行此工作线程,只需创建一个实例并调用execute()即可:

new DownloadFilesTask().execute(url1, url2, url3);

资源

http://developer.android.com/training/articles/perf-anr.html


1

我的ANR问题,经过大量工作,我发现一个线程正在调用布局中不存在的资源,而不是返回异常,而是得到了ANR ...


这是非常奇怪的
Nilabja


0

基于@Horyun Lee的答案,我编写了一个小python 脚本来帮助调查来自的ANR traces.txt

graphviz如果已grapvhviz在系统上安装,则ANR将作为图形输出。

$ ./anr.py --format png ./traces.txt

如果在file中检测到ANR,则png输出如下所示traces.txt。更直观。

在此处输入图片说明

traces.txt上面使用的示例文件是从这里获取的。


0

考虑使用ANR-Watchdog库来高度详细地准确跟踪和捕获ANR堆栈跟踪。然后,您可以将它们发送到崩溃报告库。我建议setReportMainThreadOnly()在这种情况下使用。您可以使应用抛出冻结点的非致命异常,也可以在ANR发生时使应用强制退出。

请注意,发送到您的Google Play开发者控制台的标准ANR报告通常不够准确,无法查明确切的问题。这就是为什么需要第三方库的原因。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.