ANR是应用开发中典型的问题类型,直译为“应用程序无响应”。
ANR说明和原因 ANR全称:Application Not Responding ,也就是应用程序无响应。
Android系统中,**ActivityManagerService(简称AMS)和 WindowManagerService(简称WMS)**会检测App的响应时间,如果App在特定时间无法相应屏幕触摸或键盘输入时间,或者特定事件没有处理完毕,就会出现ANR。
以下四个条件都可以造成ANR发生:
InputDispatching Timeout :5秒内无法响应屏幕触摸事件或键盘输入事件
BroadcastQueue Timeout :在执行前台广播(BroadcastReceiver)的onReceive()函数时10秒没有处理完成,后台为60秒。
Service Timeout :前台服务20秒内,后台服务在200秒内没有执行完毕。
ContentProvider Timeout :ContentProvider的publish在10s内没进行完。
所以,尽量避免在主线程(UI线程)中作耗时操作。耗时操作就放到子线程中去执行。
ANR的避免与检测 为了避免在开发中引入可能导致应用发生ANR的问题,除了切记不要在主线程中作耗时操作,我们也可以借助于一些工具来进行检测,从而更有效的避免ANR的引入。
StrictMode
严格模式StrictMode是Android SDK提供的一个用来检测代码中是否存在违规操作的工具类,StrictMode主要检测两大类问题。
线程策略 ThreadPolicy detectCustomSlowCalls:检测自定义耗时操作 detectDiskReads:检测是否存在磁盘读取操作 detectDiskWrites:检测是否存在磁盘写入操作 detectNetWork:检测是否存在网络操作 虚拟机策略VmPolicy detectActivityLeaks:检测是否存在Activity泄露 detectLeakedClosableObjects:检测是否存在未关闭的Closeable对象泄露 detectLeakedSqlLiteObjects:检测是否存在Sqlite对象泄露 setClassInstanceLimit:检测类实例个数是否超过限制
可以看到,ThreadPolicy可以用来检测可能催在的主线程耗时操作,需要注意的是我们只能在Debug版本中使用它,发布到市场上的版本要关闭掉。StrictMode的使用很简单,我们只需要在应用初始化的地方例如Application或者MainActivity类的onCreate方法中执行如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_login); StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyLog() .penaltyDialog() 打印logcat,当然也可以定位到dropbox,通过文件保存相应的log .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectAll() .penaltyLog() .build()); }
上面的初始化代码调用penaltyLog表示在Logcat中打印日志,调用detectAll方法表示启动所有的检测策略,我们也可以根据应用的具体要求只开启某些策略,语句如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads() .detectDiskWrites() .detectNetwork() .penaltyLog() .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects() .detectLeakedClosableObjects() .detectActivityLeaks() .penaltyLog() .build());
ANR分析方法 ANR示例
生成一个按钮跳转到ANRTestActivity,在后者的onCreate()中主线程休眠20秒:
1 2 3 4 5 6 7 8 @Override protected void onCreate (@Nullable Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_anr_test); SystemClock.sleep(20 * 1000 ); }
在进入ANRTestActivity后黑屏一段时间,大概有七八秒,终于弹出了ANR异常。
ANR分析办法一:看logcat 产生ANR后,看下Log:
可以看到logcat清晰地记录了ANR发生的时间,以及线程的tid和一句话概括原因:WaitingInMainSignalCatcherLoop,大概意思为主线程等待异常。 最后一句The application may be doing too much work on its main thread.告知可能在主线程做了太多的工作。
ANR分析办法二:看trace文件 刚才的log有第二句Wrote stack traces to ‘/data/anr/traces.txt’,说明ANR异常已经输出到traces.txt文件,使用adb命令把这个文件从手机里导出来:
cd到adb.exe所在的目录,也就是Android SDK 的platform-tools目录,例如:
1 cd D:\Android\AndroidSdk\platform-tools
此外,除了Windows的cmd以外,还可以使用AndroidStudio的Terminal来输入adb命令。
2.到指定目录后执行以下adb命令导出traces.txt文件:
1 adb pull /data/anr/traces.txt
traces.txt默认会被导出到Android SDK 的\platform-tools 目录。一般来说traces.txt文件记录的东西会比较多,分析的时候需要有针对性地去找相关记录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 ----- pid 23346 at 2017-11-07 11:33:57 ----- ----> 进程id和ANR产生时间 Cmd line: com.sky.myjavatest Build fingerprint: 'google/marlin/marlin:8.0.0/OPR3.170623.007/4286350:user/release-keys' ABI: 'arm64' Build type: optimized Zygote loaded classes=4681 post zygote classes=106 Intern table: 42675 strong; 137 weak JNI: CheckJNI is on; globals=526 (plus 22 weak) Libraries: /system/lib64/libandroid.so /system/lib64/libcompiler_ rt.so /system/lib64/libjavacrypto.so /system/lib64/libjnigraphics.so /system/lib64/libmedia_ jni.so /system/lib64/libsoundpool.so /system/lib64/libwebviewchromium_ loader.so libjavacore.so libopenjdk.so (9) Heap: 22 ... "main" prio=5 tid=1 Sleeping ----> 原因为Sleeping | group="main" sCount=1 dsCount=0 flags=1 obj=0x733d0670 self=0x74a4abea00 | sysTid=23346 nice=-10 cgrp=default sched=0/0 handle=0x74a91ab9b0 | state=S schedstat=( 391462128 82838177 354 ) utm=33 stm=4 core=3 HZ=100 | stack=0x7fe6fac000-0x7fe6fae000 stackSize=8MB | held mutexes= at java.lang.Thread.sleep(Native method) - sleeping on <0x053fd2c2> (a java.lang.Object) at java.lang.Thread.sleep(Thread.java:373) - locked <0x053fd2c2> (a java.lang.Object) at java.lang.Thread.sleep(Thread.java:314) at android.os.SystemClock.sleep(SystemClock.java:122) at com.sky.myjavatest.ANRTestActivity.onCreate(ANRTestActivity.java:20) ----> 产生ANR的包名以及具体行数 at android.app.Activity.performCreate(Activity.java:6975) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1213) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2770) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892) at android.app.ActivityThread.-wrap11(ActivityThread.java:-1) at android.app.ActivityThread$ H.handleMessage(ActivityThread.java:1593) at android.os.Handler.dispatchMessage(Handler.java:105) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6541) at java.lang.reflect.Method.invoke(Native method) at com.android.internal.os.Zygote$ MethodAndArgsCaller.run(Zygote.java:240) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
在文件中使用 ctrl + F 查找包名可以快速定位相关代码。 通过上方log可以看出相关问题:
进程id和包名:pid 23346 com.sky.myjavatest
造成ANR的原因:Sleeping
造成ANR的具体行数:ANRTestActivity.java:20类的第20行
特别注意:产生新的ANR,原来的 traces.txt 文件会被覆盖。
ANR分析办法三:java线程调用分析 通过JDK提供的命令可以帮助分析和调试Java应用,命令为:
其中pid可以通过jps命令获得,jps命令会列出当前系统中运行的所有Java虚拟机进程,比如
ANR分析办法四:DDMS分析ANR问题
使用DDMS——Update Threads工具
阅读Update Threads的输出
造成ANR的原因及解决办法 上面例子只是由于简单的主线程耗时操作造成的ANR,造成ANR的原因还有很多:
解决办法:避免死锁的出现,使用子线程来处理耗时操作或阻塞任务。尽量避免在主线程query provider、不要滥用SharePreferenceS
解决办法:文件读写或数据库操作放在子线程异步操作。
解决办法:AndroidManifest.xml文件中可以设置 android:largeHeap=”true”,以此增大App使用内存。不过不建议使用此法 ,从根本上防止内存泄漏,优化内存使用才是正道。
各大组件生命周期中也应避免耗时操作,注意BroadcastReciever的onRecieve()、后台Service和ContentProvider也不要执行太长时间的任务。
ANR源码分析 Service造成的Service Timeout Service Timeout 是位于**”ActivityManager”**线程中的AMS.MainHandler收到SERVICE_TIMEOUT_MSG消息时触发。
发送延时消息 Service 进程attach到system_server进程的过程中会调用realStartServiceLocked,紧接着mAm.mHandler.sendMessageAtTime()来发送一个延时消息,延时的时常是定义好的,如前台Service 的20秒。ActivityManager 线程中的AMS.MainHandler 收到SERVICE_TIMEOUT_MSG消息时会触发。
AS.realStartServiceLocked
ActiveServices.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private final void realStartServiceLocked (ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException { ... bumpServiceExecutingLocked(r, execInFg, "create" ); try { ... app.thread.scheduleCreateService(r, r.serviceInfo, mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo), app.repProcState); } catch (DeadObjectException e) { mAm.appDiedLocked(app); throw e; } finally { ... } }
AS.bumpServiceExecutingLocked
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private final void bumpServiceExecutingLocked (ServiceRecord r, boolean fg, String why) { ... scheduleServiceTimeoutLocked(r.app); } void scheduleServiceTimeoutLocked (ProcessRecord proc) { if (proc.executingServices.size() == 0 || proc.thread == null ) { return ; } long now = SystemClock.uptimeMillis(); Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_TIMEOUT_MSG); msg.obj = proc; mAm.mHandler.sendMessageAtTime(msg, proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT)); }
进入目标进程的主线程创建Service 经过Binder等层层调用进入目标进程的主线程 handleCreateService(CreateServiceData data)。
ActivityThread.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private void handleCreateService (CreateServiceData data) { ... java.lang.ClassLoader cl = packageInfo.getClassLoader(); Service service = (Service) cl.loadClass(data.info.name).newInstance(); ... try { ContextImpl context = ContextImpl.createAppContext(this , packageInfo); context.setOuterContext(service); Application app = packageInfo.makeApplication(false , mInstrumentation); service.attach(context, this , data.info.name, data.token, app, ActivityManagerNative.getDefault()); service.onCreate(); ActivityManagerNative.getDefault().serviceDoneExecuting( data.token, SERVICE_DONE_EXECUTING_ANON, 0 , 0 ); } catch (Exception e) { ... } }
这个方法中会创建目标服务对象,以及回调常用的Service 的onCreate()方法,紧接着通过serviceDoneExecuting()回到system_server执行取消AMS.MainHandler的延时消息。
回到system_server执行取消AMS.MainHandler的延时消息 AS.serviceDoneExecutingLocked
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private void serviceDoneExecutingLocked (ServiceRecord r, boolean inDestroying, boolean finishing) { ... if (r.executeNesting <= 0 ) { if (r.app != null ) { r.app.execServicesFg = false ; r.app.executingServices.remove(r); if (r.app.executingServices.size() == 0 ) { mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app); ... } ... }
此方法中Service逻辑处理完成则移除之前延时的消息SERVICE_TIMEOUT_MSG。如果没有执行完毕不调用这个方法,则超时后会发出SERVICE_TIMEOUT_MSG来告知ANR发生。
BroadcastReceiver造成的BroadcastQueue Timeout
BroadcastReceiver Timeout是位于”ActivityManager”线程中的BroadcastQueue.BroadcastHandler收到BROADCAST_TIMEOUT_MSG消息时触发。
处理广播函数 processNextBroadcast() 中 broadcastTimeoutLocked(false) 发送延时消息 广播处理顺序为先处理并行广播,再处理当前有序广播。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 final void processNextBroadcast (boolean fromMsg) { synchronized (mService) { ... do { r = mOrderedBroadcasts.get(0 ); int numReceivers = (r.receivers != null ) ? r.receivers.size() : 0 ; if (mService.mProcessesReady && r.dispatchTime > 0 ) { long now = SystemClock.uptimeMillis(); if ((numReceivers > 0 ) && (now > r.dispatchTime + (2 *mTimeoutPeriod*numReceivers))) { broadcastTimeoutLocked(false ); ... } } if (r.receivers == null || r.nextReceiver >= numReceivers || r.resultAbort || forceReceive) { if (r.resultTo != null ) { performReceiveLocked(r.callerApp, r.resultTo, new Intent(r.intent), r.resultCode, r.resultData, r.resultExtras, false , false , r.userId); r.resultTo = null ; } cancelBroadcastTimeoutLocked(); } } while (r == null ); ... r.receiverTime = SystemClock.uptimeMillis(); if (!mPendingBroadcastTimeoutMessage) { long timeoutTime = r.receiverTime + mTimeoutPeriod; setBroadcastTimeoutLocked(timeoutTime); } ... } }
上文的step 1. broadcastTimeoutLocked(false)函数:记录时间信息并调用函数设置发送延时消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 final void broadcastTimeoutLocked (boolean fromMsg) { ... long now = SystemClock.uptimeMillis(); if (fromMsg) { if (mService.mDidDexOpt) { 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 ; } }
上文的step 2.setBroadcastTimeoutLocked函数: 设置广播超时具体操作,同样是发送延时消息
1 2 3 4 5 6 7 final void setBroadcastTimeoutLocked (long timeoutTime) { if (! mPendingBroadcastTimeoutMessage) { Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this ); mHandler.sendMessageAtTime(msg, timeoutTime); mPendingBroadcastTimeoutMessage = true ; } }
setBroadcastTimeoutLocked(long timeoutTime)函数的参数timeoutTime是当前时间加上设定好的超时时间。 也就是上文的
1 long timeoutTime = SystemClock.uptimeMillis() + mTimeoutPeriod;
mTimeoutPeriod 也就是前台队列的10s和后台队列的60s。
1 2 3 4 5 6 7 8 9 10 11 public ActivityManagerService (Context systemContext) { ... static final int BROADCAST_FG_TIMEOUT = 10 * 1000 ; static final int BROADCAST_BG_TIMEOUT = 60 * 1000 ; ... mFgBroadcastQueue = new BroadcastQueue(this , mHandler, "foreground" , BROADCAST_FG_TIMEOUT, false ); mBgBroadcastQueue = new BroadcastQueue(this , mHandler, "background" , BROADCAST_BG_TIMEOUT, true ); ... }
cancelBroadcastTimeoutLocked :处理广播消息函数 processNextBroadcast() 中 performReceiveLocked() 处理广播消息完毕则调用 cancelBroadcastTimeoutLocked() 取消超时消息。
1 2 3 4 5 6 final void cancelBroadcastTimeoutLocked () { if (mPendingBroadcastTimeoutMessage) { mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this ); mPendingBroadcastTimeoutMessage = false ; } }
ContentProvider的ContentProvider Timeout
ContentProvider Timeout是位于”ActivityManager”线程中的AMS.MainHandler收到CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG消息时触发。
Android ANR的信息收集 无论是四大组件或者进程等只要发生ANR,最终都会调用AMS.appNotResponding()方法。