记忆总是会慢慢褪去,所以让文字记住一切~
分类: Android平台
2017-03-21 21:58:28
ANR文件提取的有用片段如下:
----- pid 13431 at 2016-09-14 11:46:10 -----
Cmd line: com.android.settings
at java.lang.Object.wait(Native Method)
- waiting on <0x41897ec8> (a java.lang.VMThread) held by tid=1 (main)
at java.lang.Thread.parkFor(Thread.java:1205)
at sun.misc.Unsafe.park(Unsafe.java:325)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:157)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:813)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:973)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1281)
at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:202)
at android.app.SharedPreferencesImpl$EditorImpl$1.run(SharedPreferencesImpl.java:364)
at android.app.QueuedWork.waitToFinish(QueuedWork.java:88)
at android.app.ActivityThread.handleStopActivity(ActivityThread.java:3418)
at android.app.ActivityThread.access$1100(ActivityThread.java:154)
可以看到这里面卡在waitToFinish()上面,该函数的等待导致了ANR的发生,看其函数描述如下:
点击(此处)折叠或打开
解释为在处理activity、service或广播的时候会被执行,以确保之前加入等待队列sPendingWorkFinishers的异步处理能够执行到结束。果然这里确实被ActivityThread调用,整体函数交互如下:
可以看到写入磁盘等待awaitCommit在apply中被创建,同时加入了QueuedWork(即apply方法会将等待写入到文件系统的任务放在QueuedWork的等待完成队列里)。awaitCommit阻塞在await(判断CountDownLatch为0时返回)。
正常情况下equeueDiskWrite()函数创建线程执行writeToDiskRunnable:
函数调用如下:
点击(此处)折叠或打开
从这里可以梳理出为什么调用QueueWork.waitToFinish()为什么会出现等待:
sPendingWorkFinshers中存在未完成的任务(awaitCommit) <=== postWriteRunnable还没有机会执行(因为执行后在队列中是被删除的) <=== writeToFile()执行未结束(从而也就没有机会去减少CountDownLatch的值) <=== awaitCommit阻塞在CountDownLatch;
看到上面的推论,其中writeToFile最耗时间,在这个异步操作过程中,我们的Activity结束了,然后调用waitToFinish()将会导致hang住。
所以如果我们使用SharedPreference的apply方法, 虽然该方法可以很快返回, 并在其它线程里将键值对写入到文件系统, 但是当Activity的onPause/onStop等方法被调用时,会等待写入到文件系统的任务完成,如果写入比较慢,主线程就会出现ANR问题。
另外,SharedPreference除了提供apply外还提供commit方法,源码如下所示,该方法直接在调用线程中执行,不会转入后台,但如果我们在UI线程commit,且磁盘写入较慢的情况下,ANR依然会发生,这个时候就需要将修改SharedPreference值的任务放到单独线程里去做。
点击(此处)折叠或打开
所以,使用SharedPreference时无论使用apply还是commit都会有很小的概率由于写入磁盘问题(IO瓶颈)出现ANR。除了IO瓶颈外,锁瓶颈也是SharedPreference的一个缺点,单个共享数据的文件在被同时操作时会有加解锁开销。
总结一下,由于IO瓶颈导致的ANR我们是束手无策的,但我们在使用过程可以优化:
1)对于多个线程访问单个SharedPreference的情况用apply比较好(提高响应速度),只有一个线程访问的话用commit比较好; 2)对于修改多个值能一次提交; 3)如果真的发现调用经常出现ANR,那就考虑起一个线程在后台来做,这个时候选用commit,而不是apply。
附上一篇文章,其粗略总结了使用SharedPreference的缺点和注意事项:
http://www.cnblogs.com/puff/p/5530825.html