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 问题
以上就是SharedPreference 初始化源码解析的详细内容,更多关于SharedPreference 初始化的资料请关注我们其它相关文章!
发表评论