SharedPreference 初始化源码解析

 更新时间:2023年03月30日 09:12:38   作者:海象  
这篇文章主要为大家介绍了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 初始化的资料请关注脚本之家其它相关文章!

蓄力AI

微信公众号搜索 “ 脚本之家 ” ,选择关注

程序猿的那些事、送书等活动等着你

原文链接:https://juejin.cn/post/7215851270342541372

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 reterry123@163.com 进行投诉反馈,一经查实,立即处理!

相关文章

  • Android Glide的简单使用

    Android Glide的简单使用

    本文主要介绍了Glide简单使用。具有一定的参考价值,下面跟着小编一起来看下吧
    2017-01-01
  • Android Framework Window体系教程

    Android Framework Window体系教程

    这篇文章主要为大家介绍了Android Framework Window体系教程,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • Android自适应不同屏幕大小的全部方法

    Android自适应不同屏幕大小的全部方法

    这篇文章主要介绍了Android自适应不同屏幕大小的全部方法,具有很好的参考价值,希望对大家有所帮助,一起跟随小编过来看看吧
    2018-05-05
  • Android实现文字消除效果

    Android实现文字消除效果

    由于项目和语音识别相关,有时候人在不经意间交流的无效音频会被识别出来,并展示于界面,为了美观,客户要求我们将这些无效的识别文本用一个从右到左的动画给清除,于是便有了下述的技术实现。感兴趣的朋友可以参考下
    2021-06-06
  • Android游戏开发学习②焰火绽放效果实现方法

    Android游戏开发学习②焰火绽放效果实现方法

    这篇文章主要介绍了Android游戏开发学习②焰火绽放效果实现方法,以实例形式详细分析了Android中粒子对象类Particle类和粒子集合类ParticleSet类及物理引擎ParticleThread类 的使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-10-10
  • Android 对话框 Dialog使用实例讲解

    Android 对话框 Dialog使用实例讲解

    对话框是在当前的页面之上弹出的小窗口, 用于显示一些重要的提示信息, 提示用户的输入,确认信息,或显示某种状态.如 : 显示进度条对话框, 退出提示.接下来通过本文给大家介绍android dialog对话框知识,感兴趣的朋友一起看看吧
    2016-09-09
  • android Palette调色板使用详解

    android Palette调色板使用详解

    本篇文章主要介绍了android Palette调色板使用详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-10-10
  • 100行Android代码轻松实现带动画柱状图

    100行Android代码轻松实现带动画柱状图

    这篇文章主要教大家通过100行Android代码轻松实现带动画柱状图,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-08-08
  • Android 如何采用Lame编码器编码mp3文件

    Android 如何采用Lame编码器编码mp3文件

    这篇文章主要介绍了Android 如何采用Lame编码器编码mp3文件,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下
    2021-03-03
  • Android下拉阻尼效果实现原理及简单实例

    Android下拉阻尼效果实现原理及简单实例

    这篇文章主要为大家详细介绍了Android下拉阻尼效果实现原理及简单实例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-06-06

最新评论