SharePreference原理

2023-10-31

SharedPreferences是Android提供的数据持久化的一种手段,适合单进程、小批量的数据存储与访问。因为SharedPreferences的实现是基于单个xml文件实现的,并且,所有持久化数据都是一次性加载到内存,如果数据过大,是不合适采用SharedPreferences存放的。而适用的场景是单进程的原因同样如此,由于Android原生的文件访问并不支持多进程互斥,所以SharePreferences也不支持,如果多个进程更新同一个xml文件,就可能存在同不互斥问题。

SharedPreferences的实现原理之:持久化数据的加载

首先,从基本使用简单看下SharedPreferences的实现原理:

    mSharedPreferences = context.getSharedPreferences("test", Context.MODE_PRIVATE);
    SharedPreferences.Editor editor = mSharedPreferences.edit();
    editor.putString(key, value);
    editor.apply();

context.getSharedPreferences其实就是简单的调用ContextImpl的getSharedPreferences,具体实现如下:

       @Override
public SharedPreferences getSharedPreferences(String name, int mode) {
    SharedPreferencesImpl sp;
    synchronized (ContextImpl.class) {
        if (sSharedPrefs == null) {
            sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>();
        }

        final String packageName = getPackageName();
        ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);
        if (packagePrefs == null) {
            packagePrefs = new ArrayMap<String, SharedPreferencesImpl>();
            sSharedPrefs.put(packageName, packagePrefs);
        }
        sp = packagePrefs.get(name);
        if (sp == null) {
        <!--读取文件-->
            File prefsFile = getSharedPrefsFile(name);
            sp = new SharedPreferencesImpl(prefsFile, mode);
            <!--缓存sp对象-->
            packagePrefs.put(name, sp);
            return sp;
        }
    }
    <!--跨进程同步问题-->
    if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
        getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
        sp.startReloadIfChangedUnexpectedly();
    }
    return sp;
}

以上代码非常简单,直接描述下来就是先去内存中查询与xml对应的SharePreferences是否已经被创建加载,如果没有那么该创建就创建,该加载就加载,在加载之后,要将所有的key-value保存到内幕才能中去,当然,如果首次访问,可能连xml文件都不存在,那么还需要创建xml文件,与SharePreferences对应的xml文件位置一般都在/data/data/包名/shared_prefs目录下,后缀一定是.xml,数据存储样式如下:

这里面数据的加载的地方需要看下,比如,SharePreferences数据的加载是同步还是异步?数据加载是new SharedPreferencesImpl对象时候开始的,

 SharedPreferencesImpl(File file, int mode) {
    mFile = file;
    mBackupFile = makeBackupFile(file);
    mMode = mode;
    mLoaded = false;
    mMap = null;
    startLoadFromDisk();
}

