SharedPreferences 部分源码阅读心得

之前一直没有读源码的习惯,直到今年年初才开始慢慢养成多看源码的习惯,不过之前在使用 SharedPreferences 时总觉得为啥要通过 Editor 去写,而在获取写入的值的时候却用 SharedPreferences 直接读出来,这次看过源码之后才知道是为什么。

说到 SharedPreferences,我们肯定想到的是它为我们提供简单数据的存储功能,因为 Android 本身支持多种类型的数据的持久化,比如文件、数据库、SharedPreferences 等等,而如果只是存出一些简单的数据类型,那么 SharedPreferences 是不错的选择,它的存储形式是基于 xml 的,通过键值对的方式保存数据,而且数据可以是私有的。

当然 SharedPreferences 的使用很简单,我们先获取 SharedPreferences 实例,然后通过 Editor 保存和提交数据,而读取的时候可以直接通过 SharedPreferences 的各种 getxxx 的方式读取。

SharedPreferences 本身是接口,里面还有一个 Editor 的接口,它的实现类是 SharedPreferencesImpl,里面实现了 SharedPreferences 各种方法,还是实现了 Editor 接口。

先来说说 SharedPreferences 的获取,有两种方式:
1.通过 PreferenceManager 获取。
2.通过 Context 中的 getSharedPreferences() 获取。
其实第一种也只是封装了一下 Context 中的 getSharedPreferences()。

这里有一点需要注意,我们应该减少 SharedPreferences 的大小,因为它本质是以 xml 存在本地,如果 SharedPreferences 数据量过大,那么初始化 SharedPreferences 时,会减慢读取速度。

这次的源码阅读只阅读了部分,帮我搞清了一些方法,但并没有通读 SharedPreferences 源码,下面就来分享一下我的收获。

先来说说 SharedPreferences 的存储形式,我一开始只是认为 SharedPreferences 将值存入磁盘中,而其实它分为两部分,首先它会先存在内存中一次,然后再提交到磁盘中。

存到内存中的部分.png

截图中的 mMap 的作用有两个,一个是初始化的时候把磁盘中读到的数据赋值给它,然后我们再次提交的新提交的部分时,也会直接存到这里,这也就是先存到内存中一次。

在 SharedPreferences 构造中,会调用 startLoadFromDisk(),这个方法会新起一个线程,执行 loadFromDisk(),这个方法就是读取磁盘中 xml 文件,然后将读取到的值赋给 mMap。

那么接下来来看看我们在 Editor 中提交的时候,代码都做了什么。

SharedPreferencesImpl 有一个内部类 EditorImpl,它实现了 SharedPreferences 中的定义的 Editor 接口,然后当我们获取 Editor 时,就是得到了一个 EditorImpl 实例。

EditorImpl 中的 mModified 是用来存储本次想要提交的内容,各种 putxxx 这里就不在多说了,看看两个重要的提交方式,一个是 commit(),另一个是 apply()。

EditorImpl.png

官方文档中也明确的说明了这两种方式的明确区别,除了返回值,还有提交到磁盘的方式,commit() 是同步,而 apply() 是异步。

但无论提交到磁盘的方式如何,它们都会首先将本次想要提交的内容通过 commitToMemory() 存到内存中,也就是存到 SharedPreferencesImpl 的 mMap 中。通过 MemoryCommitResult 拿到提交内容的内存的结果,然后再将拿到的结果写入磁盘。
final MemoryCommitResult mcr = commitToMemory();

写到磁盘时,都是通过调用 SharedPreferencesImpl 的 enqueueDiskWrite() 来完成的,apply() 调用 enqueueDiskWrite() 时会传入一个实例化的 Runnable,而 commit() 则传 null 值过去,enqueueDiskWrite() 通过传递过来 Runnable 是否为 null,来判断是否以什么方式提交。

    private void enqueueDiskWrite(final MemoryCommitResult mcr,
                                  final Runnable postWriteRunnable) {
        final Runnable writeToDiskRunnable = new Runnable() {
                public void run() {
                    synchronized (mWritingToDiskLock) {
                        writeToFile(mcr);
                    }
                    synchronized (SharedPreferencesImpl.this) {
                        mDiskWritesInFlight--;
                    }
                    if (postWriteRunnable != null) {
                        postWriteRunnable.run();
                    }
                }
            };

        final boolean isFromSyncCommit = (postWriteRunnable == null);

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

        QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
    }

这就是 enqueueDiskWrite() 的源码,如果传递过来的 postWriteRunnable 为 null 则说明同步写入到磁盘中,而非 null 时利用单线程的线程池来将结果写入到磁盘中。

至此一个基本上写入的流程就完成了。

那些读的时候就直接调用 SharedPreferencesImpl 中的 getxxx 就可以了,而这些方法会线程安全的从 mMap 中拿到我们需要的值。

读取时.png

SharedPreferences 源码基本上就读到了这里,基本的一些操作也都梳理清楚,接下来说几点使用时需要注意的地方。

1.单个 SharedPreferences 文件不宜过大,所以最好不使用默认的存储目录,而是根据需求,自己定义存储的文件名,如果单个 xml 文件过大,那么初始化时会影响读取速度。
2.SharedPreferences 是进程内单例,当然它也可以读到别人进程写入的内容,但是会有些小问题,所以跨进程使用 SharedPreferences 还有很多需要注意的地方。
3.提交时如果不需要返回结果(是否提交成功),那么直接调用 commit()。
4.单次提交内容不易过大过多,那么同步可能会阻塞,虽然也可以异步,但是异步其实也只是单线程。
5.它会同时存在磁盘中,也会存在内存中。

所以是否会存在这种问题?内容不足时,回收了 mMap,下次使用时之前的存过的值会返回 null 或者默认值了呢?
如果真的会的话就要重新再去实例化一个 SharedPreferences 来使用了。

以及内容就是本次阅读心得,仅供参考,如果问题,可以留言沟通。

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

推荐阅读更多精彩内容