Android ANR的产生与分析
ANR即Application Not Responding应用无响应,一般在ANR的时候会弹出一个应用无响应对话框。也许有些开发者在使用某些手机开发中不在弹出应用无响应弹出框,特别是国产手机Android4.0以上的系统中,即使在开发者选项中设置了“显示所有应用无响应-为后台应用显示无响应ANR对话框”,主要是因为在某些国产手机系统中就将该选项屏蔽了,应用超过了一定时间无响应也不会弹出ANR对话框了。
一般情况下应用无响应的时候回产生一个日志文件,位于/data/anr/文件夹下面,trace文件是Android Davik虚拟机在收到异常终止信号时产生的,最常见的一个触发条件就是Android应用中产生了FC(force close)。由于该文件的产生是在DVM中的,所以只有运行DVM实例的进程才能产生该文件,也就是说只有Java代码才能产生该文件,App应用的Native层(如Android Library、用c/c++编译的库)即使异常也不会产生ANR日志文件。我们可以通过ANR产生的traces日志文件分析应用在哪里产生了ANR,以此来有效解决应用中的ANR。
为什么会产生ANR
在Android里,应用程序的响应是由ActivityManager和WindowManager服务系统服务监视的,当检测到下面三种情况的任何一种时,Android就会针对特定的应用程序显示ANR对话框。
- Activity的UI在5秒内没有响应输入事件(例如,按键按下,屏幕触摸)–主要类型
- BroadcastReceiver在10秒内没有执行完毕
- Service在特定时间内(20秒内)无法处理完成–小概率类型
造成ANR的原因有很多,无论是在Activity或者BroadcastReceiver还是在Service,我们看到都是在主线程中操作引起的ANR,因此我们应该避免在主线程做太多耗时的操作,网络请求不用说了,Android4.0以后就禁止在主线程成执行请求了,除此之外就是要注意如下几个方面:
- 主线程频繁进行IO操作,比如读写文件或者数据库;
- 硬件操作如进行调用照相机或者录音等操作;
- 多线程操作的死锁,导致主线程等待超时;
- 主线程操作调用join()方法、sleep()方法或者wait()方法;
- system server中发生WatchDog ANR;
- service binder的数量达到上限。
traces.txt文件分析
如果拉取traces.txt日志文件
当产生ANR的时候系统会生成一个日志文件,日志存放在/data/anr/文件夹下面,一般名称为traces.txt,但是也有例外的,如下:
如果手机已经是完全root的了,可以直接通过DDMS的File Explorer直接导出来,如果不是root的手机,可以通过如下adb命令查看ANR日志文件位于哪里。
adb shell ls /data/anr/
然后通过adb的pull将日志文件拉取到指定的路径。
adb pull /data/anr/traces.txt d:/
但是如果手机没有进行root,执行adb pull命令就会出现如下提示:
remote object ‘/data/anr/traces.txt’ does not exist
这时候我们可以使用adb将文件copy一份到sdcard,然后再拉取出来。
adbshell
cat /data/anr/traces.txt >/mnt/sdcard/traces.txt
exit
traces.txt日志文件分析
一般traces.txt日志输出格式如下,本实例是在主线程中强行Sleep导致的ANR日志:
DALVIKTHREADS :
(mutexes: tll=0 tsl=0 tscl=0 ghl=0 hwl=0 hwll=0)
"main" prio=5 tid=1 Sleeping
| group="main" sCount=1 dsCount=0 obj=0x73f11000 self=0xf3c25800
| sysTid=2957 nice=0 cgrp=default sched=0/0 handle=0xf7770ea0
| state=S schedstat=( 107710942 40533261 131 ) utm=4 stm=6 core=2 HZ=100
| stack=0xff49d000-0xff49f000 stackSize=8MB
| heldmutexes=
atjava.lang.Thread.sleep!(Native method)
- sleepingon <0x31fd6f5d> (a java.lang.Object)
atjava.lang.Thread.sleep(Thread.java:1031)
- locked <0x31fd6f5d> (a java.lang.Object)
atjava.lang.Thread.sleep(Thread.java:985)
atcom.sunny.demo.MainActivity.startMethod(MainActivity.java:21)
atjava.lang.reflect.Method.invoke!(Native method)
atjava.lang.reflect.Method.invoke(Method.java:372)
atandroid.view.View$1.onClick(View.java:4015)
atandroid.view.View.performClick(View.java:4780)
atandroid.view.View$PerformClick.run(View.java:19866)
atandroid.os.Handler.handleCallback(Handler.java:739)
atandroid.os.Handler.dispatchMessage(Handler.java:95)
atandroid.os.Looper.loop(Looper.java:135)
atandroid.app.ActivityThread.main(ActivityThread.java:5254)
atjava.lang.reflect.Method.invoke!(Native method)
atjava.lang.reflect.Method.invoke(Method.java:372)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
- 第1行是固定头,指明下面都是当前运行的dvm thread:“DALVIK THREADS”;
- 第2行输出的是改进程中各线程互斥量的值,有些手机上面可能没有这一行日志信息;
- 第3行输出的是线程名字(“main”),线程优先级(“prio=5”),线程id(“tid=1”),线程状态(Sleeping),比较常见的状态还有Native、Waiting;
- 第4行分别是线程所处的线程组 (“main”),线程被正常挂起的次处(“sCount=1”),线程因调试而挂起次数(”dsCount=0“),当前线程所关联的java线程对象(”obj=0x73f11000“)以及该线程本身的地址(“0xf3c25800”);
- 第5行 显示线程调度信息,分别是该线程在linux系统下得本地线程id (“ sysTid=2957”),线程的调度有优先级(“nice=0”),调度策略(sched=0/0),优先组属(“cgrp=default”)以及 处理函数地址(“handle=0xf7770ea0”);
- 第6行 显示更多该线程当前上下文,分别是调度状态(从 /proc/[pid]/task/[tid]/schedstat读出)(“schedstat=( 107710942 40533261 131 )”),以及该线程运行信息 ,它们是线程用户态下使用的时间值(单位是jiffies)(“utm=4”), 内核态下得调度时间值(“stm=6”),以及最后运行改线程的cup标识(“core=2”);
- 第7行表示线程栈的地址(“stack=0xff49d000-0xff49f000”)以及栈大小(“stackSize=8MB”);
- 后面是线程的调用栈信息,也是分析ANR的核心所在。
通过traces.txt中 at com.sunny.demo.MainActivity.startMethod(MainActivity.java:21) 很容易就可以定位到我们的问题所在,MainActivity的第21行,然后我们可以看到代码:
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
这就是由于主线程睡眠10s导致的无法响应。开发中定位ANR问题日志有个很简单的规律,就是 直接找到我们自己开发App所使用的包名(包括第三方Library库)信息开始定位找就可以了 。
再看另外一个实例,这是在开发中实际遇到的一个问题。
DALVIKTHREADS (23):
"main" prio=5 tid=1 Native
| group="main" sCount=1 dsCount=0 obj=0x73f11000 self=0xf3c25800
| sysTid=2929 nice=0 cgrp=default sched=0/0 handle=0xf77bbea0
| state=R schedstat=( 42455718229 4950682716 114556 ) utm=208 stm=4036 core=1 HZ=100
| stack=0xff38d000-0xff38f000 stackSize=8MB
| heldmutexes=
atandroid.database.sqlite.SQLiteConnection.nativeExecute(Native method)
atandroid.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:555)
atandroid.database.sqlite.SQLiteSession.endTransactionUnchecked(SQLiteSession.java:437)
atandroid.database.sqlite.SQLiteSession.endTransaction(SQLiteSession.java:401)
atandroid.database.sqlite.SQLiteDatabase.endTransaction(SQLiteDatabase.java:522)
atcom.xxx.xxxx.db.dao.DownloadItemDao$2.execute(DownloadItemDao.java:171)
atcom.xxx.xxxx.db.DBManager.executeTask(DBManager.java:44)
atcom.xxx.xxxx.db.dao.DownloadItemDao.updateDownload(DownloadItemDao.java:154)
atcom.xxx.xxxx.download.DownloadManager$ManagerListener.onUIProgress(DownloadManager.java:222)
atcom.sunny.net.listener.impl.UIProgressListener$UIHandler.progress(UIProgressListener.java:30)
atcom.sunny.net.listener.handler.ProgressHandler.handleMessage(ProgressHandler.java:47)
atandroid.os.Handler.dispatchMessage(Handler.java:102)
atandroid.os.Looper.loop(Looper.java:135)
atandroid.app.ActivityThread.main(ActivityThread.java:5254)
atjava.lang.reflect.Method.invoke!(Native method)
atjava.lang.reflect.Method.invoke(Method.java:372)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
先定位数据库操作处的问题,在DownloadItemDao的updateDownload()方法的154行,代码如下:
- DBManager.getInstance().executeTask(new QueryExecutor() {
- @Override
- public void execute(SQLiteDatabasedatabase) {
- //...
- }
- });
- }
然后接着定位executeTask方法,方法定义如下:
- public void executeTask(QueryExecutorexecutor) {
- SQLiteDatabasedatabase = openDB();
- executor.execute(database);
- closeDB();
- }
从这里可以看出来在下载过程中我们对数据库的操作都是在Main线程中进行的,所以我们可以再定义一个异步的方法,将DownloadItemDao的updateDownload()方法中调用改为DBManager.getInstance().executeAsyncTask()的方式,这样我们将对数据库这种比较耗时的操作子线程执行异步操作。
- public void executeAsyncTask(final QueryExecutorexecutor) {
- new Thread(new Runnable() {
- public void run() {
- SQLiteDatabasedatabase = openDB();
- executor.execute(database);
- closeDB();
- }
- }).start();
- }
接着看一下下载中的回调方式at com.sunny.net.listener.handler.ProgressHandler.handleMessage(ProgressHandler.java:47),在ProgressHandler类的47行,我们看到这里是将子线程异步下载的信息实时回调到主线程的地方:
if (!progressModel.isDone()) {
progress(uiProgessListener, progressModel.getCurrentBytes(), progressModel.getContentLength(), progressModel.isDone());
}
这里的实现方式毫无疑问是实时回调进度,在UI展现层进度可以不必实时回调,可以减少对频繁UI更新操作,从分析可以知道频繁更新UI也是导致ANR的一个很重要的原因,因此可以适当的采用一定的延时再执行UI回调,更改后代码如下:
public static final int MIN_RATE=1000;
long currTime = SystemClock.uptimeMillis();
if (currTime - lastUpdateTime >= MIN_RATE&&!progressModel.isDone()) {
lastUpdateTime = currTime;
progress(uiProgessListener, progressModel.getCurrentBytes(), progressModel.getContentLength(), progressModel.isDone());
}
这样通过ANR日志文件可以非常方面快捷的定位到问题所在,并及时的给出解决方式提高日常开发中对问题的处理效率。
如何避免ANR
- 避免在主线程进行复杂耗时的操作,特别是文件读取或者数据库操作;
- 避免频繁实时更新UI;
- BroadCastReceiver 要进行复杂操作的的时候,可以在onReceive()方法中启动一个Service来处理;
- 避免在IntentReceiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应Intent广 播时需要向用户展示什么,你应该使用Notification Manager来实现。
- 在设计及代码编写阶段避免出现出现同步/死锁或者错误处理不恰当等情况。
参考资料
Android ANR的产生与分析的更多相关文章
- 如何分析解决Android ANR
来自: http://blog.csdn.net/tjy1985/article/details/6777346 http://blog.csdn.net/tjy1985/article/detail ...
- Android ANR分析(1)
转自:http://blog.csdn.net/itachi85/article/details/6918761 一:什么是ANR ANR:Application Not Responding,即应用 ...
- android anr分析方法
目录(?)[+] 案例1关键词ContentResolver in AsyncTask onPostExecute high iowait 案例2关键词在UI线程进行网络数据的读写 一:什么是AN ...
- Android ANR分析及解决方案
一:什么是ANR ANR:Application Not Responding,即应用无响应. ANR定义:在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个 ...
- 【转】如何分析解决Android ANR
来自: http://blog.csdn.net/tjy1985/article/details/6777346 http://blog.csdn.net/tjy1985/article/detail ...
- Android ANR(应用无响应)解决分析【转】
本文转载自:https://blog.csdn.net/u014630142/article/details/81709459 来自: http://blog.csdn.net/tjy1985/art ...
- 如何分析解决Android ANR(转载)
转载自:http://blog.csdn.net/dadoneo/article/details/8270107 一:什么是ANR ANR:Application Not Responding,即应用 ...
- Android ANR从原理到日志分析,记下来就够了
站在巨人的肩膀上可以看的更远 做一个优秀的搬运工 Android 彻底理解安卓应用无响应机制 Android ANR日志分析全面解析 优秀的文章不可独享,要扩散,要做好笔记,哈 <沁园春长沙&g ...
- android ANR产生原因和解决办法【转】
ANR (Application Not Responding) ANR定义:在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(AN ...
随机推荐
- 多个SDK控制管理
需求:制作一个公共组件,可以实现多个SDK想用哪个用哪个,集中管理 组织方式: 架构形式 注意点: 1.sdk必须通过maven库来compile,因为jar会打到aar中:所以library和主mo ...
- UVA 315 Network (模板题)(无向图求割点)
<题目链接> 题目大意: 给出一个无向图,求出其中的割点数量. 解题分析: 无向图求割点模板题. 一个顶点u是割点,当且仅当满足 (1) u为树根,且u有多于一个子树. (2) u不为树根 ...
- 004.Ansible Ad-Hoc命令集
一 Ad-Hoc使用场景 Ad-Hoc更倾向于解决简单.临时性任务. 1.1 Ad-Hoc基础命令 基本语法: 1 ansible <host-pattern> [options] < ...
- Vue proxyTable 解决开发环境的跨域问题
在 config/index.js 配置文件中,添加 dev: { proxyTable: { '/ssp/withdraw': { target: 'http://dev.home.phiwifi. ...
- 2079 ACM 选课时间 背包 或 母函数
题目:http://acm.hdu.edu.cn/showproblem.php?pid=2079 题意:同样的学分 ,有多少种组合数,注意同样学分,课程没有区别 思路:两种方法 背包 母函数 背包: ...
- (文件操作)Android相关的File文件操作
判断文件是否存在: /** * 判断文件是否存在 * * @param path 文件路径 * @return [参数说明] * @return boolean [返回类型说明] */ public ...
- php数组和对象转换函数
/** * 数组 转 对象 * * @param array $arr 数组 * @return object */ function array_to_object($arr) { ...
- BZOJ4249 : Walls 防壁
首先可以将攻击位置整理成折线,答案不变. 对于一个长度为$k$的询问,若折线不超过两段,那么显然可以暴力贪心求解. 否则考虑折线中最短的一段$x\rightarrow y$,若其长度$\leq k$: ...
- parcel 中小型项目打包工具
"0配置"打包器(bundler)Parcel Parcel官网(有中文文档) webpack 要有大量的配置,这样带来的成本就是复杂性--与此相对的,Parcel 带来了简洁性. ...
- sqoop导出到hdfs
./sqoop export --connect jdbc:mysql://192.168.58.180/db --username root --password 123456 --export- ...