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 putSharedPreferencesImpl
object 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 inmapToWriteToDisk
object 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 inSharedPreferencesImpl
class 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 contains
mcr.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;
}
}
- getString
Getting data can be blocked before initialized with loading from disk,then return data from Map.
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
private void awaitLoadedLocked() {
if (!mLoaded) {
// Raise an explicit StrictMode onReadFromDisk for this
// thread, since the real read will be in a different
// thread and otherwise ignored by StrictMode.
BlockGuard.getThreadPolicy().onReadFromDisk();
}
while (!mLoaded) {
try {
mLock.wait();//loadDataFromDisk completed will notifyAll
} catch (InterruptedException unused) {
}
}
if (mThrowable != null) {
throw new IllegalStateException(mThrowable);
}
}
Conclusion
imageReal Size Picture Address:https://i.loli.net/2019/04/26/5cc2ba881badf.jpg
And article will be synced to wechat blog:Android部落格.
网友评论