在我们平时开发的过程中,常常测试的同事会告诉我们在运行的时候偶现ANR错误或者monkey测试时,出现了ANR的错误,今天就从原理到真实的案例一步一步的教大家如何去分析ANR相关的问题,希望大家在下次遇到ANR问题时,可以更加从容不迫。
知识点汇总:
一:ANR出现的简述与原理
二:ANR的类型与日志输出
三:ANR错误类别汇总
四:ANR实例讲解(相册)
五:ANR分析总结
六:扩展阅读
一:ANR出现的简述与原理
描述:ANR(Application Not responding),是指应用程序未响应,Android系统对于一些事件需要在一定的时间范围内完成,如果超过预定时间能未能得到有效响应或者响应时间过长,都会造成ANR。一般地,这时往往会弹出一个提示框,告知用户当前xxx未响应,用户可选择继续等待或者Force Close。
在ANR触发的详细原理,可以最后参考网址仔细了解,在参考网址中,四大组件的触发原理都略有不同,其中Activity的触发原理与其他三者区别比较大,下面大概说一下ANR触发的原理。
在说ANR的原理前,普及一下系统相关的一些知识,在我们程序运行的过程中,有一个系统服务进程(systemService)一直默默的为我们服务,而有些系统服务我们是可以使用的,例如:调用接口getSystemService,可以使用一些系统提供给我们的服务,但有些系统服务我们是无法直接使用的,但是却在我们程序运行的时候为我们服务,并起到了非常重要的效果,例如几个核心系统服务:AMS(activityManagerService),PMS(packageManagerService),WMS(windowManagerService),而触发ANR的函数就在AMS系统服务里面,叫appnotresponding()函数。
ANR的触发原理:
ANR是一套监控Android应用响应是否及时的机制,可以把发生ANR比作是引爆炸弹,那么整个流程包含三部分组成:
埋定时炸弹:中控系统(system_server进程)启动倒计时,在规定时间内如果目标(应用进程)没有干完所有的活,则中控系统会定向炸毁(杀进程)目标。
拆炸弹:在规定的时间内干完工地的所有活,并及时向中控系统报告完成,请求解除定时炸弹,则幸免于难。
引爆炸弹:中控系统立即封装现场,抓取快照,搜集目标执行慢的罪证(traces),便于后续的案件侦破(调试分析),最后是炸毁目标。
下面以Service为例子来简单介绍一下ANR的原理:
service超时机制:
从上面的图我们可以看到,绿色的进程即为系统服务进程,而ANR对话框的触发条件就是在系统进程AMS的appNotResponsing中触发的,而蓝色的进程运行的程序则为我们自己的进程,这里我用比较通俗的话来给大家解释一下这个过程。
把绿色进程比做:老板,蓝色进程比做:工人
老板在早上起来,先在自己身上放一个20秒(service 超时是20s)的定时炸弹,让后发送消息告诉工人要干活了,并且要在规定的时间内干完活,并发送回老板干完活了,告诉老板可以拆炸弹了,这时老板就可以拆炸弹了,所以如果从老板埋炸弹开始,到收到拆炸弹过程,如果有任何一个环节出了问题,都有可能导致炸弹爆炸,即发送ANR(工人未能按时完成工作只是其中一种场景)
下面我列出有可能会导致ANR的原因:
一:发送消息到Service进程时,消息阻塞了,导致ANR。
二:Service进程任务处理时间超时,导致ANR。(最常见)
三:Service进程访问内存对象时,对象被其他线程阻塞,导致ANR。
四:Service进程执行任务时,主线程的时间片被其他线程抢占(GC线程),导致处理时间超时,导致ANR。
五:Service进程发送拆炸弹消息阻塞,导致系统进程延迟收到消息,导致ANR。
二:ANR的类型与日志输出
ANR分为四种类型,分别如下:
一:Service Timeout:比如前台服务在20s内未执行完成。
二:BroadcastQueue Timeout:比如前台广播在10s内未执行完成。
三:ContentProvider Timeout:内容提供者,在publish过超时10s。
四:InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件。(KeyDispatchTimeout)。
ANR超时阈值:
不同组件的超时阈值各有不同,关于service、broadcast、contentprovider以及input的超时阈值如下表:
这时候大家可能会觉得有点奇怪,为什么ANR会有后台ANR,其实如果后台进程一直被阻塞或者主线程一直做耗时操作,也有可能会导致后台ANR的,那下面我们就区分一下,什么是后台service和后台broadcase吧。
进程优先级(Adj):(前后台服务ANR区分)
描述:进程调度组大体可分为TOP、前台、后台,其对应关系可粗略理解为Adj等于0的进程属于Top进程组,Adj等于100或者200的进程属于前台进程组,Adj大于200的进程属于后台进程组,前台服务准确来说,是指由处于前台进程调度组的进程发起的服务。
后台广播ANR区分:根据发送广播sendBroadcast(Intent intent)中的intent的flags是否包含FLAG_RECEIVER_FOREGROUND来决定把该广播是放入前台广播队列或者后台广播队列,前台广播队列的超时为10s,后台广播队列的超时为60s,默认情况下广播是放入后台广播队列,除非指明加上FLAG_RECEIVER_FOREGROUND标识。
三:ANR的日志输出
对于service、broadcast、provider、input发生ANR后,中控系统会马上去抓取现场的信息,用于调试分析。收集的信息包括如下:
1、将am_anr信息输出到EventLog,也就是说ANR触发的时间点最接近的就是EventLog中输出的am_anr信息。
2、收集以下重要进程的各个线程调用栈trace信息,保存在data/anr/traces.txt文件 。
2.1、当前发生ANR的进程,system_server进程以及所有persistent进程
2.2、audioserver, cameraserver, mediaserver, surfaceflinger等重要的native进程
2.3、CPU使用率排名前5的进程
3、将发生ANR的reason以及CPU使用情况信息输出到main log。
4、将traces文件和CPU使用情况信息保存到dropbox,即data/system/dropbox目录。
5、对用户可感知的进程则弹出ANR对话框告知用户,对用户不可感知的进程发生ANR则直接杀掉。
注意事项:
获取日志有一点需要注意,发生ANR后,不要选择结束进程,因为这样AMS会kill掉该进程,有些信息会打印不出来(比如MTK平台上会生成db.XX.ANR,写入到aee_exp文件夹下需要时间),最好是ANR发生后等两三分钟左右,再获取日志。
一般需要data/anr下生成的trace文件以及手机系统日志。
四:ANR错误类别汇总
类别一:主线程Binder调用等待超时
解析:很明显当时在做Binder通信,并没有waiting to lock等代表死锁的字样,那么说明这个案例即有可能是在等Binder对端响应,我们知道Binder通信对于发起方来说是默认是阻塞等待响应,只有有了返回结果后才会继续执行下去。
这个进程当时在做什么,这时候就需要找到anr文件夹下另外一个文件binderinfo,这里需要找到与我们发起方进程1461通信的是哪个进程。
可以看到是1666号这个进程,再回到trace中看下,这个进程当时在做什么
解析:可以看到当时对端在做消息的读取,也就是说这里出了问题,很明显这里我们无法修改,我们这个问题在于主线程执行了Binder请求,对端迟迟未返回便很容易出现这个问题,当前做法异步中执行。
类别二:主线程等待锁
解析:这个案例中gallery的main thread在执行UploaderChimeraService的onDestroy方法时,需要lock 0x23f65d8b,但这个lock有被upload_periodic GCM Task 拿住,这个thread当前是在做连接网络的动作。从这段信息来看,很有可能与测试时手机连接的网络有关,当时连接的事google的网络,由于墙的原因,无法连接gms的相关server有关
还有一种情况就是死锁,即形成了头尾相连,互相等待的情况,对于这种问题以及上面案例的解决,一般会尝试将锁改为超时锁,比如lock的trylock,超时会自动释放锁,从而避免一直持有锁的情况发生。
类别三:卡在IO上
这种情况一般是和文件操作相关,判断是否是这种情况,可以看main log中搜索关键字“ANR in”,看这段信息的最下边,比如下面的信息:
ANRManager: 100% TOTAL: 2% user + 2.1% kernel + 95% iowait + 0.1% softirq
很明显,IO占比很高,这个时候就需要查看trace日志看当时的callstack,或者在这段ANR点往前看0~4s,看看当时做的什么文件操作,这种场景有遇到过,常见解决方法是对耗时文件操作采取异步操作。
类别四:主线程有耗时的动作
这种情况是ANR类型问题里遇到最多的,比如网络访问,访问数据库之类的,都很容易造成主线程堵塞,
这里以访问数据库来说,这类型引起的ANR,一般来讲看当时的CPU使用情况会发现user占比较高,看trace中主线程当时的信息会发现会有一些比如query像ContentProvider这种数据库的动作。这种情况下,还可以去看eventlog或者mainlog,在ANR发生前后打印出来的信息,比如访问数据库这种,在eventlog中搜索"am_anr",然后看前后片段,会发现发生ANR的这个进程有很多数据库相关的信息,说明在发生ANR前后主线程一直在忙于访问数据库,这类型的问题常见于图库,联系人,彩短信应用。
所以这种问题的解决,一般考虑的是异步解决,异步解决并不是简单的new一个线程,要根据业务场景以及频率来决定,Android常见的异步AsyncTask, IntentService, 线程池(官方四种或自定义), new thread等,一般来说不建议直接new thread。
特殊案例:Sharepreference操作导致的ANR。
类别五:binder线程池被占满
系统对每个process最多分配15个binder线程,这个是谷歌的设计(/frameworks/native/libs/binder/ProcessState.cpp)
如果另一个process发送太多重复binder请求,那么就会导致接收端binder线程被占满,从而处理不了其它的binder请求,这时候请求端发起的请求就会阻塞等待了(未设置异步请求的前提下),这本身就是系统的一个限制,如果应用未按照系统的要求来实现对应逻辑,那么就会造成问题。
而系统端是不会(也不建议)通过修改系统行为来兼容应用逻辑,否则更容易造成其它根据系统需求正常编写的应用反而出现不可预料的问题。
判断Binder是否用完,可以在trace中搜索关键字“binder_f”,如果搜索到则表示已经用完,然后就要找log其他地方看是谁一直在消耗binder或者是有死锁发生,之前有遇到过压力测试手电筒应用,出现Binder线程池被占满情况,解决的思路就是降低极短时间内大量Binder请求的发生,修复的手法是发送BInder请求的函数中做时间差过滤,限定在500ms内最多执行一次。
类别六:只存在于Monkey测试下
有些问题是只有在Monkey环境下才能跑出来,平时的user版本用户使用是不会出现的,这种问题的话就没有改动的意义。
比如下面这个例子:
ActivityManager: Not finishing activity because controller resumed
03-18 07:25:50.901 810 870 I am_anr : [0,25443,android.process.media,1086897733,Input dispatching timed out (Waiting because no window has focus but there is a focused application that may eventually add a window when it finishes starting up.)]
发生这个ANR的原因是Contoller将resume的操作给拦截了, 导致Focus不过去, 从而导致ANR,User版本不会有Contoller, 所以不会出现这个 ANR. 所以这个 ANR 可以忽略。
下面我会有两个实例,是我在项目开发的过程中解决的两个ANR。
四:ANR实例讲解(相册)
实例一:#9531:相册】【Dev6】【偶现】照片tab存在大量照片(如3000张),折叠状态快速滑动后展开至大屏,出现“相册没有响应”(详情见附件)
一:logcat-event-log( EventLog )文件搜索关键字am_anr:(非必需)
ANR错误日志:
09-17 14:12:00.406 1187 1352 I am_anr : [0,5608,org.codeaurora.gallery,953728069,Input dispatching timed out (Waiting to send non-key event because the touched window has not finished processing certain input events that were delivered to it over 500.0ms ago. Wait queue length: 17. Wait queue head age: 5521.1ms.)]
二:logcat-log( main log )文件搜索关键字anr in:(非必需)
09-17 14:12:03.779 1187 1352 E ActivityManager: ANR in org.codeaurora.gallery (org.codeaurora.gallery/com.android.gallery3d.app.GalleryActivity)
09-17 14:12:03.779 1187 1352 E ActivityManager: PID: 5608
09-17 14:12:03.779 1187 1352 E ActivityManager: Reason: Input dispatching timed out (Waiting to send non-key event because the touched window has not finished processing certain input events that were delivered to it over 500.0ms ago. Wait queue length: 17. Wait queue head age: 5521.1ms.)
09-17 14:12:03.779 1187 1352 E ActivityManager: Load: 9.33 / 5.85 / 3.35
09-17 14:12:03.779 1187 1352 E ActivityManager: CPU usage from 21279ms to 0ms ago (2019-09-17 14:11:39.112 to 2019-09-17 14:12:00.391):
09-17 14:12:03.779 1187 1352 E ActivityManager: 31% 1187/system_server: 11% user + 19% kernel / faults: 4240 minor
09-17 14:12:03.779 1187 1352 E ActivityManager: 20% 5608/org.codeaurora.gallery: 9.4% user + 10% kernel / faults: 17590 minor
三:Traces.txt日志分析,搜索关键字:tid=1
"main" prio=5 tid=1 Native
| group="main" sCount=1 dsCount=0 flags=1 obj=0x75bbb9f0 self=0x7977a14c00
| sysTid=4581 nice=-10 cgrp=default sched=0/0 handle=0x79fda63548
| state=S schedstat=( 8102631431 3226315969 19028 ) utm=598 stm=211 core=5 HZ=100
| stack=0x7fdd543000-0x7fdd545000 stackSize=8MB
| held mutexes=kernel: (couldn't read /proc/self/task/4581/stack)
native: #00 pc 000000000001f22c /system/lib64/libc.so (syscall+28)
native: #01 pc 00000000000d7278 /system/lib64/libart.so (art::ConditionVariable::WaitHoldingLocks(art::Thread*)+148)
native: #02 pc 0000000000513e70 /system/lib64/libart.so
at android.os.BinderProxy.transactNative(Native method)
at android.os.BinderProxy.transact(Binder.java:1127)
at android.hardware.display.IDisplayManager$Stub$Proxy.getDisplayInfo(IDisplayManager.java:404)
at android.hardware.display.DisplayManagerGlobal.getDisplayInfo(DisplayManagerGlobal.java:124)
- locked <0x08e21a90> (a java.lang.Object)
at android.view.Display.updateDisplayInfoLocked(Display.java:1056)
at android.view.Display.getSize(Display.java:613)
- locked <0x07bd5f89> (a android.view.Display)
at com.bumptech.glide.request.target.ViewTarget$SizeDeterminer.getDisplayDimens(ViewTarget.java:267)
at com.bumptech.glide.request.target.ViewTarget$SizeDeterminer.getSizeForParam(ViewTarget.java:250)
at com.bumptech.glide.request.target.ViewTarget$SizeDeterminer.getViewHeightOrParam(ViewTarget.java:231)
at com.bumptech.glide.request.target.ViewTarget$SizeDeterminer.getSize(ViewTarget.java:209)
at com.bumptech.glide.request.target.ViewTarget.getSize(ViewTarget.java:100)
at com.bumptech.glide.request.GenericRequest.begin(GenericRequest.java:272)
at com.bumptech.glide.manager.RequestTracker.runRequest(RequestTracker.java:37)
at com.bumptech.glide.GenericRequestBuilder.into(GenericRequestBuilder.java:661)
at com.bumptech.glide.GenericRequestBuilder.into(GenericRequestBuilder.java:697)
at com.bumptech.glide.BitmapRequestBuilder.into(BitmapRequestBuilder.java:498)
at com.android.gallery.ui.home.timelinepage.TimelinePageAdapter$MediaItemAdapter.setPhotoItem
(TimelinePageAdapter.java:215)
at com.android.gallery.ui.home.timelinepage.TimelinePageAdapter$MediaItemAdapter.convert(TimelinePageAdapter.java:193)
at com.android.gallery.ui.home.timelinepage.TimelinePageAdapter$MediaItemAdapter.convert(TimelinePageAdapter.java:178)
通过查看日志,我们可以看到是开源框架里面的代码发生ANR了,再结合测试同事的操作,我发现是在快速滑动的时候,如果转屏,glide框架大量重复执行不同图片的裁剪动作,因为旋转屏幕后imageview的大小发生变化,导致裁剪的图片发生变化。
解决思路一:在转屏的时候停止列表的滑动(stopScrolling)
解决思路二:不要调用裁剪图片的函数,改用其他实现方式。
实例二:#9587【相册】【DEV6】照片tab存在1000张照片,在照片tab全选删除照片,提示“相册没有响应”(非必须)
一:logcat-event-log( EventLog )文件搜索关键字am_anr:(非必需)
ANR错误日志:
09-19 10:27:20.760 1213 1383 I am_anr : [0,10929,org.codeaurora.gallery,953728069,Input dispatching timed out (Waiting to send key event because the focused window has not finished processing all of the input events that were previously delivered to it. Outbound queue length: 0. Wait queue length: 4.)]
上面这行表示ANR类型为Input dispatching timed out, 这种anr的原因的是在viewrootimpl分发事件时,并没有找到focuswindow导致的,这一步确认时间点在:10:27:20 , 进程号:10929。
二:logcat-log( main log )文件搜索关键字anr in:(非必需)
09-19 10:27:26.147 1213 1383 E ActivityManager: ANR in org.codeaurora.gallery (org.codeaurora.gallery/com.android.gallery3d.app.GalleryActivity)
09-19 10:27:26.147 1213 1383 E ActivityManager: PID: 10929
09-19 10:27:26.147 1213 1383 E ActivityManager: Reason: Input dispatching timed out (Waiting to send key event because the focused window has not finished processing all of the input events that were previously delivered to it. Outbound queue length: 0. Wait queue length: 4.)
09-19 10:27:26.147 1213 1383 E ActivityManager: Load: 3.52 / 3.93 / 3.29
09-19 10:27:26.147 1213 1383 E ActivityManager: CPU usage from 0ms to 5386ms later (2019-09-19 10:27:20.726 to 2019-09-19 10:27:26.112) with 99% awake:
09-19 10:27:26.147 1213 1383 E ActivityManager: 38% 1213/system_server: 14% user + 24% kernel / faults: 12458 minor 13 major
09-19 10:27:26.147 1213 1383 E ActivityManager: 0.7% 950/media.codec: 0.4% user + 0.3% kernel / faults: 15822 minor 4 major
09-19 10:27:26.147 1213 1383 E ActivityManager: 18% 743/android.hardware.sensors@1.0-service: 5% user + 12% kernel / faults: 744 minor
09-19 10:27:26.147 1213 1383 E ActivityManager: 14% 2109/com.android.systemui: 8.7% user + 5.9% kernel / faults: 3454 minor
09-19 10:27:26.147 1213 1383 E ActivityManager: 8.7% 4403/com.sohu.inputmethod.sogou.oem: 5.1% user + 3.5% kernel / faults: 2317 minor 8 major
查看ANR时的CPU以及IO率(可选)
这一步一般来说能基本定位是什么造成了ANR,是IO高还是CPU高,如两者都不是,需进入第三步trace日志分析环节,查看mobilelog文件夹下的main_log,搜索关键字"ANR in",可以看到当时的CPU以及IO率,这一个环节一般来讲主要是看发生ANR时的CPU使用情况,CPU是否吃紧,
还有需要注意iowait的占有率,如果占比比较高,则排查的方向要倾向与读取文件操作有关的信息,可以看trace日志中有没有一些读取文件或者操作SD卡的动作。
anr_2019-09-19-10-27-21-129(traces.txt)文件搜索关键字tid=1:
"main" prio=5 tid=1 Blocked
| group="main" sCount=1 dsCount=0 flags=1 obj=0x759d2fa8 self=0x7192c14c00
| sysTid=10929 nice=-10 cgrp=default sched=0/0 handle=0x7218c2f548
| state=S schedstat=( 12920555277 3219927306 32391 ) utm=981 stm=310 core=4 HZ=100
| stack=0x7fe00ff000-0x7fe0101000 stackSize=8MB
| held mutexes=
at com.android.gallery.data.reposity.AlbumSetAllDataLoader$ReloadTask.notifyDirty(AlbumSetAllDataLoader.java:-1)
- waiting to lock <0x0aefa2a7> (a com.android.gallery.data.reposity.AlbumSetAllDataLoader$ReloadTask) held by thread 58
at com.android.gallery.data.reposity.AlbumSetAllDataLoader$MySourceListener.onContentDirty(AlbumSetAllDataLoader.java:202)
at com.android.gallery3d.data.MediaSet.notifyContentChanged(MediaSet.java:254)
- locked <0x0fc15354> (a java.lang.Object)
at com.android.gallery3d.data.ClusterAlbumSet.onContentDirty(ClusterAlbumSet.java:107)
at com.android.gallery3d.data.MediaSet.notifyContentChanged(MediaSet.java:254)
- locked <0x03ac42fd> (a java.lang.Object)
atcom.android.gallery3d.data.ComboAlbumSet.onContentDirty(ComboAlbumSet.java:103)
at com.android.gallery3d.data.MediaSet.notifyContentChanged(MediaSet.java:254)
- locked <0x096fcbf2> (a java.lang.Object)
at com.android.gallery3d.data.ChangeNotifier.onChange(ChangeNotifier.java:54)
at com.android.gallery3d.data.DataManager$NotifyBroker.onChange(DataManager.java:450)
- locked <0x03e9c243> (a com.android.gallery3d.data.DataManager$NotifyBroker)
at android.database.ContentObserver.onChange(ContentObserver.java:130)
at android.database.ContentObserver.onChange(ContentObserver.java:145)
at android.database.ContentObserver$NotificationRunnable.run(ContentObserver.java:216)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6766)
at java.lang.reflect.Method.invoke(Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
"Thread-112" prio=5 tid=58 Blocked
| group="main" sCount=1 dsCount=0 flags=1 obj=0x138409f0 self=0x71758a3000
| sysTid=11785 nice=10 cgrp=default sched=0/0 handle=0x716e7ff4f0
| state=S schedstat=( 131607643 242516728 1013 ) utm=9 stm=3 core=0 HZ=100
| stack=0x716e6fc000-0x716e6fe000 stackSize=1041KB
| held mutexes=
at com.android.gallery3d.data.ClusterAlbumSet.isLoading(ClusterAlbumSet.java:-1)
- waiting to lock <0x02ba1cd8> (a com.android.gallery3d.data.ClusterAlbumSet) held by thread 56
at com.android.gallery.data.reposity.AlbumSetAllDataLoader$ReloadTask.run(AlbumSetAllDataLoader.java:369)
- locked <0x0aefa2a7> (a com.android.gallery.data.reposity.AlbumSetAllDataLoader$ReloadTask)
解决思路一:由于主线程频繁的执行工作线程的函数notifyDirty(),而当工作线程也被阻塞时,主线程执行的函数无法在规定时间内完成工作,解决思路:减少主线程调用notifyDirty()的屏幕,解决此问题。
解决思路二:解决56线程的阻塞问题也可能可以解决此问题。
Log查看总结:
一:、logcat-event-log( EventLog )文件搜索关键字am_anr:(非必需)
解析:04-29 10:00:57.240 1267 1341 I am_anr : [0,6073,com.android.dialer,952745541,Input dispatching timed out。
二:logcat-log( main log )文件搜索关键字anr in:(非必需)
解析:可以看到当时的CPU以及IO率,这一个环节一般来讲主要是看发生ANR时的CPU使用情况,CPU是否吃紧,
还有需要注意iowait的占有率,如果占比比较高,则排查的方向要倾向与读取文件操作有关的信息,可以看trace日志中有没有一些读取文件或者操作SD卡的动作。
三:anr_2019-09-19-10-27-21-129(traces.txt)文件搜索关键字tid=1:
解析:一般来讲直接先看tid=1的堆栈即对应主线程,因为ANR都是主线程执行超时导致。
五:ANR分析总结
先定位发生ANR时间点,然后查看trace信息,接着分析是否有耗时的message、binder调用,锁的竞争,CPU资源的抢占,以及结合具体场景的上下文来分析,调试手段就需要针对前面说到的message、binder、锁等资源从系统角度细化更多debug信息,这里不再展开,后续再以ANR案例来讲解。
作为应用开发者应让主线程尽量只做UI相关的操作,避免耗时操作,比如过度复杂的UI绘制,网络操作,文件IO操作;避免主线程跟工作线程发生锁的竞争,减少系统耗时binder的调用,谨慎使用SharePreference ,注意主线程执行provider query操作。简而言之,尽可能减少主线程的负载,让其空闲待命,以期可随时响应用户的操作。
附加:当SharePreference操作发生ANR的场景:
1、 SharePreference主线程 getXX 方法会 ANR。
2、 SharePreference apply 方法会 ANR。
3、 SharePreference主线程调用 commit 方法 ANR。
解决思路:在主线程调用 commit 方法会出现 ANR,可以将所有的 commit 任务放到单线程池的线程里去执行。
( SingleThreadPool )
六:扩展阅读
1、http://gityuan.com/2019/04/06/android-anr/(彻底理解安卓应用无响应机制)
2、http://gityuan.com/2016/07/02/android-anr/(理解Android ANR的触发原理)
3、https://www.jianshu.com/p/653aef57ae51(SharedPreferences ANR 总结)
4、https://www.jianshu.com/p/3959a601cea6(ANR问题一般解决思路 )