Android ANR监测诊断以及解决办法

当UI线程阻塞时间太长,应用无响应(ANR)错误便会触发。如果应用位于前台,系统还会显示给用户一个ANR对话框,让用户有机会强制关闭应用。

ANR是一个问题,因为应用中负责更新UI的主线程无法处理用户输入事件或者绘制操作,从而导致用户感到沮丧。

一般分三种情况:

  • KeyDispatchTimeout(5 seconds):主要情况。按键或者触摸事件无法在特定时间内完成。
  • BroadcastTimeout(10 seconds) :BroadcastReceiver在特定时间内无法处理完成
  • ServiceTimeout(20 seconds): Service在特定的时间内无法处理完成(所以虽然service是后台执行的,但是他是运行在UI线程的,如果处理一些耗时操作,会造成ANR)

监测和诊断问题

如果应用已经发布了,Android vitals可以向你警告ANR问题的发生。(PS:前提是要发布到Google Play Store上,国内如果不是面向海外的,可以集成友盟SDK或者腾讯Bugly等)

Android vitals

Google Play Console的Android vitals模块统计了应用的性能相关情况,包括ANR和Crash等。可以根据上面的统计来分析解决ANR和Crash问题。

诊断ANR

诊断ANR可用的常规套路:

  • 在主线程中执行IO操作
  • 在主线程执行长时间的计算
  • 主线程执行同步Binder操作访问另一个进程,该进程执行很长时间再返回
  • 非主线程持有lock,导致主线程等待lock超时
  • 主线程和另一个线程发生死锁,可以是位于当前进程或者通过Binder调用。

用下面的技巧帮助你分析到底是上面的哪种情况引起了ANR。

Strict mode

使用StrictMode帮你找到主线程哪里调用了IO操作。这个模式打开后,可以尽可能帮助你找到主线程中的磁盘访问和网络访问操作,网络访问操作是肯定需要放到子线程中执行的。而磁盘操作的话,通常都会执行很快,当然能做到子线程中最好。官方文档也说了,不要强迫修复StrictMode帮你找到的所有内容,特别是,在正常的Activity生命周期中,许多磁盘访问操作是需要的。

But don't feel compelled to fix everything that StrictMode finds. In particular, many cases of disk access are often necessary during the normal activity lifecycle

调试模式下打开,但是发布状态不允许打开。

可以在Activity或者Appliction的onCreat()方法中打开:

public void onCreate() {
     if (DEVELOPER_MODE) {
         StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                 .detectDiskReads()
                 .detectDiskWrites()
                 .detectNetwork()   // or .detectAll() for all detectable problems
                 .penaltyLog()
                 .build());
         StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                 .detectLeakedSqlLiteObjects()
                 .detectLeakedClosableObjects()
                 .penaltyLog()
                 .penaltyDeath()
                 .build());
     }
     super.onCreate();
 }

打开后台ANR弹框

设备的开发者选项中可以打开“显示所有ANR”。默认Android只对于前台应用发生ANR时弹框,打开后,后台应用发生ANR也会弹框。

TraceView

使用TraceView分析应用运行时你根据用例情况在卡顿前后捕捉的方法调用trace文件。trace文件可以通过代码调用生成,或者通过Android Studio捕捉。

Debug.startMethodTracing("hellotrace");    //开始 trace,保存文件到 "/sdcard/hellotrace.trace"
    // ...
Debug.stopMethodTracing();    //结束

使用adb命令将trace文件导出到电脑,然后放到DDMS中打开分析

adb pull /sdcard/hellotrace.trace /tmp

拉取traces文件

当ANR发生时,Android系统会将一些trace信息存储到设备的/data/anr/traces.txt文件中。你可以利用adb命令将其拉取出来分析(前提是要root?不记得了)。

对于模拟器,简单快速查看:

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

还可以用bugreport命令导出。

解决问题

主线程执行慢代码

将耗时操作异步执行。

主线程执行IO操作

建议将所有IO操作放到子线程执行。

锁争用

工作线程持有主线程需要获取某个资源的锁又不能及时释放的情况。

通常发生ANR时,主线程处于Monitor或者BLOCKED状态。例子:

@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]);
       }
   }
}

上述代码,主线程需要获取lockedResource锁,而该锁被LockTask持有,其sort方法为耗时操作,导致不能及时释放锁,从而引发主线程阻塞超时,导致ANR。

另外一个例子,主线程等待子线程执行结果超时:

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();
       }
   }
}

这些情况需要评估锁耗时,保证锁尽可能占有最少的时间。或者移除锁。

死锁

尽可能避免死锁。

执行缓慢的Broadcast Receiver

当应用花费了太长时间处理广播消息时候也会导致ANR发生。

下面的情况会导致ANR发生:

  • 广播接收者没能及时执行完成onReceive()方法(通常10s)
  • 广播接收者调用了goAsync()方法,但是没有调用PendingResult对象的finish()方法。

如果onReceive方法中要执行耗时操作,可以将任务放到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);
   }
}

或者,调用BroadcastReceiver告诉系统,我需要更多时间来处理消息。你处理完成之后,必须调用PendingResult对象的finish方法。

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,你必须在10s之内完成操作

参考资料:https://developer.android.com/topic/performance/vitals/anr.html

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

推荐阅读更多精彩内容