startLoadFromDisk很简单,就是读取xml配置,如果其他线程想要在读取之前就是用的话,就会被阻塞,一直wait等待,直到数据读取完成。

    private void loadFromDiskLocked() {
   ...
    Map map = null;
    StructStat stat = null;
    try {
        stat = Os.stat(mFile.getPath());
        if (mFile.canRead()) {
            BufferedInputStream str = null;
            try {
    <!--读取xml中配置-->
                str = new BufferedInputStream(
                        new FileInputStream(mFile), 16*1024);
                map = XmlUtils.readMapXml(str);
            }...
    mLoaded = true;
    ...
    <!--唤起其他等待线程-->
    notifyAll();
}

可以看到其实就是直接使用xml解析工具XmlUtils,直接在当前线程读取xml文件,所以,如果xml文件稍大,尽量不要在主线程读取,读取完成之后,xml中的配置项都会被加载到内存,再次访问的时候,其实访问的是内存缓存。

SharedPreferences的实现原理之:持久化数据的更新

通常更新SharedPreferences的时候是首先获取一个SharedPreferences.Editor,利用它缓存一批操作,之后当做事务提交,有点类似于数据库的批量更新:

    SharedPreferences.Editor editor = mSharedPreferences.edit();
    editor.putString(key1, value1);
    editor.putString(key2, value2);
    editor.putString(key3, value3);
    editor.apply();//或者commit

Editor是一个接口,这里的实现是一个EditorImpl对象,它首先批量预处理更新操作,之后再提交更新,在提交事务的时候有两种方式,一种是apply,另一种commit,两者的区别在于:何时将数据持久化到xml文件,前者是异步的,后者是同步的。Google推荐使用前一种,因为,就单进程而言,只要保证内存缓存正确就能保证运行时数据的正确性,而持久化,不必太及时,这种手段在Android中使用还是很常见的,比如权限的更新也是这样,况且,Google并不希望SharePreferences用于多进程,因为不安全,手下卡一下apply与commit的区别

    public void apply() {
    <!--添加到内存-->
        final MemoryCommitResult mcr = commitToMemory();
        final Runnable awaitCommit = new Runnable() {
                public void run() {
                    try {
                        mcr.writtenToDiskLatch.await();
                    } catch (InterruptedException ignored) {
                    }
                }
            };

        QueuedWork.add(awaitCommit);
        Runnable postWriteRunnable = new Runnable() {
                public void run() {
                    awaitCommit.run();
                    QueuedWork.remove(awaitCommit);
                }
            };
        <!--延迟写入到xml文件-->
        SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
        <!--通知数据变化-->
        notifyListeners(mcr);
    }
 
 public boolean commit() {
        MemoryCommitResult mcr = commitToMemory();
        SharedPreferencesImpl.this.enqueueDiskWrite(
            mcr, null /* sync write on this thread okay */);
        try {
            mcr.writtenToDiskLatch.await();
        } catch (InterruptedException e) {
            return false;
        }
        notifyListeners(mcr);
        return mcr.writeToDiskResult;
    }     

从上面可以看出两者最后都是先调用commitToMemory,将更改提交到内存,在这一点上两者是一致的,之后又都调用了enqueueDiskWrite进行数据持久化任务,不过commit函数一般会在当前线程直接写文件,而apply则提交一个事务到已给线程池,之后直接返回,实现如下:

 private void enqueueDiskWrite(final MemoryCommitResult mcr,
                              final Runnable postWriteRunnable) {
    final Runnable writeToDiskRunnable = new Runnable() {
            public void run() {
                synchronized (mWritingToDiskLock) {
                    writeToFile(mcr);
                }
                synchronized (SharedPreferencesImpl.this) {
                    mDiskWritesInFlight--;
                }
                if (postWriteRunnable != null) {
                    postWriteRunnable.run();
                }
            }
        };
   final boolean isFromSyncCommit = (postWriteRunnable == null);
    if (isFromSyncCommit) {
        boolean wasEmpty = false;
        synchronized (SharedPreferencesImpl.this) {
            wasEmpty = mDiskWritesInFlight == 1;
        }
        <!--如果没有其他线程在写文件,直接在当前线程执行-->
        if (wasEmpty) {
            writeToDiskRunnable.run();
            return;
        }
    }
   QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
}

不过如果有线程在写文件,那么就不能直接写,这个时候就跟apply函数一致了,但是,如果直观说两者的区别的话,直接说commit同步,而apply异步应该也是没有多大问题的

SharePreferences多进程使用问题

SharePreferences在新建的有个mode参数,可以指定它的加载模式,MODE_MULTI_PROCESS是Google提供的一个多进程模式,但是这种模式并不是我们说的支持多进程同步更新等,它的作用只会在getSharedPreferences的时候,才会重新从xml重加载,如果我们在一个进程中更新xml,但是没有通知另一个进程,那么另一个进程的SharePreferences是不会自动更新的。

@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
    SharedPreferencesImpl sp;
    ...
    if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
        getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
        // If somebody else (some other process) changed the prefs
        // file behind our back, we reload it.  This has been the
        // historical (if undocumented) behavior.
        sp.startReloadIfChangedUnexpectedly();
    }
    return sp;
}

也就是说MODE_MULTI_PROCESS只是个鸡肋Flag,对于多进程的支持几乎为0,下面是Google文档,简而言之,就是:不要用

MODE_MULTI_PROCESS does not work reliably in some versions of Android, and furthermore does not provide any mechanism for reconciling concurrent modifications across processes. Applications should not attempt to use it. Instead, they should use an explicit cross-process data management approach such as ContentProvider。

响应的Google为多进程提供了一个数据同步互斥方案,那就是基于Binder实现的ContentProvider,关于ContentProvider后文分析。

SharedPreferences的mode解释

Context.MODE_PRIVATE:只能被本应用程序读写

Context.MODE_WORLD_READBALE:能被其他程序读取、但是不能写

