SharedPreferences(简称 SP)是 Android 开发中最资深的轻量级存储方案。虽然在 2026 年我们有很多现代化的替代品(如 DataStore、MMKV),但理解 SP 的原理依然是每个 Android 开发者打好地基的必修课。
一、 核心定义
SP 是一种基于 Key-Value(键值对) 的持久化机制。它底层通过 XML 文件 存储数据,并提供内存缓存以提高读取效率。
存储路径:
/data/data/<package_name>/shared_prefs/xxx.xml支持类型:
boolean,int,float,long,String,Set<String>。
二、基本使用
获取SharedPreferences对象
要想使用SharedPreferences来存储数据,首先需要获取到SharedPreferences 对象。Android中主要提供了2种方法用于得到SharedPreferences 对象。
Context 类中的getSharedPreferences()方法
此方法接收两个参数:
第一个参数用于指定SharedPreferences文件的名称,如果指定的文件不存在则会创建一个,SharedPreferences文件都是存放在
/data/data/<package name>/shared_prefs/目录下的。第二个参数用于指定操作模式,目前只有MODE_PRIVATE这一种模式可选,它是默认的操作模式,和直接传入0效果是相同的,表示只有当前的应用程序才可以对这个SharedPreferences文件进行读写。
Activity 类中的getPreferences()方法
这个方法和Context中的getSharedPreferences() 方法很相似,不过它只接收一个操作模式参数,因为使用这个方法时会自动将当前活动的类名作为SharedPreferences的文件名。
注意:自API级别17以来,已弃用MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE模式。如果使用则抛出SecurityException。
三、 工作原理:内存与磁盘的“双重奏”
SP 的设计思路可以总结为:“一次加载,全程常驻,异步落盘”。
加载阶段: 当你调用
getSharedPreferences()时,系统会启动一个子线程去读取对应的 XML 文件,并将其内容反序列化到一个Map中。读取阶段: 所有的
getXXX()操作都是直接从内存中的Map读取的。这也是为什么 SP 读取速度很快的原因。写入阶段: 通过
Editor对象进行修改。修改会先更新内存中的Map,然后再根据指令决定何时同步到磁盘文件。
四、 关键操作:Commit vs Apply
这是面试中的高频考点。理解这两者的区别是避坑的关键:
commit:直接在主线程中进行写入操作,属于同步提交,返回boolean值。容易阻塞主线程导致ANR。
apply:可能会导致数据丢失,将文件写入操作放到一个Runnable对象中,等待系统在工作线程中调用,属于异步提交,返回void,可能会导致数据丢失。其原理是创建一个等待锁放到QueuedlMork()中,并将真正数据持久化封装成一个任务放到异步队列中执行,任务执行结束会释放锁。而Activity onPuase,onStop以及Scrvice处理 onStop,onStartCommand等情况下,就会执行QueuedWork.waitToFinish()等待QueuedlMork()中的所有问题执行完(主线程等待所有任务)。因此apply 调用次数过多也会容易引起ANR问题。
五、为什么被“嫌弃”?
多进程不友好:MODE_MULTI_PROCESS 也没用。跨进程频繁读写可能导致数据损坏或丢失。
全量加载:初始化时,SP 必须把整个 XML 文件一次性读完。如果你只想读一个
is_first_launch,但文件里存了 2MB 的杂物,你的主线程必须等着这 2MB 数据读取并解析完毕,导致明显的卡顿,也不宜存放大数据。写文件也是全量: 即便你只改了一个布尔值,SP 也会把内存里完整的 Map 重新生成一份 XML 覆盖掉老文件。这种 I/O 开销随着数据量增大而呈指数级增长。
ANR风险高:无论是 commit() 还是 apply() ,可能造成卡顿或者 ANR。
缺乏事务支持: 不支持像数据库那样的局部回滚或复杂的逻辑事务。
六、 最佳实践:如何正确使用 SP
如果你的项目中依然在使用 SP,请务必遵守以下“保命指南”:
瘦身运动: 一个 SP 文件建议只存储几十个简单的配置项。严禁存入长文本、Base64 图片、复杂的 JSON 字符串。
按需拆分: 不要把所有东西都塞进
config.xml。可以根据业务拆分成user_settings.xml,palyer_config.xml等,减少单文件加载压力。不要在主线程读写: 虽然读取走内存,但初始化读取文件是异步的,如果初始化没完成你就调用
get(),主线程会陷入等待。考虑迁移: 如果数据量超过 1KB 或者有高频写入需求,直接换成 MMKV 或 DataStore。
七、操作模式(Operating Modes)
1. 核心模式对比表
2. 为什么 WORLD_READABLE/WRITEABLE 必须死?
在 Android 的早期,跨应用共享数据就像“西部荒野”一样自由,但这带来了巨大的安全隐患:
隐私泄露: 任何 App 只要猜到你的包名,就能读取你的 Token、用户信息。
数据污染: 恶意 App 可以随意篡改你的配置信息,导致你的应用逻辑崩溃。
现在的替代方案:
如果真的需要跨应用共享数据,Android 官方强制要求使用 FileProvider 或 ContentProvider。它们提供了基于 URI 的临时授权机制,比直接开文件权限要安全得多。
3. MODE_MULTI_PROCESS 的“谎言”
很多开发者在做多进程(比如有单独的 :remote 服务进程)时会尝试使用这个模式。
真相: 设置了这个模式,系统只是在每次
getSharedPreferences时重新从磁盘读取一次文件,而不是只读内存缓存。风险: 它依然无法解决并发写入的问题。两个进程同时
commit(),后写的会覆盖先写的,没有任何锁机制保证数据完整性。解决建议: 凡是涉及多进程 KV 存储,请直接放弃 SP,改用 MMKV。
4. 正确的初始化姿势
即便 MODE_PRIVATE 的值是 0,为了代码的可读性和严谨性,建议始终显式传入:
Kotlin
// 2026 年的标准写法
val sp = context.getSharedPreferences("secure_settings", Context.MODE_PRIVATE)
如果你在 API 24 及以上设备上强行使用 MODE_WORLD_READABLE:
Java
// 这行代码在现代设备上运行会直接让 App 崩溃
context.getSharedPreferences("danger_zone", Context.MODE_WORLD_READABLE);
// 报错:java.lang.SecurityException: MODE_WORLD_READABLE no longer supported
总结建议
操作模式的收紧标志着 Android 从“开放获取”走向了“沙盒化管理”。
单进程应用: 锁死
MODE_PRIVATE。跨应用/跨进程: 放弃 SP 的模式逻辑,拥抱
ContentProvider或支持多进程的存储框架(如 MMKV)。
Android数据持久化(二):SharedPreferences
https://lautung.com/archives/LzQwEaYS