Android App开发之ANR异常的原因分析及处理总结

ANR的全称是application not responding,根据它的意思我们就能看出来是应用程序未响应,就像是我们在电脑上碰到的程序未响应,一般电脑出现这种情况,可能是由于我们打开了很多应用程序,占用了大量的内存,或者CPU时间片被一个应用程序长时间占用,不够分配,导致部分应用程序出现了无响应。Android和Windows基本一样,接下来我们就分析一下Android是怎么产生的这个问题。

下图就是我们见到的APP无响应的时候出现的对话框,产生ANR的原因很多,但是只有在Activity中的ANR才会弹出对话框,对话框有两个选项,一个是等待,一个是关闭程序供用户选择:

《Android App开发之ANR异常的原因分析及处理总结》 ANR

一、为什么会产生ANR(Application not response)这种情况呢?

在Android里, App的响应能力是由Activity Manager和Window Manager系统服务来监控的.,通常情况下产生ANR有这么三个条件:

1.只有主线程才会产生ANR,主线程就是UI线程;

2.必须发生某些输入事件或特定操作,比如按键或触屏等输入事件,在BroadcastReceiver或Service的各个生命周期调用函数;

3.上述事件响应超时,不同的context规定的上限时间不同

具备了以上三个条件,那么加上这两个环境,就会产生ANR了:

1.5秒内无法对输入事件(按键及触摸)做出响应

2.广播接收器无法在10秒内结束运行

二、ANR时系统做了什么

1.弹出一个对话框

2.将ANR信息输出到traces.txt文件中

traces.txt文件是一个ANR记录文件,用于开发人员调试,目录位于/data/anr中,无需root权限即可通过pull命令获取,下面的命令可以将traces.txt文件拷贝到当前目录下

adbpull /data/anr

3.将ANR信息输出到Logcat中

三、对ANR产生详细分析

为什么会产生ANR,在第一条讲解中,具体剖析(部分文章参考简书作者):

a.主线程对输入事件5秒内没有处理完毕

b.主线程在执行BroadcastReceiver的onReceive()函数时10秒内没有处理完毕

c.主线程在Service的各个生命周期函数时20秒内没有处理完毕。

那么导致ANR的根本原因是什么呢?简单的总结有以下两点:

1.主线程执行了耗时操作,比如数据库操作或网络编程

2.其他进程(就是其他程序)占用CPU导致本进程得不到CPU时间片,比如其他进程的频繁读写操作可能会导致这个问题。

细分的话,导致ANR的原因有如下几点:

1.耗时的网络访问

2.大量的数据读写

3.数据库操作

4.硬件操作(比如camera)

5.调用thread的join()方法、sleep()方法、wait()方法或者等待线程锁的时候

6.service binder的数量达到上限

7.system server中发生WatchDog ANR

8.service忙导致超时无响应

9.其他线程持有锁,导致主线程等待超时

10.其它线程终止或崩溃导致主线程一直等待。

四、ANR机制的实现原理:

文章:http://gityuan.com/2016/07/02/android-anr/从源码角度详细的分析了ANR机制实现的原理。对于上一章讲到的1-4中情况,分别找到了其源码中是如何实现的,对于每一种大概原理如下:1.在进行相关操作调用hander.sendMessageAtTime()发送一个ANR的消息,延时时间为ANR发生的时间(如前台Service是当前时间20s之后)。2.进行相关的操作3.操作结束后向remove掉该条message。如果相关的操作在规定时间没有执行完成,该条message将被handler取出并执行,就发生了ANR。

下面以BroadcastReceiver为例详细介绍:

BroadcastQueue.processNextBroadcast()

