Android开发套路# ANR(应用程序不响应)

当Android应用程序的UI线程阻塞时间过长时,会触发“应用程序不响应”(ANR)错误。如果应用程序处于前台,系统会向用户显示一个对话框,如图1所示。ANR对话框为用户提供强制退出应用程序的机会。


图1.显示给用户的ANR对话框.png

ANR是一个问题,因为应用程序的主线程(负责更新UI)不能处理用户输入的事件或绘制,从而导致用户感到沮丧。

当下列情况之一发生时,您的应用将触发ANR:
  • 当你的活动处于前台时,你的应用程序BroadcastReceiver在5秒内没有响应输入事件或(例如按键或屏幕触摸事件)。
  • 虽然在前台没有任何活动,但BroadcastReceiver在相当长的时间内还没有完成执行。
  • 如果你的应用程序遇到ANR,则可以使用本文中的指导来诊断和解决问题。

检测和诊断问题

Android提供了多种方式让您知道你的应用有问题,并帮助你诊断。如果你已经发布了你的应用程序,Android维权人员可以提醒你问题正在发生,并且有诊断工具可帮助你查找问题。

Android的重要功能

Android的重要功能可以通过Play控制台提醒你,当你的应用显示过多的ANR时,可以帮助你提高应用的性能 。Android的重要功能认为应用程序的ANR过度:

  • 至少在每日会议的0.47%中至少展示一次ANR。
  • 展品2或更多的ANR至少在其日常会议的0.24%。
  • 一个日常的会话是指在使用你的应用程序的日子。

诊断ANRs

在诊断ANR时有一些常见的模式可供选择:

  • 该应用程序正在执行缓慢的操作涉及主线程上的I / O。
  • 该应用程序在主线程上做了长时间的计算。
  • 主线程正在对另一个进程执行同步联编程序调用,而另一个进程需要很长时间才能返回。
  • 主线程被阻塞,等待一个正在另一个线程上发生的长操作的同步块。
  • 主线程与另一个线程处于死锁状态,无论是在你的进程还是通过联编程序调用。主线不仅仅是等待长时间的操作才能完成,而是陷入了僵局。

以下技术可以帮助您找出哪些原因导致您的ANR。

strict模式

使用StrictMode帮助你在开发应用程序时在主线程上发现偶然的I / O操作。你可以StrictMode在应用程序或活动级别使用。

启用背景ANR对话框

Android仅在设备的“ 开发人员”选项中显示所有ANR的情况下,才会显示ANR对话框,用于处理广播消息所需的时间太长的应用程序。出于这个原因,背景ANR对话并不总是显示给用户,但应用程序仍然可能遇到性能问题。

Traceview

您可以使用Traceview在遍历用例的同时获取正在运行的应用程序的跟踪信息,并确定主线程繁忙的地方。

使用一个跟踪文件

Android /data/anr/traces.txt在设备上的文件中遇到ANR时会存储一些跟踪信息 。你可以通过以root用户身份在设备上启动shell会话来从仿真程序获取文件,如以下命令行示例所示:

adb root
adb shell
cat /data/anr/traces.txt

你可以使用设备上的“获取错误报告开发者”选项或开发计算机上的“adb bugreport”命令来从物理设备捕获错误报告。

解决问题

在确定问题后,你可以使用本问文中的提示来解决常见问题。

主线程上的代码变慢

确定你的代码中应用程序的主线程忙于超过5秒钟的地方。在你的应用程序中查找可疑用例并尝试重现ANR。

例如,图2显示了Traceview时间线,其中主线程忙于超过5秒钟。


图2. Traceview时间线显示繁忙的主线程.png

图2显示了大多数有问题的代码发生在onClick(View)处理程序中,如以下代码示例所示:

@Override
public void onClick(View view) {
   // This task runs on the main thread.
   BubbleSort.sort(data);
}

在这种情况下,你应该将在主线程中运行的工作移至工作线程。Android框架包括可以帮助将任务移动到工作线程的类,以下代码显示了如何使用AsyncTaskhelper类来处理工作线程上的任务:

@Override
public void onClick(View view) {
   // The long-running operation is run on a worker thread
   new AsyncTask<Integer[], Integer, Long>() {
       @Override
       protected Long doInBackground(Integer[]... params) {
           BubbleSort.sort(params[0]);
       }
   }.execute(data);
}

Traceview显示大部分代码在工作线程上运行,如图3所示。主线程可用于响应用户事件。


图3. Traceview时间线显示工作线程处理的工作.png

主线程上的IO

在主线程上执行IO操作是导致主线程操作速度慢的一个常见原因,这会导致ANR。建议将所有IO操作移至工作线程。
IO操作的一些示例是网络和存储操作。

争用锁

在某些情况下,导致ANR的工作不会直接在应用程序的主线程上执行。如果工作者线程在主线程完成工作所需的资源上持有锁,则可能会发生ANR。

例如,图4显示了Traceview时间线,其中大部分工作是在工作线程上执行的。


图4.显示正在工作线程上执行的工作的Traceview时间线.png

但是,如果你的用户仍然遇到ANR,则应该查看Android设备监视器中主线程的状态。通常情况下,如果主线程已经RUNNABLE准备好更新用户界面,并且通常是响应的,那么主线程就处 但如果主线程无法恢复执行,则处于BLOCKED状态,无法响应事件。Android设备监视器上的状态显示为“ 监视”或“ 等待”,如图5所示。


图5.监视器状态中的主线程.png

以下跟踪显示了被阻止等待资源的应用程序主线程:

