Android SharedPreferences

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 put SharedPreferencesImpl 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 in mapToWriteToDisk 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 in SharedPreferencesImpl 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

image

Real Size Picture Address:https://i.loli.net/2019/04/26/5cc2ba881badf.jpg
And article will be synced to wechat blog:Android部落格.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,980评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,422评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,130评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,553评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,408评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,326评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,720评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,373评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,678评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,722评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,486评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,335评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,738评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,283评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,692评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,893评论 2 335

推荐阅读更多精彩内容

  • pyspark.sql模块 模块上下文 Spark SQL和DataFrames的重要类: pyspark.sql...
    mpro阅读 9,424评论 0 13
  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom阅读 2,673评论 0 3
  • NAME dnsmasq - A lightweight DHCP and caching DNS server....
    ximitc阅读 2,781评论 0 0
  • Part1 1,从本篇文章/音频/视频中我学到的如何了解更多的语法 2,我在本片文章/音频/视频中学到的怦然心动的...
    行管222王淑芳阅读 228评论 2 1
  • 六朝古都,自古繁华。 当车子穿过城门,两旁的梧桐合拢而来, 南京到了。 南京,我来了。 记忆中的南京,和伟岸的长江...
    静静的开水阅读 226评论 0 0