finalvoidprocessNextBroadcast(booleanfromMsg){ …synchronized(mService) { …do{if(mOrderedBroadcasts.size() ==0) { …if(mService.mProcessesReady && r.dispatchTime >0) {longnow = SystemClock.uptimeMillis();if((numReceivers >0) && (now > r.dispatchTime + (2* mTimeoutPeriod * numReceivers))) {//1.发送延时消息 broadcastTimeoutLocked(false); // forcibly finish this broadcast forceReceive = true; r.state = BroadcastRecord.IDLE; } } if (r.state != BroadcastRecord.IDLE) { if (DEBUG_BROADCAST) Slog.d(TAG, “processNextBroadcast(” + mQueueName + “) called when not idle (state=” + r.state + “)”); return; } if (r.receivers == null || r.nextReceiver >= numReceivers || r.resultAbort || forceReceive) { // No more receivers for this broadcast! Send the final // result if requested… if (r.resultTo != null) { try { //2. 处理广播消息 performReceiveLocked(r.callerApp, r.resultTo, new Intent(r.intent), r.resultCode, r.resultData, r.resultExtras, false, false, r.userId); // Set this to null so that the reference // (local and remote) isn’t kept in the mBroadcastHistory. r.resultTo = null; } catch (RemoteException e) { … } } //3.取消延时消息 cancelBroadcastTimeoutLocked(); … } } while (r == null) ; … } } }1.发送延时消息:broadcastTimeoutLocked(false):finalvoidbroadcastTimeoutLocked(booleanfromMsg){ …longnow = SystemClock.uptimeMillis();if(fromMsg) {if(mService.mDidDexOpt) {// Delay timeouts until dexopt finishes. mService.mDidDexOpt = false; long timeoutTime = SystemClock.uptimeMillis() + mTimeoutPeriod; setBroadcastTimeoutLocked(timeoutTime); return; } if (!mService.mProcessesReady) { return; } long timeoutTime = r.receiverTime + mTimeoutPeriod; if (timeoutTime > now) { setBroadcastTimeoutLocked(timeoutTime); return; } }他调用了setBroadcastTimeoutLocked(longtimeoutTime):finalvoidsetBroadcastTimeoutLocked(longtimeoutTime){if(! mPendingBroadcastTimeoutMessage) { Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG,this); mHandler.sendMessageAtTime(msg, timeoutTime); mPendingBroadcastTimeoutMessage =true; } }传入setBroadcastTimeoutLocked(longtimeoutTime)的时间xxx + mTimeoutPeriod,mTimeoutPeriod就是onRecieve()可以执行的时间,在BroadcastQueue初始化时候被赋值,前台队列为10s后台队列为60s:ActivityManagerService.java:publicActivityManagerService(Context systemContext){ …staticfinalintBROADCAST_FG_TIMEOUT =10*1000;staticfinalintBROADCAST_BG_TIMEOUT =60*1000; … mFgBroadcastQueue =newBroadcastQueue(this, mHandler,”foreground”, BROADCAST_FG_TIMEOUT,false); mBgBroadcastQueue =newBroadcastQueue(this, mHandler,”background”, BROADCAST_BG_TIMEOUT,true); … }

performReceiveLocked()为广播的实际处理,就不展开了

cancelBroadcastTimeoutLocked() :

该方法的主要工作是当service启动完成,则移除服务超时消息SERVICE_TIMEOUT_MSG。

finalvoidcancelBroadcastTimeoutLocked(){if(mPendingBroadcastTimeoutMessage) { mHandler.removeMessages(BROADCAST_TIMEOUT_MSG,this); mPendingBroadcastTimeoutMessage =false; }

五、如何避免ANR呢?

1.避免在主线程执行耗时操作,所有耗时操作应新开一个子线程完成,然后再在主线程更新UI。

2.BroadcastReceiver要执行耗时操作时应启动一个service,将耗时操作交给service来完成。

3.避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应Intent广 播时需要向用户展示什么,你应该使用Notification Manager来实现。

UI线程尽量只做跟UI相关的工作

耗时的工作()比如数据库操作,I/O,网络操作),采用单独的工作线程处理

用Handler来处理UIthread和工作thread的交互

UI线程,例如:

Activity:onCreate(), onResume(), onDestroy(), onKeyDown(), onClick(),etc

AsyncTask: onPreExecute(), onProgressUpdate(), onPostExecute(), onCancel,etc

Mainthread handler: handleMessage(), post*(runnable r), etc

ANR分析:需要关注CPU/IO,trace死锁等数据。

六、总结

ANR在我们开发APP中也比较常见,总的来说,我们需要执行耗时得操作就在子线程中执行,避免在主线程中执行。

    原文作者:啦啦哦噢啦啦冬东
    原文地址: https://www.jianshu.com/p/d34f160d77a2
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