Android随笔-SharedPreferences

一、SharedPreferences是什么,作用是什么

SharedPreferences(简称 SP)是 Android 提供的轻量级键值对(Key-Value)存储方案,数据最终以 XML 文件形式持久化在:

复制代码
/data/data/<包名>/shared_prefs/<文件名>.xml

典型用途:保存用户设置、登录 token、首次启动标记、简单配置等小体量、结构简单 的数据。不适合存大量数据或复杂结构。

文件内容:

xml 复制代码
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="username">songyan</string>
    <int name="launch_count" value="17" />
    <boolean name="is_first_launch" value="false" />
</map>

二、基本使用

获取实例(三种方式)

java 复制代码
// 1. 通过 Context,文件名自定义,最常用
SharedPreferences sp = context.getSharedPreferences("config", Context.MODE_PRIVATE);

// 2. Activity 专属,文件名默认就是 Activity 的类名
SharedPreferences sp2 = getPreferences(Context.MODE_PRIVATE);

// 3. 默认文件(包名_preferences),配合 Settings 页面用
SharedPreferences sp3 = PreferenceManager.getDefaultSharedPreferences(context);

MODE_PRIVATE 是唯一能用的模式,MODE_WORLD_READABLE、MODE_WORLD_WRITEABLE 在Android N 已废弃并抛异常(直接暴露数据给其他 App,安全性太差)。

写入

java 复制代码
sp.edit()
  .putString("username", "songyan")
  .putInt("launch_count", 17)
  .apply();   // 或 commit()

读取

java 复制代码
String name = sp.getString("username", "默认值");
int count = sp.getInt("launch_count", 0);

三、apply() 和 commit() 的区别(高频面试点)

commit() apply()
写磁盘 同步,在调用线程直接写 异步,交给后台队列写
返回值 有,boolean 表示成功与否
阻塞主线程 会(有 ANR 风险) 写入本身不会
失败通知 可感知 无感知

但注意:apply() 并不是完全没有 ANR 风险, 原因看下面的原理部分。

四、内部原理(源码视角)

1. 进程内单例缓存

ContextImpl.getSharedPreferences() 内部维护了一个静态 ArrayMap

复制代码
ArrayMap<String(包名), ArrayMap<File, SharedPreferencesImpl>>

同一个文件在整个进程内只有一个 SharedPreferencesImpl 实例,第一次创建后常驻内存。

2. 首次加载:异步读文件 + 全量进内存

创建 SharedPreferencesImpl 时会开子线程执行 loadFromFile(),把 XML 全量解析 进一个 HashMap 。之后所有 getXxx() 都直接读这个 HashMap------读操作没有磁盘 IO ,这就是 SP 快的原因。

代价是:

  • 文件越大,首次加载越慢、内存占用越高 → SP 不要存大数据
  • 如果读操作发生在文件加载完成前,getXxx() 会在 awaitLoadedLocked() 里阻塞等待 ------所以不要在主线程第一次访问一个很大的 SP 文件

3. apply() 的写流程

复制代码
apply()
 ├── 内存:同步写入 HashMap,立即生效
 └── 磁盘:构造一个写任务,丢进 QueuedWork 队列异步执行

关键点来了:系统在 ActivityThread.handleStopActivity()、handlePauseActivity()、handleServiceArgs() 等时机,会调用 QueuedWork.waitToFinish() 等待所有 pending 的写任务完成

也就是说 :如果你在主线程频繁 apply(),磁盘写入任务堆积,Activity 跳转/停止、Service 处理时主线程会被卡住等 IO------这是线上 ANR 的经典来源之一。这也是为什么很多团队埋点统计 "waitToFinish" 耗时。

4. commit() 的写流程

内存同步写入后,直接在调用线程执行 writeToFile(),写完返回结果。

5. 写文件的原子性

写磁盘时会先写到 .bak 备份逻辑(老版本)/ 先写临时文件再 rename 的机制保证:写一半崩溃了,还有备份兜底,不会得到一个残缺的 XML。

五、监听器的一个坑

java 复制代码
SharedPreferences.OnSharedPreferenceChangeListener listener = (sp, key) -> {
    Log.d("SP", key + " 变了");
};
sp.registerOnSharedPreferenceChangeListener(listener);

SP 内部用 WeakHashMap 存监听器------如果你注册的是一个没有强引用持有的匿名内部类/局部变量 ,GC 之后回调就再也不会触发了。必须把 listener 存成成员变量,不再用时 unregisterOnSharedPreferenceChangeListener()。

六、多进程问题

MODE_MULTI_PROCESS 早已被废弃且本身就不可靠 :SP 的内存缓存只在各自进程内生效,A 进程改了,B 进程的内存 HashMap 不会同步更新,数据会错乱。

跨进程共享数据的正确姿势:

  • ContentProvider
  • MMKV(腾讯开源,支持多进程)
  • DataStore + 多进程方案(有限支持)

七、SP 的缺陷总结

  1. 全量加载进内存,大文件首读卡顿、占内存;
  2. apply() 的异步写可能引发主线程 ANR;
  3. 不支持多进程;
  4. 明文 XML 存储,敏感数据(token、密码)直接放不安全(需要加密或用 Keystore);
  5. 无类型安全,key 写错、类型存错都是运行时才炸。

八、现代替代方案

Jetpack DataStore(官方钦定的接班人)

  • 基于 Kotlin 协程 + Flow,所有 IO 默认在后台线程,从机制上杜绝 ANR;
  • 事务性更新,保证数据一致性;
  • 两种形态:Preferences DataStore(键值对,对标 SP)和 Proto DataStore(protobuf,类型安全)
kotlin 复制代码
// Preferences DataStore 示例
val Context.dataStore by preferencesDataStore(name = "settings")
val KEY_COUNT = intPreferencesKey("launch_count")

// 写
context.dataStore.edit { it[KEY_COUNT] = 17 }

// 读
context.dataStore.data.map { it[KEY_COUNT] ?: 0 }.collect { count -> ... }

MMKV(腾讯):基于 mmap 内存映射,性能极高,支持多进程,大厂 App 用得很多。

九、日常注意事项

  • 只存小数据,单文件建议别超过几十 KB,配置多的话按业务拆多个文件;
  • 写入统一用 apply(),但避免在主线程高频连续写(比如列表滚动时实时存进度);
  • 首次读写提前到后台线程或启动阶段做,别卡在主线程关键路径;
  • 敏感信息别明文存;
  • 新项目直接上 DataStore,老项目渐进迁移(官方提供 SharedPreferencesMigration)。
相关推荐
茶栀(*´I`*)2 小时前
Android 测试入门指南:ADB 基础配置与常用设备管理命令解析
android·adb
gxgldyh4 小时前
Android Framework源码解析(七):BootAnimation 启动流程解析——开机动画是如何显示出来的?
android
达达尼昂5 小时前
在 Claude Cowork 中用好 Claude Fable 5
android·人工智能·后端
sTone873756 小时前
写时复制COW的第一性理解
android·c++·flutter
sTone873756 小时前
Zygote的第一性理解
android
sTone873756 小时前
类RN框架通过Service暴露卡片渲染能力给AI Chat
android·react native
灯火不休6 小时前
Android Studio & Flutter 构建命令完全指南
android
杉氧6 小时前
Ktor 全栈之路 (5):JWT 认证全流程实战 —— 打造安全通信闭环
android·架构·kotlin
️学习的小王6 小时前
Python + MySQL 学生信息管理系统实战教程
android·python·mysql