我们知道,在主线程进行耗时任务会造成 ANR(Application Not Responding),其原因可能有:

  • 5 秒内无法响应对输入事件(例如硬件点击或者屏幕触摸事件)
  • BroadReceiver 不能够在 10 秒内执行完接收到的任务
  • Service 在特定的时间内无法处理完成

有经验的同学都知道,网络操作、数据库读写、文件读写、大量计算等等这些耗时任务都应该做成异步的。但是,出于种种意外我们还是可能会碰到 ANR。

那我们用什么办法去应对呢?

StrictMode

StrictMode,即严格模式,可以根据我们设定的策略来检测代码中不规范的地方并给出提示,log、dialog 或者直接崩溃。具体的策略分两类,TreadPolicy and VmPolicy。

Thread Policy:

  • Custom Slow Calls,耗时方法
  • Disk Reads & Writes,磁盘读写
  • Network,网络操作
  • Resource Mismatches,资源不匹配

VM Policy:

  • Activity Leaks,
  • Class Instance Limit,类的对象数量上限
  • Leaked Sql Lite Objects,SQL 对象泄露
  • Leaked Closable Objects

更多的策略及其设置方法可以在 android.os.StrictMode 类中查看。我在 Application.onCreate() 中调用了以下方法,需要注意的是 StrictMode 最低支持 API 9.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void toggleStrictMode(boolean isDebug) {
if (!isDebug) {
return;
}
StrictMode.ThreadPolicy threadPolicy = new StrictMode.ThreadPolicy
.Builder()
.detectAll()
.penaltyLog()
.build();
StrictMode.setThreadPolicy(threadPolicy);
StrictMode.VmPolicy vmPolicy = new StrictMode.VmPolicy
.Builder()
.detectAll()
.penaltyLog()
.build();
StrictMode.setVmPolicy(vmPolicy);
}

Analyzing ANR Trace File

事实上,发生 ANR 后,系统中会生成一个文件:/data/anr/traces.txt,这个文件记录了 ANR 的一些信息,我们可以把它拿出来分析,命令如下:

1
adb pull /data/anr/traces.txt ~/Downloads/traces.txt

在这个文件靠前的位置会保留最后一次发生 ANR 时被调用的方法栈,下面的 trace 信息显示了我的应用产生 ANR 的地方是调用了 com.iamwent.gank.ui.daily.DailyActivity.chooseOneDay 这个方法,里面使用了 Thread.sleep。你不妨自己手动制造一个 ANR 然后看看这个文件的内容 ^_^

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
"main" prio=5 tid=1 Sleeping
| group="main" sCount=1 dsCount=0 obj=0x76cf0fb8 self=0x7fb3874a00
| sysTid=12199 nice=0 cgrp=default sched=0/0 handle=0x7fb78c4fc8
| state=S schedstat=( 0 0 0 ) utm=62 stm=20 core=7 HZ=100
| stack=0x7fc7eac000-0x7fc7eae000 stackSize=8MB
| held mutexes=
at java.lang.Thread.sleep!(Native method)
- sleeping on <0x056f8f79> (a java.lang.Object)
at java.lang.Thread.sleep(Thread.java:1031)
- locked <0x056f8f79> (a java.lang.Object)
at java.lang.Thread.sleep(Thread.java:985)
at com.iamwent.gank.ui.daily.DailyActivity.chooseOneDay(DailyActivity.java:177)
at com.iamwent.gank.ui.daily.DailyActivity.onOptionsItemSelected(DailyActivity.java:157)
at android.app.Activity.onMenuItemSelected(Activity.java:3204)
at android.support.v4.app.FragmentActivity.onMenuItemSelected(FragmentActivity.java:408)
at android.support.v7.app.AppCompatActivity.onMenuItemSelected(AppCompatActivity.java:195)
at android.support.v7.view.WindowCallbackWrapper.onMenuItemSelected(WindowCallbackWrapper.java:113)
at android.support.v7.view.WindowCallbackWrapper.onMenuItemSelected(WindowCallbackWrapper.java:113)
at android.support.v7.app.ToolbarActionBar$2.onMenuItemClick(ToolbarActionBar.java:69)

More

还有一点,我们可以在开发者选项中打开 显示全部 ANR,可以显示所有后台应用 ANR 的对话框。不同的手机改选项的位置和名称可能略有不同。

我们避免 ANR 某种程度上也是为了提高应用的流畅度。在官方文档中提及,用户能够察觉到卡顿的上限一般是 100ms - 200ms,这也就是说,我们对 UI 的响应时间不要超过这个范围,这也就要求我们的方法(在主线程)的执行时间不要超过这个间隔,这个就是我们要研究的另一个问题了。

Reading