微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

SharedPreference初始化源码分析

本篇内容介绍了“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

“SharedPreference初始化源码分析”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程之家网站,小编将为大家输出更多高质量的实用文章

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