Context.MODE_WORLD_WRITEABLE:能被其他程序读、写

Context.MODE_APPEND:如果文件已存在,然后将数据写入现有文件的末尾,而不是擦除它。

Context.MODE_MULTI_PROCESS:弃用,无法可靠工作在某些版本的Android,而且不提供任何

协调跨进程。应用程序不应该尝试使用它

Context.MODE_ENABLE_WRITE_AHEAD_LOGGING:设置后,数据库将以预写方式打开,默认情况下启用日志记录

Context.MODE_NO_LOCALIZED_COLLATORS:设置后,打开数据库时不支持本地化拼贴器。

总结

1. SharePreferences是Android基于xml实现的一种数据持久化手段。

2. sp不适合存储过大的数据,因为它一直保存在内存中,数据过大容易造成内存溢出。

3. SharePreferences的commit与apply一个是同步一个是异步(大部分场景下),sp的commit方法是直接在当前线程执行文件写入      操作,而apply方法是在工作线程执行文件写入,尽可能使用apply,因为不会阻塞当前线程。

4. sp并不支持跨进程,因为它不能保证更新本地数据后被另一个进程所知道,而且跨进程的操作标记已经被弃用。

5. sp批量更改数据时,只需要保留最后一个apply即可,避免添加多余的写文件任务。

6. 每个sp存储的键值对不宜过多,否则在加载文件数据到内存时会耗时过长,而阻塞sp的相关getput方法,造成ui卡顿。

7. 频繁更改的配置项和不常更改的配置项应该分开为不同的sp存放,避免不必要的io操作。

