Chinaunix首页 | 论坛 | 博客
  • 博客访问: 147880
  • 博文数量: 23
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 326
  • 用 户 组: 普通用户
  • 注册时间: 2014-02-26 10:49
个人简介

记忆总是会慢慢褪去,所以让文字记住一切~

文章分类

全部博文(23)

文章存档

2017年(5)

2016年(3)

2015年(9)

2014年(6)

我的朋友

分类: 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的发生,看其函数描述如下:

点击(此处)折叠或打开

  1. /**
  2.  * Finishes or waits for async operations to complete.
  3.  * (e.g. SharedPreferences$Editor#startCommit writes)
  4.  *
  5.  * Is called from the Activity base class's onPause(), after
  6.  * BroadcastReceiver's onReceive, after Service command handling,
  7.  * etc. (so async work is never lost)
  8.  */
  9. public static void waitToFinish() {
  10.     Runnable toFinish;
  11.     while ((toFinish = sPendingWorkFinishers.poll()) != null) {
  12.         toFinish.run();
  13.     }
  14. }

解释为在处理activity、service或广播的时候会被执行,以确保之前加入等待队列sPendingWorkFinishers的异步处理能够执行到结束。果然这里确实被ActivityThread调用,整体函数交互如下:


可以看到写入磁盘等待awaitCommit在apply中被创建,同时加入了QueuedWork(即apply方法会将等待写入到文件系统的任务放在QueuedWork的等待完成队列里)。awaitCommit阻塞在await(判断CountDownLatch为0时返回)。


正常情况下equeueDiskWrite()函数创建线程执行writeToDiskRunnable:

  1. 当正常写入成功后,在setDiskWriteResult()会将CountDownLatch减值为0;
  2. 进一步调用postWriteRunnable执行删除QueueWork中的任务。

函数调用如下:




点击(此处)折叠或打开

  1. private void enqueueDiskWrite(final MemoryCommitResult mcr,
  2.                               final Runnable postWriteRunnable) {
  3.     final Runnable writeToDiskRunnable = new Runnable() {
  4.             public void run() {
  5.                 synchronized (mWritingToDiskLock) {
  6.                     writeToFile(mcr);                                                           //写入文件,最终调用mcr.setDiskWriteResult()设置writtenToDiskLatch.countDown();
  7.                 }
  8.                 synchronized (SharedPreferencesImpl.this) {
  9.                     mDiskWritesInFlight--;
  10.                 }
  11.                 if (postWriteRunnable != null) {
  12.                     postWriteRunnable.run();            //这里实际执行的awaitCommit,执行时由于之前countDown操作使得count=0从而await()会直接返回
  13.                 }
  14.             }
  15.         };
  16.     ......
  17.     QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
  18. }

从这里可以梳理出为什么调用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值的任务放到单独线程里去做。


点击(此处)折叠或打开

  1. public boolean commit() {
  2.     MemoryCommitResult mcr = commitToMemory();
  3.     SharedPreferencesImpl.this.enqueueDiskWrite(
  4.         mcr, null /* sync write on this thread okay */);
  5.     try {
  6.         mcr.writtenToDiskLatch.await();
  7.     } catch (InterruptedException e) {
  8.         return false;
  9.     }
  10.     notifyListeners(mcr);
  11.     return mcr.writeToDiskResult;
  12. }

所以,使用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


阅读(5634) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

pxshl2019-01-27 22:55:58

writeToFile(mcr);  是在CountDownLatch之前执行的。 理论上不应该是由CountDownLatch.await阻塞呀。
看了代码,实在没看出CountDownLatch对apply的 作用。
但是commit确实是由countDownLatch阻塞的。