SharedPreference初始化源码分析

其他教程   发布日期:2024年11月01日   浏览次数:304

本篇内容介绍了“SharedPreference初始化源码分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

初始化

sp 内部将数据放到 xml 文件中,加载时首先会将硬盘中文件读取到内存中,这样加快了访问速度

这次从源码开始,看看里面具体做了什么

  1. // 初始化
  2. SharedPreferencesImpl(File file, int mode) {
  3. // 文件
  4. mFile = file;
  5. //备份文件 .bak 结尾,看看什么时候排上作用,比如恢复数据
  6. mBackupFile = makeBackupFile(file);
  7. mMode = mode;
  8. mLoaded = false;
  9. mMap = null;
  10. mThrowable = null;
  11. // 从硬盘中读取
  12. startLoadFromDisk();
  13. }

硬盘中读取文件开了新线程,主要将文件中的内容,转换为Map

  1. private void loadFromDisk() {
  2. synchronized (mLock) {
  3. if (mLoaded) {
  4. return;
  5. }
  6. // 存在备份文件,删除 file,为什么
  7. if (mBackupFile.exists()) {
  8. mFile.delete();
  9. mBackupFile.renameTo(mFile);
  10. }
  11. }
  12. Map<String, Object> map = null;
  13. StructStat stat = null;
  14. Throwable thrown = null;
  15. stat = Os.stat(mFile.getPath());
  16. // 读取流
  17. BufferedInputStream str = null;
  18. try {
  19. str = new BufferedInputStream(
  20. new FileInputStream(mFile), 16 * 1024);
  21. // 转为 map
  22. map = (Map<String, Object>) XmlUtils.readMapXml(str);
  23. } catch (Exception e) {
  24. Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
  25. } finally {
  26. // 关闭流
  27. IoUtils.closeQuietly(str);
  28. }
  29. }

流程很简单,就是读取硬盘,转换为一个 Map

apply,commit 区别

首先 apply,commit 分别是异步/同步的写入操作,它们都会先写入内存中,也就是更新 Map,不同在于写入到硬盘的时机不同

  • commit 先看 commit 做了什么 ,commit 方法将返回一个布尔值,表示结果

  1. @Override
  2. public boolean commit() {
  3. // 先提交到内存中
  4. MemoryCommitResult mcr = commitToMemory();
  5. // 执行硬盘中的更新
  6. SharedPreferencesImpl.this.enqueueDiskWrite(
  7. mcr, null /* sync write on this thread okay */);
  8. try {
  9. mcr.writtenToDiskLatch.await();
  10. } catch (InterruptedException e) {
  11. // 提交异常,返回 false
  12. return false;
  13. }
  14. // 通知监听
  15. notifyListeners(mcr);
  16. // 返回结果
  17. return mcr.writeToDiskResult;
  18. }
  • apply

  1. @Override
  2. public void apply() {
  3. final long startTime = System.currentTimeMillis();
  4. // 都是一样的,先写到内存
  5. final MemoryCommitResult mcr = commitToMemory();
  6. final Runnable awaitCommit = new Runnable() {
  7. @Override
  8. public void run() {
  9. //
  10. mcr.writtenToDiskLatch.await();
  11. }
  12. };
  13. // 往 sFinishers 队列中添加,等待执行
  14. QueuedWork.addFinisher(awaitCommit);
  15. // 在写完后执行 postWriteRunnable
  16. Runnable postWriteRunnable = new Runnable() {
  17. @Override
  18. public void run() {
  19. // 执行 awaitCommit
  20. awaitCommit.run();
  21. // sFinishers 队列中移除
  22. QueuedWork.removeFinisher(awaitCommit);
  23. }
  24. };
  25. // 写入硬盘
  26. SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
  27. }

硬盘中是如何更新的呢

  1. private void enqueueDiskWrite(final MemoryCommitResult mcr,
  2. final Runnable postWriteRunnable) {
  3. final boolean isFromSyncCommit = (postWriteRunnable == null);
  4. // 创建 Runnable 对象
  5. final Runnable writeToDiskRunnable = new Runnable() {
  6. @Override
  7. public void run() {
  8. // 写到文件,这里的锁是 mWritingToDiskLock 对象
  9. synchronized (mWritingToDiskLock) {
  10. writeToFile(mcr, isFromSyncCommit);
  11. }
  12. synchronized (mLock) {
  13. mDiskWritesInFlight--;
  14. }
  15. // 执行 postWriteRunnable, commit 这里为 null
  16. // apply 时不为i而空
  17. if (postWriteRunnable != null) {
  18. postWriteRunnable.run();
  19. }
  20. }
  21. };
  22. // Typical #commit() path with fewer allocations, doing a write on
  23. // the current thread.
  24. // 是否为同步提交
  25. // 根据 postWriteRunnable 是否为空, commit 这里为 true
  26. // apply
  27. if (isFromSyncCommit) {
  28. boolean wasEmpty = false;
  29. synchronized (mLock) {
  30. wasEmpty = mDiskWritesInFlight == 1;
  31. }
  32. if (wasEmpty) {
  33. writeToDiskRunnable.run();
  34. return;
  35. }
  36. }
  37. // 放到队列中执行,内部是一个 HandlerThread,按照队列逐个执行任务
  38. QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
  39. }

这里用队列来放任务,应该是要应对多个 commit 情况,这里将所有 commit 往队列里面放,放完后就会执行硬盘的写,apply 也会调用到这里

  1. public static void queue(Runnable work, boolean shouldDelay) {
  2. Handler handler = getHandler();
  3. synchronized (sLock) {
  4. // 添加到 sWork 队列中
  5. sWork.add(work);
  6. // 异步 apply 走这个
  7. if (shouldDelay && sCanDelay) {
  8. handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
  9. } else {
  10. // 同步 commit 走这个
  11. handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
  12. }
  13. }
  14. }

apply 的硬盘写入,需要等待 Activity.onPause() 等时机才会执行

读取

读取比写入就简单很多了

  • 先查看是否从硬盘加载到了内存,没有就先去加载

  • 从内存中读取

  1. public String getString(String key, @Nullable String defValue) {
  2. synchronized (mLock) {
  3. // 检查是否从硬盘加载到了内存,没有就先去加载
  4. awaitLoadedLocked();
  5. String v = (String)mMap.get(key);
  6. return v != null ? v : defValue;
  7. }
  8. }

如何保证线程安全的

通过 sync 加对象锁,内存读写都是用的同一把锁,所以读写都是线程安全的

数据恢复

存在备份机制

  • 对文件进行写入操作,写入成功时,则将备份文件删除

  • 如果写入失败,之后重新初始化时,就使用备份文件恢复

SP 与 ANR

由于 Activity.onPause 会执行 apply 的数据落盘,里面是有等待锁的,如果时间太长就会 ANR

以上就是SharedPreference初始化源码分析的详细内容,更多关于SharedPreference初始化源码分析的资料请关注九品源码其它相关文章!