...
AsyncTask #2" prio=5 tid=18 Runnable
  | group="main" sCount=0 dsCount=0 obj=0x12c333a0 self=0x94c87100
  | sysTid=25287 nice=10 cgrp=default sched=0/0 handle=0x94b80920
  | state=R schedstat=( 0 0 0 ) utm=757 stm=0 core=3 HZ=100
  | stack=0x94a7e000-0x94a80000 stackSize=1038KB
  | held mutexes= "mutator lock"(shared held)
  at com.android.developer.anrsample.BubbleSort.sort(BubbleSort.java:8)
  at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:147)
  - locked <0x083105ee> (a java.lang.Boolean)
  at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:135)
  at android.os.AsyncTask$2.call(AsyncTask.java:305)
  at java.util.concurrent.FutureTask.run(FutureTask.java:237)
  at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
  at java.lang.Thread.run(Thread.java:761)
...

查看跟踪可以帮助你找到阻塞主线程的代码。下面的代码负责持有阻塞前一个跟踪中的主线程的锁:

@Override
public void onClick(View v) {
    // The worker thread holds a lock on lockedResource
   new LockTask().execute(data);

   synchronized (lockedResource) {
       // The main thread requires lockedResource here
       // but it has to wait until LockTask finishes using it.
   }
}

public class LockTask extends AsyncTask<Integer[], Integer, Long> {
   @Override
   protected Long doInBackground(Integer[]... params) {
       synchronized (lockedResource) {
           // This is a long-running operation, which makes
           // the lock last for a long time
           BubbleSort.sort(params[0]);
       }
   }
}

另一个示例是应用程序的主线程正在等待工作线程的结果,如以下代码所示:

de
public void onClick(View v) {
   WaitTask waitTask = new WaitTask();
   synchronized (waitTask) {
       try {
           waitTask.execute(data);
           // Wait for this worker thread’s notification
           waitTask.wait();
       } catch (InterruptedException e) {}
   }
}

class WaitTask extends AsyncTask<Integer[], Integer, Long> {
   @Override
   protected Long doInBackground(Integer[]... params) {
       synchronized (this) {
           BubbleSort.sort(params[0]);
           // Finished, notify the main thread
           notify();
       }
   }
}

还有一些其他的情况下,可以阻止主线程,包括使用线程Lock,Semaphore以及资源池(如数据库连接池)或其他互斥(互斥)的机制。

一般来说,你应该评估你的应用程序对资源所持有的锁,但是如果你想避免ANR,那么你应该看看为主线程所需的资源所持有的锁。

确保锁保持最少的时间,或者甚至更好,评估应用程序是否需要首先举行。如果使用锁来确定何时基于工作线程的处理来更新UI,请使用诸如onProgressUpdate()和之类的机制onPostExecute()来在工作线程和主线程之间进行通信。

死锁

当线程进入等待状态时会发生死锁,因为所需的资源由另一个线程持有,这也正在等待第一个线程占用的资源。如果应用程序的主线程在这种情况下,ANR可能会发生。

死锁在计算机科学中是一个研究得很好的现象,并且有可以用来避免死锁的死锁预防算法。

慢速广播接收机

应用程序可以通过广播接收器响应广播消息,例如启用或禁用飞行模式或更改连接状态。当应用程序花费太长时间来处理广播消息时,会发生ANR。

ANR发生在下列情况下:

  • 广播接收机还没有onReceive()在相当长的时间内完成其方法。
  • 广播接收者呼叫goAsync()并且不能呼叫finish()PendingResult对象。
  • 你的应用程序只能在onReceive()方法中执行短操作BroadcastReceiver。但是,如果你的应用程序由于广播消息而需要更复杂的处理,则应将任务推迟到IntentService

你可以使用Traceview等工具来确定你的广播接收器是否在应用的主线程上执行长时间运行的操作。例如,图6显示了在主线程上处理消息大约100秒的广播接收器的时间线。

图6.显示```BroadcastReceiver```在主线程上工作的```Traceview```时间线.png

此行为可以通过对该onReceive()方法执行长时间运行的操作来引起BroadcastReceiver,如以下示例所示:

@Override
public void onReceive(Context context, Intent intent) {
    // This is a long-running operation
    BubbleSort.sort(data);
}

在这样的情况下,建议将长时间运行的操作移动到一个IntentService,因为它使用一个工作线程执行工作。以下代码显示如何使用一个IntentService来处理长时间运行的操作:

@Override
public void onReceive(Context context, Intent intent) {
    // The task now runs on a worker thread.
    Intent intentService = new Intent(context, MyIntentService.class);
    context.startService(intentService);
}

public class MyIntentService extends IntentService {
   @Override
   protected void onHandleIntent(@Nullable Intent intent) {
       BubbleSort.sort(data);
   }
}

IntentService在工作者线程而不是主线程上执行长时间运行的操作。图7显示了推迟到Traceview时间线中的工作线程的工作。

图7. Traceview时间线显示在工作线程上处理的广播消息.png

你的广播接收器可以用goAsync()来向系统发出信号表示它需要更多时间来处理消息。但是,你应该调用finish()的PendingResult对象。以下示例显示如何调用finish()从而让系统回收广播接收器并避免ANR

final PendingResult pendingResult = goAsync();
new AsyncTask<Integer[], Integer, Long>() {
   @Override
   protected Long doInBackground(Integer[]... params) {
       // This is a long-running operation
       BubbleSort.sort(params[0]);
       pendingResult.finish();
   }
}.execute(data);

但是,如果广播在后台,将代码从慢速广播接收器移动到另一个线程并使用goAsync()将不会修复ANR。ANR超时仍然适用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容