每日一问:谈谈 SharedPreferences 的 apply() 和 commit() (6)

看完了这个 writeToFile() ,我们再来看看下面做了啥。

// Typical #commit() path with fewer allocations, doing a write on // the current thread. if (isFromSyncCommit) { boolean wasEmpty = false; synchronized (mLock) { wasEmpty = mDiskWritesInFlight == 1; } if (wasEmpty) { writeToDiskRunnable.run(); return; } } QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);

可以看到,当且仅当是 commit() 并且只有一个待写入操作的时候才能直接执行到 writeToDiskRunnable.run(),否则都会执行到 QueuedWork 的 queue() 方法,这个 QueuedWork 又是什么东西?

/** Finishers {@link #addFinisher added} and not yet {@link #removeFinisher removed} */ @GuardedBy("sLock") private static final LinkedList<Runnable> sFinishers = new LinkedList<>(); /** Work queued via {@link #queue} */ @GuardedBy("sLock") private static final LinkedList<Runnable> sWork = new LinkedList<>(); /** * Internal utility class to keep track of process-global work that's outstanding and hasn't been * finished yet. * * New work will be {@link #queue queued}. * * It is possible to add 'finisher'-runnables that are {@link #waitToFinish guaranteed to be run}. * This is used to make sure the work has been finished. * * This was created for writing SharedPreference edits out asynchronously so we'd have a mechanism * to wait for the writes in Activity.onPause and similar places, but we may use this mechanism for * other things in the future. * * The queued asynchronous work is performed on a separate, dedicated thread. * * @hide */ public class QueuedWork { /** * Add a finisher-runnable to wait for {@link #queue asynchronously processed work}. * * Used by SharedPreferences$Editor#startCommit(). * * Note that this doesn't actually start it running. This is just a scratch set for callers * doing async work to keep updated with what's in-flight. In the common case, caller code * (e.g. SharedPreferences) will pretty quickly call remove() after an add(). The only time * these Runnables are run is from {@link #waitToFinish}. * * @param finisher The runnable to add as finisher */ public static void addFinisher(Runnable finisher) { synchronized (sLock) { sFinishers.add(finisher); } } /** * Remove a previously {@link #addFinisher added} finisher-runnable. * * @param finisher The runnable to remove. */ public static void removeFinisher(Runnable finisher) { synchronized (sLock) { sFinishers.remove(finisher); } } /** * Trigger queued work to be processed immediately. The queued work is processed on a separate * thread asynchronous. While doing that run and process all finishers on this thread. The * finishers can be implemented in a way to check weather the queued work is finished. * * Is called from the Activity base class's onPause(), after BroadcastReceiver's onReceive, * after Service command handling, etc. (so async work is never lost) */ public static void waitToFinish() { long startTime = System.currentTimeMillis(); boolean hadMessages = false; Handler handler = getHandler(); synchronized (sLock) { if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) { // Delayed work will be processed at processPendingWork() below handler.removeMessages(QueuedWorkHandler.MSG_RUN); if (DEBUG) { hadMessages = true; Log.d(LOG_TAG, "waiting"); } } // We should not delay any work as this might delay the finishers sCanDelay = false; } StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); try { processPendingWork(); } finally { StrictMode.setThreadPolicy(oldPolicy); } try { while (true) { Runnable finisher; synchronized (sLock) { finisher = sFinishers.poll(); } if (finisher == null) { break; } finisher.run(); } } finally { sCanDelay = true; } synchronized (sLock) { long waitTime = System.currentTimeMillis() - startTime; if (waitTime > 0 || hadMessages) { mWaitTimes.add(Long.valueOf(waitTime).intValue()); mNumWaits++; if (DEBUG || mNumWaits % 1024 == 0 || waitTime > MAX_WAIT_TIME_MILLIS) { mWaitTimes.log(LOG_TAG, "waited: "); } } } } /** * Queue a work-runnable for processing asynchronously. * * @param work The new runnable to process * @param shouldDelay If the message should be delayed */ public static void queue(Runnable work, boolean shouldDelay) { Handler handler = getHandler(); synchronized (sLock) { sWork.add(work); if (shouldDelay && sCanDelay) { handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY); } else { handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN); } } } }

简单地说,这个 QueuedWork 类里面有一个专门存放 Runnable 的两个 LinkedList 对象,他们分别对应未完成的操作 sFinishers 和正在工作的 sWork。
我们在 waitToFinish() 方法中,会不断地去遍历执行未完成的 Runnable。我们根据注释也知道了这个方法会在 Activity 的 onPause() 和 BroadcastReceiver 的 onReceive() 方法后调用。假设我们频繁的调用了 apply()方法,并紧接着调用了 onPause() ,那么就可能会发生 onPause() 一直等待 QueuedWork.waitToFinish 执行完成而产生 ANR。也就是说,即使是调用了 apply() 方法去异步提交,也不是完全安全的。如果 apply() 方法使用不当,也是可能出现 ANR 的。

总结

说了这么多,我们当然还是需要做一个总结。

apply() 没有返回值而 commit() 返回 boolean 表明修改是否提交成功 ;

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpyfpx.html