SharedPreference 初始化源码解析
脚本之家 / 编程助手:解决程序员“几乎”所有问题!
脚本之家官方知识库 → 点击立即使用
初始化
sp 内部将数据放到 xml 文件中,加载时首先会将硬盘中文件读取到内存中,这样加快了访问速度
这次从源码开始,看看里面具体做了什么
1 2 3 4 5 6 7 8 9 10 11 12 13 | // 初始化 SharedPreferencesImpl(File file, int mode) { // 文件 mFile = file; //备份文件 .bak 结尾,看看什么时候排上作用,比如恢复数据 mBackupFile = makeBackupFile(file); mMode = mode; mLoaded = false ; mMap = null ; mThrowable = null ; // 从硬盘中读取 startLoadFromDisk(); } |
硬盘中读取文件开了新线程,主要将文件中的内容,转换为Map
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | private void loadFromDisk() { synchronized (mLock) { if (mLoaded) { return ; } // 存在备份文件,删除 file,为什么 if (mBackupFile.exists()) { mFile.delete(); mBackupFile.renameTo(mFile); } } Map<String, Object> map = null ; StructStat stat = null ; Throwable thrown = null ; stat = Os.stat(mFile.getPath()); // 读取流 BufferedInputStream str = null ; try { str = new BufferedInputStream( new FileInputStream(mFile), 16 * 1024 ); // 转为 map map = (Map<String, Object>) XmlUtils.readMapXml(str); } catch (Exception e) { Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e); } finally { // 关闭流 IoUtils.closeQuietly(str); } } |
流程很简单,就是读取硬盘,转换为一个 Map
apply,commit 区别
首先 apply,commit 分别是异步/同步的写入操作,它们都会先写入内存中,也就是更新 Map,不同在于写入到硬盘的时机不同
- commit 先看 commit 做了什么 ,commit 方法将返回一个布尔值,表示结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | @Override public boolean commit() { // 先提交到内存中 MemoryCommitResult mcr = commitToMemory(); // 执行硬盘中的更新 SharedPreferencesImpl. this .enqueueDiskWrite( mcr, null /* sync write on this thread okay */ ); try { mcr.writtenToDiskLatch.await(); } catch (InterruptedException e) { // 提交异常,返回 false return false ; } // 通知监听 notifyListeners(mcr); // 返回结果 return mcr.writeToDiskResult; } |
- apply
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | @Override public void apply() { final long startTime = System.currentTimeMillis(); // 都是一样的,先写到内存 final MemoryCommitResult mcr = commitToMemory(); final Runnable awaitCommit = new Runnable() { @Override public void run() { // mcr.writtenToDiskLatch.await(); } }; // 往 sFinishers 队列中添加,等待执行 QueuedWork.addFinisher(awaitCommit); // 在写完后执行 postWriteRunnable Runnable postWriteRunnable = new Runnable() { @Override public void run() { // 执行 awaitCommit awaitCommit.run(); // sFinishers 队列中移除 QueuedWork.removeFinisher(awaitCommit); } }; // 写入硬盘 SharedPreferencesImpl. this .enqueueDiskWrite(mcr, postWriteRunnable); } |
硬盘中是如何更新的呢
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | private void enqueueDiskWrite( final MemoryCommitResult mcr, final Runnable postWriteRunnable) { final boolean isFromSyncCommit = (postWriteRunnable == null ); // 创建 Runnable 对象 final Runnable writeToDiskRunnable = new Runnable() { @Override public void run() { // 写到文件,这里的锁是 mWritingToDiskLock 对象 synchronized (mWritingToDiskLock) { writeToFile(mcr, isFromSyncCommit); } synchronized (mLock) { mDiskWritesInFlight--; } // 执行 postWriteRunnable, commit 这里为 null // apply 时不为i而空 if (postWriteRunnable != null ) { postWriteRunnable.run(); } } }; // Typical #commit() path with fewer allocations, doing a write on // the current thread. // 是否为同步提交 // 根据 postWriteRunnable 是否为空, commit 这里为 true // apply if (isFromSyncCommit) { boolean wasEmpty = false ; synchronized (mLock) { wasEmpty = mDiskWritesInFlight == 1 ; } if (wasEmpty) { writeToDiskRunnable.run(); return ; } } // 放到队列中执行,内部是一个 HandlerThread,按照队列逐个执行任务 QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit); } |
这里用队列来放任务,应该是要应对多个 commit 情况,这里将所有 commit 往队列里面放,放完后就会执行硬盘的写,apply 也会调用到这里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public static void queue(Runnable work, boolean shouldDelay) { Handler handler = getHandler(); synchronized (sLock) { // 添加到 sWork 队列中 sWork.add(work); // 异步 apply 走这个 if (shouldDelay && sCanDelay) { handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY); } else { // 同步 commit 走这个 handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN); } } } |
apply 的硬盘写入,需要等待 Activity.onPause() 等时机才会执行
读取
读取比写入就简单很多了
- 先查看是否从硬盘加载到了内存,没有就先去加载
- 从内存中读取
1 2 3 4 5 6 7 8 | public String getString(String key, @Nullable String defValue) { synchronized (mLock) { // 检查是否从硬盘加载到了内存,没有就先去加载 awaitLoadedLocked(); String v = (String)mMap.get(key); return v != null ? v : defValue; } } |
如何保证线程安全的
通过 sync 加对象锁,内存读写都是用的同一把锁,所以读写都是线程安全的
数据恢复
存在备份机制
- 对文件进行写入操作,写入成功时,则将备份文件删除
- 如果写入失败,之后重新初始化时,就使用备份文件恢复
SP 与 ANR
由于 Activity.onPause 会执行 apply 的数据落盘,里面是有等待锁的,如果时间太长就会 ANR
小结
从 sp 的初始化,读写方面简单分析了流程,sp 有数据恢复机制这是 mmkv 所欠缺的,但在多进程环境下,sp 是不可靠的
相关链接
以上就是SharedPreference 初始化源码解析的详细内容,更多关于SharedPreference 初始化的资料请关注脚本之家其它相关文章!

微信公众号搜索 “ 脚本之家 ” ,选择关注
程序猿的那些事、送书等活动等着你
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 reterry123@163.com 进行投诉反馈,一经查实,立即处理!
最新评论