软件编程
位置:首页>> 软件编程>> Android编程>> SharedPreference 初始化源码解析

SharedPreference 初始化源码解析

作者:海象  发布时间:2023-11-13 07:00:43 

标签:SharedPreference,初始化,源码解析

初始化

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

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

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

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

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 方法将返回一个布尔值,表示结果


       @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

@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);
       }

硬盘中是如何更新的呢

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 也会调用到这里

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() 等时机才会执行

读取

读取比写入就简单很多了

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

  • 从内存中读取

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 是不可靠的

相关链接

  • 官方也无力回天?Android SharedPreferences的设计与实现

  • 剖析 SharedPreference apply 引起的 ANR 问题

来源:https://juejin.cn/post/7215851270342541372

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com