final class SharedPreferencesImpl implements SharedPreferences {
private static final String TAG = "SharedPreferencesImpl";
private static final boolean DEBUG = false;
+ private static final Object CONTENT = new Object();
// Lock ordering rules:
- // - acquire SharedPreferencesImpl.this before EditorImpl.this
- // - acquire mWritingToDiskLock before EditorImpl.this
+ // - acquire SharedPreferencesImpl.mLock before EditorImpl.mLock
+ // - acquire mWritingToDiskLock before EditorImpl.mLock
private final File mFile;
private final File mBackupFile;
private final int mMode;
+ private final Object mLock = new Object();
+ private final Object mWritingToDiskLock = new Object();
- private Map<String, Object> mMap; // guarded by 'this'
- private int mDiskWritesInFlight = 0; // guarded by 'this'
- private boolean mLoaded = false; // guarded by 'this'
- private long mStatTimestamp; // guarded by 'this'
- private long mStatSize; // guarded by 'this'
+ @GuardedBy("mLock")
+ private Map<String, Object> mMap;
- private final Object mWritingToDiskLock = new Object();
- private static final Object mContent = new Object();
+ @GuardedBy("mLock")
+ private int mDiskWritesInFlight = 0;
+
+ @GuardedBy("mLock")
+ private boolean mLoaded = false;
+
+ @GuardedBy("mLock")
+ private long mStatTimestamp;
+
+ @GuardedBy("mLock")
+ private long mStatSize;
+
+ @GuardedBy("mLock")
private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
}
private void startLoadFromDisk() {
- synchronized (this) {
+ synchronized (mLock) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
}
private void loadFromDisk() {
- synchronized (SharedPreferencesImpl.this) {
+ synchronized (mLock) {
if (mLoaded) {
return;
}
/* ignore */
}
- synchronized (SharedPreferencesImpl.this) {
+ synchronized (mLock) {
mLoaded = true;
if (map != null) {
mMap = map;
} else {
mMap = new HashMap<>();
}
- notifyAll();
+ mLock.notifyAll();
}
}
}
void startReloadIfChangedUnexpectedly() {
- synchronized (this) {
+ synchronized (mLock) {
// TODO: wait for any pending writes to disk?
if (!hasFileChangedUnexpectedly()) {
return;
// Has the file changed out from under us? i.e. writes that
// we didn't instigate.
private boolean hasFileChangedUnexpectedly() {
- synchronized (this) {
+ synchronized (mLock) {
if (mDiskWritesInFlight > 0) {
// If we know we caused it, it's not unexpected.
if (DEBUG) Log.d(TAG, "disk write in flight, not unexpected.");
return true;
}
- synchronized (this) {
+ synchronized (mLock) {
return mStatTimestamp != stat.st_mtime || mStatSize != stat.st_size;
}
}
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
- synchronized(this) {
- mListeners.put(listener, mContent);
+ synchronized(mLock) {
+ mListeners.put(listener, CONTENT);
}
}
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
- synchronized(this) {
+ synchronized(mLock) {
mListeners.remove(listener);
}
}
}
while (!mLoaded) {
try {
- wait();
+ mLock.wait();
} catch (InterruptedException unused) {
}
}
}
public Map<String, ?> getAll() {
- synchronized (this) {
+ synchronized (mLock) {
awaitLoadedLocked();
//noinspection unchecked
return new HashMap<String, Object>(mMap);
@Nullable
public String getString(String key, @Nullable String defValue) {
- synchronized (this) {
+ synchronized (mLock) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
@Nullable
public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
- synchronized (this) {
+ synchronized (mLock) {
awaitLoadedLocked();
Set<String> v = (Set<String>) mMap.get(key);
return v != null ? v : defValues;
}
public int getInt(String key, int defValue) {
- synchronized (this) {
+ synchronized (mLock) {
awaitLoadedLocked();
Integer v = (Integer)mMap.get(key);
return v != null ? v : defValue;
}
}
public long getLong(String key, long defValue) {
- synchronized (this) {
+ synchronized (mLock) {
awaitLoadedLocked();
Long v = (Long)mMap.get(key);
return v != null ? v : defValue;
}
}
public float getFloat(String key, float defValue) {
- synchronized (this) {
+ synchronized (mLock) {
awaitLoadedLocked();
Float v = (Float)mMap.get(key);
return v != null ? v : defValue;
}
}
public boolean getBoolean(String key, boolean defValue) {
- synchronized (this) {
+ synchronized (mLock) {
awaitLoadedLocked();
Boolean v = (Boolean)mMap.get(key);
return v != null ? v : defValue;
}
public boolean contains(String key) {
- synchronized (this) {
+ synchronized (mLock) {
awaitLoadedLocked();
return mMap.containsKey(key);
}
// context.getSharedPreferences(..).edit().putString(..).apply()
//
// ... all without blocking.
- synchronized (this) {
+ synchronized (mLock) {
awaitLoadedLocked();
}
// Return value from EditorImpl#commitToMemory()
private static class MemoryCommitResult {
- public long memoryStateGeneration;
- public List<String> keysModified; // may be null
- public Set<OnSharedPreferenceChangeListener> listeners; // may be null
- public Map<?, ?> mapToWriteToDisk;
- public final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
- public volatile boolean writeToDiskResult = false;
-
- public void setDiskWriteResult(boolean result) {
+ final long memoryStateGeneration;
+ @Nullable final List<String> keysModified;
+ @Nullable final Set<OnSharedPreferenceChangeListener> listeners;
+ final Map<String, Object> mapToWriteToDisk;
+ final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
+
+ @GuardedBy("mWritingToDiskLock")
+ volatile boolean writeToDiskResult = false;
+
+ private MemoryCommitResult(long memoryStateGeneration, @Nullable List<String> keysModified,
+ @Nullable Set<OnSharedPreferenceChangeListener> listeners,
+ Map<String, Object> mapToWriteToDisk) {
+ this.memoryStateGeneration = memoryStateGeneration;
+ this.keysModified = keysModified;
+ this.listeners = listeners;
+ this.mapToWriteToDisk = mapToWriteToDisk;
+ }
+
+ void setDiskWriteResult(boolean result) {
writeToDiskResult = result;
writtenToDiskLatch.countDown();
}
}
public final class EditorImpl implements Editor {
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
private final Map<String, Object> mModified = Maps.newHashMap();
+
+ @GuardedBy("mLock")
private boolean mClear = false;
public Editor putString(String key, @Nullable String value) {
- synchronized (this) {
+ synchronized (mLock) {
mModified.put(key, value);
return this;
}
}
public Editor putStringSet(String key, @Nullable Set<String> values) {
- synchronized (this) {
+ synchronized (mLock) {
mModified.put(key,
(values == null) ? null : new HashSet<String>(values));
return this;
}
}
public Editor putInt(String key, int value) {
- synchronized (this) {
+ synchronized (mLock) {
mModified.put(key, value);
return this;
}
}
public Editor putLong(String key, long value) {
- synchronized (this) {
+ synchronized (mLock) {
mModified.put(key, value);
return this;
}
}
public Editor putFloat(String key, float value) {
- synchronized (this) {
+ synchronized (mLock) {
mModified.put(key, value);
return this;
}
}
public Editor putBoolean(String key, boolean value) {
- synchronized (this) {
+ synchronized (mLock) {
mModified.put(key, value);
return this;
}
}
public Editor remove(String key) {
- synchronized (this) {
+ synchronized (mLock) {
mModified.put(key, this);
return this;
}
}
public Editor clear() {
- synchronized (this) {
+ synchronized (mLock) {
mClear = true;
return this;
}
// Returns true if any changes were made
private MemoryCommitResult commitToMemory() {
- MemoryCommitResult mcr = new MemoryCommitResult();
- synchronized (SharedPreferencesImpl.this) {
+ long memoryStateGeneration;
+ List<String> keysModified = null;
+ Set<OnSharedPreferenceChangeListener> listeners = null;
+ Map<String, Object> mapToWriteToDisk;
+
+ synchronized (SharedPreferencesImpl.this.mLock) {
// We optimistically don't make a deep copy until
// a memory commit comes in when we're already
// writing to disk.
// noinspection unchecked
mMap = new HashMap<String, Object>(mMap);
}
- mcr.mapToWriteToDisk = mMap;
+ mapToWriteToDisk = mMap;
mDiskWritesInFlight++;
boolean hasListeners = mListeners.size() > 0;
if (hasListeners) {
- mcr.keysModified = new ArrayList<String>();
- mcr.listeners =
- new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
+ keysModified = new ArrayList<String>();
+ listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}
- synchronized (this) {
+ synchronized (mLock) {
boolean changesMade = false;
if (mClear) {
changesMade = true;
if (hasListeners) {
- mcr.keysModified.add(k);
+ keysModified.add(k);
}
}
mCurrentMemoryStateGeneration++;
}
- mcr.memoryStateGeneration = mCurrentMemoryStateGeneration;
+ memoryStateGeneration = mCurrentMemoryStateGeneration;
}
}
- return mcr;
+ return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
+ mapToWriteToDisk);
}
public boolean commit() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr, isFromSyncCommit);
}
- synchronized (SharedPreferencesImpl.this) {
+ synchronized (mLock) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
// the current thread.
if (isFromSyncCommit) {
boolean wasEmpty = false;
- synchronized (SharedPreferencesImpl.this) {
+ synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
if (isFromSyncCommit) {
needsWrite = true;
} else {
- synchronized (this) {
+ synchronized (mLock) {
// No need to persist intermediate states. Just wait for the latest state to
// be persisted.
if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
try {
final StructStat stat = Os.stat(mFile.getPath());
- synchronized (this) {
+ synchronized (mLock) {
mStatTimestamp = stat.st_mtime;
mStatSize = stat.st_size;
}