转载地址:SharePreference原理及跨进程数据共享的问题 - 简书

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SharePreference原理 的相关文章

  • 在 Eclipse 中删除空块之前的新行

    我更喜欢奥尔曼式 http en wikipedia org wiki Brace style Allman style大括号 例如 if foo magical prancing unicorn stuff 而不是 if foo unma
  • 生成一定长度的所有排列

    假设我们有一个字母表 abcdefghiklimnop 如何以有效的方式以五个一组的形式重复该字母表来递归生成排列 几天来我一直在为此苦苦挣扎 任何反馈都会有帮助 本质上这与 生成给定字符串的所有排列 https stackoverflow
  • 我怎样才能实现CoverFlow视图[关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我想用点线布局实现溢出视图 目前我正在使用 polidea 封面流库 URL github https
  • 在异步请求中使用超时回调

    我之前问过这个问题 但我将用提出的解决方案来完成这个问题 并提出另一个问题 我正在使用这个类来进行异步网络请求 http msdn microsoft com en us library system net webrequest aspx
  • 如何获取 JDBC 中 UPDATE 查询影响的所有行?

    我有一项任务需要使用更新记录PreparedStatement 一旦记录被更新 我们知道更新查询返回计数 即受影响的行数 但是 我想要的不是计数 而是受更新查询影响的行作为响应 或者至少是受影响的行的 id 值列表 这是我的更新查询 UPD
  • 如何在 JASPIC 中保存经过身份验证的用户?

    我开发了一个安全认证模块 SAM 并实现了validateRequest方法 我还有一个简单的 Web 应用程序配置为使用此 SAM In my validateRequest方法 我检查 clientSubject 并设置一个Caller
  • 在 Kotlin 中声明静态属性?

    My Java code public class Common public static ModelPengguna currentModelPengguna public class Common companion object v
  • 如何使用 AffineTransform.quadrantRotate 旋转位图?

    我想旋转一个bitmap关于它的中心点 然后将其绘制成更大的图形上下文 位图是40x40 pixels 图形上下文是500x500 pixels 这就是我正在做的 BufferedImage bi new BufferedImage 500
  • log4j.properties 在 Wildfly 上无法正常工作

    我的类路径中有一个 log4j properties 文件 它位于 APP XX jar log4j properties 位置 我注意到在ear文件中我还可以在lib文件夹中找到log4j 1 2 17 jar 但无论我在 log4j p
  • 线程数组?

    所以我在理解如何避免线程的顺序执行时遇到了问题 我试图创建一个线程数组并在单独的循环中执行 start 和 join 函数 这是我现在拥有的代码示例 private static int w static class wThreads im
  • 在 Spring MVC 中将请求写入文件

    我希望能够将整个请求写入 Spring MVC 控制器中的文件 我已尝试以下操作 但即使我使用大量参数发出 POST 请求 文件也始终为空 RequestMapping method RequestMethod POST value pay
  • 难以理解 通配符

    我有一个非常基本的问题 下面的代码无法编译 假设 Apple Extends Fruit List
  • 如何更改 JAX-WS Web 服务的地址位置

    我们目前已经公开了具有以下 URL 的 JAX RPC Web 服务 http xx xx xx xx myservice MYGatewaySoapHttpPort wsdl http xx xx xx xx myservice MYGa
  • 为什么/何时应该使用泛型方法?

    学习Java的时候遇到过通用方法 public
  • Android 模拟器提示和技巧 [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 如何像UCBrowser一样使用webview打开url

    我是安卓新手 我正在尝试制作一个示例应用程序来在 webview 中打开网站 问题是 网站以桌面模式打开 如何才能像UC浏览器 手机模式 一样打开网站 尝试这个 它应该有效 webview1 getSettings setJavaScrip
  • 如何获取视图到手机底部的距离?

    如果我在布局上有某个视图 ImageView 例如 是否可以找到View的下边框到手机屏幕底部的距离 Thanks instantiate DisplayMetrics DisplayMetrics dm new DisplayMetric
  • 无法 ACTION_VIEW 外部存储上的文件

    我的 Android 手机的外部存储中有一个文件 在本例中是模拟的 知道通往它的路径和 或拥有File代表它的对象 我如何使用Intent在适当的应用程序中打开它 我尝试的第一件事是 startActivity new Intent Int
  • JPA ManyToMany 产生的空联接表

    我有一个应用程序 其中我尝试使用 Hibernate 作为 JPA 提供程序来实现两个实体之间的多对多关系 我正在尝试的例子是一个单向的 其中一个相机可以有多个镜头 而镜头可以安装到多个相机中 以下是我的实体类 只需粘贴其中的相关部分 Ca
  • MyBatis 枚举的使用

    我知道以前有人问过这个问题 但我无法根据迄今为止找到的信息实施解决方案 所以也许有人可以向我解释一下 我有一个表 状态 它有两列 id 和 name id是PK 我不想使用 POJO Status 而是使用枚举 我创建了这样一个枚举 如下所

随机推荐

  • Unity基础 单点和多点触摸

    总结一下触摸事件 最简单的鼠标单点点击触摸 也可以在安卓和IOS上面实现触屏的操作 OnMouseDown 鼠标按下 点击的一瞬间触发 OnMouseDrag 鼠标持续按住拖动触发 OnMouseEnter 鼠标经过物体时触发 OnMous
  • [PyTroch系列-2]:Facebook PyTroch简介、生态环境、开发架构、软件架构

    作者主页 文火冰糖的硅基工坊 文火冰糖 王文兵 的博客 文火冰糖的硅基工坊 CSDN博客 本文网址 PyTroch系列 2 Facebook PyTroch简介 生态环境 开发架构 软件架构 文火冰糖 王文兵 的博客 CSDN博客 第1章
  • git远程仓库新建了分支,但是vscode分支管理里还没有这个分支怎么办

    git远程仓库新建了分支 但是vscode分支管理里还没有这个分支怎么办 git终端输入以下命令 成功后再次查看 就有了 git fetch origin 分支名
  • Jenkins基础篇--Docker容器部署

    容器部署Jenkins的优势 1 安全 容器之间的进程是相互隔离的 单独容器环境稳定 宿主机中环境变量的修改 不容易影响容器的运行结果 2 更轻松地部署和扩展 容器可方便迁移 一次交付 多次利用 容器可将打包好的jenkins环境迁移到其他
  • spark-sql提交参数详解整理

    1 spark任务提交 当SPARK SQL开发完成后需要将其提交到大数据平台上去跑 提交的时候需要对要使用的资源参数进行设置 目的 a 让任务在正确的环境下运行 b 根据任务自身情况 设置合理参数 提高运行效率 2 设置参数说明 2 1
  • python是从abc发展_Python 简介

    Python 简介 Python 是一门解释型语言 因为无需编译和链接 你可以在程序开发中节省宝贵的时间 Python 解释器可以交互的使用 这使得试验语言的特性 编写临时程序或在自底向上的程序开发中测试方法非常容易 Python 是面向对
  • 简单易懂矩阵螺旋打印

    简单易懂矩阵螺旋打印 C语言 给定一个 m 行 n 列的矩阵 请按照顺时针螺旋的顺序输出矩阵中所有的元素 输入格式 首先在第一行输入 2 个整数 分别对应题目描述中的 mm 和 nn 1 leq m n leq 1001 m n 100 之
  • 设置入校时间字段的有效性规则为_2012年计算机二级Access第二十六套上机试题及答案详解...

    1 基本操作题 在考生文件夹下 存在一个数据库文件 samp1 mdb 和一个图像文件 photo bmp 在数据库文件中已经建立一个表对象 tStud 试按以下操作要求 完成各种操作 1 设置 ID 字段为主键 并设置 ID 字段的相应属
  • UE4 C++ 用蓝图调用C++里定义的变量、方法

    UE4 C 用蓝图调用C 里定义的变量 方法 这是一个Object的C 脚本 h UCLASS Blueprintable 可被蓝图继续 class BASICTRAINING API UMyObject public UObject GE
  • 企业微信接入自研小程序流程

    一 背景 企业微信是企业内部办公常用的即时通讯工具 可以作为企业内部工作的枢纽 例如 重要内容通知 重要应用的集成等 二 自研程序接入企业微信配置 1 登录企业微信管理后台https work weixin qq com 2 找到应用管理
  • java -jar xxx.jar中没有主清单属性

    使用Spring Initailizr创建的项目 使用mvn打包后 java jar xxx jar显示xxx jar中没有主清单属性 去掉标签即可
  • Java实现阿里云短信发送功能(保姆级!!!搞懂短信功能,这一篇就够了!)

    目录 一 准备工作 1 功能如何切入 2 为什么要用阿里云来实现 二 阿里云部分 三 代码部分 OK 分享结束 收 一 准备工作 1 功能如何切入 第一步 分析业务需求 想要实现短信通知功能那就要有短信的收发双方 而手机上的短信功能需要占用
  • FFmpeg滤镜:制作图片视频流

    iPhone相册有个 为你推荐 功能 它会挑选一些照片形成一个主题 点击后可以像视频一样播放 那么 怎样才能把多张照片转成一个视频文件呢 使用FFmpeg可以这么来做 ffmpeg f image2 framerate 0 5 i D MT
  • Qt使用QSplitter实现分割窗口

    分割窗口在应用程序中经常用到 它可以灵活分布窗口布局 经常用于类似文件资源管理器的窗口设计中 然后抱着这样的想法简单的实现了下 cpp view plain copy print main cpp include
  • Android之通过BaseAdapter自定义适配器的使用

    通过BaseAdapter创建自定义适配器 在所有的适配器中 通过BaseAdapter定义的适配器非常好用 可以自定义ListView每行布局的样式 使用非常的广泛 是开发过程中必不可少的 下面看一个效果图 接下来一起来实现聊天列表 1
  • 在 ROS 中使用 Protobuf 替代 ros msg

    转自 https segmentfault com a 1190000012734275 Background 做 ROS 相关开发的 应该都知道 ros msg 有个非常大的槽点 ros msg 扩展性较差 即如果 msg 的字段发生变化
  • JAVA 输出一个会动的爱心

    以下是 Java 代码 可以在控制台输出一个会动的爱心 public class Love public static void main String args throws InterruptedException while true
  • 看完百度文心一言的魔性作图,我头都笑掉了...

    近日看到网友们用百度文心一言来作图 看了后我都愣住了 1 AI 作画 车水马龙 2 AI 作画 驴肉火烧 3 AI 作画 唐伯虎点秋香 4 AI 作画 鱼香肉丝 5 AI 作画 胸有成竹 6 AI 作画 夫妻肺片 7 AI 作画 红烧狮子头
  • 【GUI】Python图形界面(一)

    Python图形界面 一 第一个界面 1 了解模块代码的组成 导入库 PySimpleGUI 定义布局 确定行数 创建窗口 事件循环 关闭窗口 1 导入库 import PySimpleGUI as sg 2 定义布局 确定行数 layou
  • SharePreference原理

    SharedPreferences是Android提供的数据持久化的一种手段 适合单进程 小批量的数据存储与访问 因为SharedPreferences的实现是基于单个xml文件实现的 并且 所有持久化数据都是一次性加载到内存 如果数据过大