Usage
SharedPreferences preferences = context.getSharedPreferences(NAME, Context.MODE_PRIVATE);
preference.edit().putInt(strKey, nValue).apply();
//or
preference.edit().putInt(strKey, nValue).commit();
Context
- getSharedPreferences
abstract method and return SharedPreferences object
ContextImpl extends Context
- getSharedPreferences
override method in Context
- getSharedPreferencesPath
create new directory for xml file
- getPreferencesDir
mPreferencesDir = new File(getDataDir(), "shared_prefs");
- getDataDir
get data directory,refer to /data/data/packagename/.And entire directory is /data/data/packagename/shared_prefs/
- makeFilename
entire arguments is:
makeFilename(getPreferencesDir(), name + ".xml");
the file and name will put into map:
ArrayMap<String, File> mSharedPrefsPaths.put(name, file);
- getSharedPreferences
the arguments is file and mode.
- getSharedPreferencesCacheLocked
init ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache and ArrayMap<File, SharedPreferencesImpl> packagePrefs,and put packagePrefs in sSharedPrefsCache with packageName as key.
- checkMode
codes:
private void checkMode(int mode) {
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) {
if ((mode & MODE_WORLD_READABLE) != 0) {
throw new SecurityException("MODE_WORLD_READABLE no longer supported");
}
if ((mode & MODE_WORLD_WRITEABLE) != 0) {
throw new SecurityException("MODE_WORLD_WRITEABLE no longer supported");
}
}
}
- SharedPreferencesImpl
constructor method invoke
and ArrayMap<File, SharedPreferencesImpl> cache putSharedPreferencesImplobject with file as key.
SharedPreferencesImpl
implements SharedPreferences interface and accomplish its methods.
- SharedPreferencesImpl()
- makeBackupFile
create temp file with bak with trail.
static File makeBackupFile(File prefsFile) {
return new File(prefsFile.getPath() + ".bak");
}
- startLoadFromDisk
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
- loadFromDisk
load file from disk to memory in a map,in the method,we can find:
Map<String, Object> map = (Map<String, Object>) XmlUtils.readMapXml(str);
Map<String, Object> mMap = map;
at the end of getSharedPreferences,we find that MODE_MULTI_PROCESS property is making effect to version which less than 11,or useless if you set it,and below 11,it just loads data from disk again.
Editor
After getSharedPreferences method,we will invoke edit() to get a object of Editor class to commit or apply.Editor is a inner interface in SharedPreferences class and will be implemented in SharedPreferencesImpl.
- edit
return Editor object,and code is blocked by a lock object mLock.
- awaitLoadedLocked
while (!mLoaded) {
try {
mLock.wait();
} catch (InterruptedException unused) {
}
}
mLoaded property is initialized in loadFromDisk method,and this means that before data is loaded from disk it will not be got Editor operator,means process is blocked.
- EditorImpl
invoke EditorImpl constructor to get its object.
EditorImpl
implements methods in Editor interface.
- putString
put data through put method,for instance:putInt,putLong,putFloat,putBoolean and so on.
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
- commit
invoke commitToMemory first and enqueueDiskWrite in SharedPreferencesImpl class and notifyListeners with MemoryCommitResult argument.
- commitToMemory
foreach Map object of mModified and put key and value inmapToWriteToDiskobject which key is String and value type is Object.
Map<String, Object> mapToWriteToDisk = mMap;
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
mapToWriteToDisk.put(k, v);
}
and then init a new object:new MemoryCommitResult.
MemoryCommitResult
this class blocked some arguments and returned to commitToMemory method,and changed data are set to mapToWriteToDisk in MemoryCommitResult class.
Back to commit method,the next method is enqueueDiskWrit in commit.
SharedPreferencesImpl
- enqueueDiskWrite
this method is defined inSharedPreferencesImplclass and do a favour to write data in xml file.
private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) {
final boolean isFromSyncCommit = (postWriteRunnable == null);
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
// 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);
}
the postWriteRunnable argument will be null and isFromSyncCommit is true,what’s more,mDiskWritesInFlight argument is self-added,wasEmpty is true.Naturelly,the thread writeToDiskRunnable will be run.
The thread of writeToDiskRunnable does work for saving data with writeToFile method and self-decreased mDiskWritesInFlight argument.
- writeToFile
The vital codes are:
FileOutputStream str = createFileOutputStream(mFile);
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
- writeMapXml
public static final void writeMapXml(Map val, String name, XmlSerializer out,WriteMapCallback callback) throws XmlPullParserException, java.io.IOException {
if (val == null) {
out.startTag(null, "null");
out.endTag(null, "null");
return;
}
out.startTag(null, "map");
if (name != null) {
out.attribute(null, "name", name);
}
Set s = val.entrySet();
Iterator i = s.iterator();
while (i.hasNext()) {
Map.Entry e = (Map.Entry)i.next();
writeValueXml(e.getValue(), (String)e.getKey(), out, callback);
}
out.endTag(null, "map");
}
we can figure out that the step is getting out all content from file and then transfered to be FileOutputStream.The changed key and value pair will be written to xml file.
The process indicates that we can not set numerous and complex data with commit method.
After execute enqueueDiskWrite method in commit method,we will find out that a CountDownLatch object will execute await method util it is counted down to zero.
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
} finally {
if (DEBUG) {
Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ " committed after " + (System.currentTimeMillis() - startTime)
+ " ms");
}
}
And at the end of writeToFile method,it will invoke:
mcr.setDiskWriteResult(true, true);
void setDiskWriteResult(boolean wasWritten, boolean result) {
this.wasWritten = wasWritten;
writeToDiskResult = result;
writtenToDiskLatch.countDown();
}
writtenToDiskLatch.countDown(); means that value has been to zero,the next step can be execute,and commit result will be returned.
- apply
public void apply() {
final long startTime = System.currentTimeMillis();
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
if (DEBUG && mcr.wasWritten) {
Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ " applied after " + (System.currentTimeMillis() - startTime)
+ " ms");
}
}
};
QueuedWork.addFinisher(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
};
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
notifyListeners(mcr);
}
the difference between commit is that postWriteRunnable argument to enqueueDiskWrite method is not null,and this runnable will be added to a QueueWork class:
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
- queue
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);
}
}
}
The runnable will be executed in handler with one by one,the lopper of handler is HandlerThread’s looper…After writeToFile executed,the postWriteRunnable will run,this thread only invoke awaitCommit.run(),awaitCommit is awaitCommitRunnable,it containsmcr.writtenToDiskLatch.await();,before this method is invoked,the result may be returned.In enqueueDiskWrite method:
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr, isFromSyncCommit);
}
synchronized code (mLock) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
postWriteRunnable.run();is not included in synchronized codes which can be executed firstly.
Invoking apply method can not ensure that our data is written in a efficient way,it may return result to invoker before data is written in xml file.If apply method is invoked many times in a time,the runnable will be waited in line to be invoked,thus,we should put values in editor in a time and then apply.
In ActivityThread,during operate onStop lifecycle,it will check QueuedWork state when sdk version more than HONEYCOMB(11):
public void handleStopActivity(IBinder token, boolean show, int configChanges,
PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
final ActivityClientRecord r = mActivities.get(token);
r.activity.mConfigChangeFlags |= configChanges;
final StopInfo stopInfo = new StopInfo();
performStopActivityInner(r, stopInfo, show, true /* saveState */, finalStateRequest,
reason);
if (localLOGV) Slog.v(
TAG, "Finishing stop of " + r + ": show=" + show
+ " win=" + r.window);
updateVisibility(r, show);
// Make sure any pending writes are now committed.
if (!r.isPreHoneycomb()) {
QueuedWork.waitToFinish();
}
stopInfo.setActivity(r);
stopInfo.setState(r.state);
stopInfo.setPersistentState(r.persistentState);
pendingActions.setStopInfo(stopInfo);
mSomeActivitiesChanged = true;
}
in QueuedWork.waitToFinish()method,it will iterate all runnable in sWork and then invoke,before all runnable completing it will not return which means onStop method can be stucked before data is written in disk.
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;
}
}
Conclusion
Real Size Picture Address:https://i.loli.net/2019/04/26/5cc2ba881badf.jpg
And article will be synced to wechat blog:Android部落格.