承接ViewModel和LiveData的MVVM体系,本章我们解决轻量配置存储的现代化替换:彻底淘汰SharedPrefrernces(简称SP),用Jetpack DataStore实现记住密码、用户设置、登录状态等轻量数据持久化。
一、为什么必须淘汰SharedPreferences
SP是Android早期的轻量存储方案,在现代开发中存在多个不可忽视的硬伤,也是官方明确推荐替换的老旧API:
1. 主线程ANR风险
- SP首次启动会全量把整个XML文件加载到内存,文件越大,阻塞主线程时间越长,容易造成页面卡顿甚至ANR(Application Not Responding(应用程序无响应))
- commit()是同步写入,直接阻塞调用线程;apply()看似异步,但在ActivityonStop、Service销毁等生命周期节点,系统会强制等待SP写入完成,依然大概率触发ANR
2. 线程不安全
SP内部读写锁粒度粗,多线程并发读写时容易出现脏读、数据丢失,性能很差。
结合 SharedPreferences(SP)来说,“内部读写锁粒度粗”的意思是:SP 内部只用了一把“大铁锁”,不管是读取任何键,还是写入任何键,所有线程都得排队抢这一把锁。
3. 无类型安全
所有key都是纯字符串,存的是int、取的时候手滑写成getString,编译不会报错,运行时才会出现问题,维护成本高。
4. 不支持事务与原子批量修改
批量修改多个配置只能多次edit()提交,无法保证原子性;中途失败会出现部分修改成功、部分失败的不一致状态。
5. 不支持响应式
数据变更后无法自动通知观察者,必须手动重新读取或注册监听,和MVVM的“数据驱动视图”理念完全脱节。
6. 功能局限
仅支持基础类型 + StringSet,无法便捷存储结构化对象;不支持数据迁移、异常恢复,文件损坏后全部失效。
二、DataStore是什么 & 两种实现类型
DataStore 是 Jetpack官方推出的新一代轻量持久化存储组件,完全替代SP,基于异步流式设计,线程安全、类型安全,天然配合LiveData / Flow,完美融入MVVM架构。
官方提供两种实现,对应不同场景:
| 方案 | 存储形式 | 适用场景 |
| Preferences DataStore | 键值对存储,和SP用法类似 | 替代原有SP,存储简单配置 |
| Proto DataStore | 基于Protocol Buffers,存储结构化对象 | 存储复杂、固定结构的数据,强类型安全 |
由于Proto DataStore的学习成本稍高,需要定义 proto 结构,因此下面所有讲解均以 Preferences DataStore 为主。
三、Preferences DataStore 核心特性
- 完全异步无阻塞:读写默认在 IO 线程执行,基于响应式流,不会阻塞主线程,从根源避免 SP 的 ANR 问题
- 线程安全原子操作:写入操作具备原子性,多线程并发安全,不会出现脏数据
- 天然响应式:数据变化自动推送更新,配合 LiveData 可直接驱动 UI,无需手动监听
- 类型安全:Key 自带类型约束,编译期就能检查类型错误
- 支持事务:批量修改可在一个事务内完成,保证原子性
- 官方迁移支持:一键从旧 SP 迁移数据,无需手动读写适配
- Jetpack 生态打通:和 ViewModel、Lifecycle、协程 / RxJava 无缝配合,符合现代 Android 架构
四、使用步骤
DataStore原生基于Kotlin协程 + Flow,Java环境官方提供了RxJava适配版本,我们可以通过RxJava + LiveData转换,无缝融入Java的MVVM体系。
1. 添加依赖
在模块级中引入依赖,这里用RxJava3版本:
dependencies { // Preferences DataStore RxJava3 版本(Java 环境专用) implementation("androidx.datastore:datastore-preferences-rxjava3:1.1.1") implementation("io.reactivex.rxjava3:rxjava:3.1.8") // 可选:RxJava 转 LiveData,方便 MVVM 架构使用 implementation("androidx.lifecycle:lifecycle-reactivestreams:2.6.2") }2. 统一管理key + 单例DataStore
DataStore实例必须单例,不能每个页面重复创建;建议封装在一个管理类 / Repository中,Key统一定义,避免散落各处。
创建配置存储管理类:
public class SettingsDataStore { private static volatile SettingsDataStore INSTANCE; private final RxDataStore<Preferences> dataStore; //统一定义key,自带类型,编译期类型安全 //记住的账号 public static final Preferences.Key<String> KEY_REMEMBER_ACCOUNT = PreferencesKeys.stringKey("remember_account"); //是否记住密码 public static final Preferences.Key<Boolean> KEY_REMEMBER_PASSWORD = PreferencesKeys.booleanKey("remember_password"); private SettingsDataStore(Context context) { //构建DataStore实例,文件名自定义 dataStore = new RxPreferenceDataStoreBuilder(context.getApplicationContext(), "app_settings").build(); } //单例获取 public static SettingsDataStore getInstance(Context context) { if(INSTANCE == null) { synchronized (SettingsDataStore.class) { if(INSTANCE == null) { INSTANCE = new SettingsDataStore(context); } } } return INSTANCE; } //暴露DataStore实例,供读写使用 public RxDataStore<Preferences> getDataStore() { return dataStore; } }3. 写入数据
写入通过updateDataAsync()完成,是异步原子操作,不会阻塞主线程:
// 这是一个公开方法,外部调用它来保存“是否记住账号密码”的设置 public void saveRememberInfo(String account, boolean isRemember) { // 【第1步】调用 dataStore 的“异步更新”方法 // 我要修改 DataStore 里存的数据了,但为了防止卡主线程,请在后台线程执行。 // preferences -> {} 是“回调函数”:当后台线程准备好时,会把“当前磁盘上的所有旧数据”传进来,名字叫 preferences。 dataStore.updateDataAsync(preferences -> { // 【第2步】把只读数据变成可写数据 // 传进来的 preferences 是“只读”的(不允许直接修改,怕你乱动原始档案)。 // 所以我必须调用 toMutablePreferences(),把这堆旧数据“复印”一份到一张可以随意涂改的草稿纸(mutablePrefs)上。 MutablePreferences mutablePrefs = preferences.toMutablePreferences(); // 【第3步】在草稿纸上写第一个数据 // 在这张草稿纸上,把 KEY_REMEMBER_ACCOUNT 这一栏的值改成 account。 mutablePrefs.set(KEY_REMEMBER_ACCOUNT, account); // 【第4步】在草稿纸上写第二个数据 // 在这张草稿纸上,把 KEY_REMEMBER_PASSWORD 这一栏的值改成 isRemember。 mutablePrefs.set(KEY_REMEMBER_PASSWORD, isRemember); // 【第5步】把修改好的草稿纸“打包”返回 // 改完了,把这张草稿纸(mutablePrefs)交给 DataStore。 // Single.just() 就相当于“用快递箱把草稿纸包好”,告诉 DataStore:“我改完了,你拿着这个箱子去覆盖磁盘上的旧文件吧!” // DataStore 收到后,会在后台自动把整个文件原子性地(一次性)写入磁盘。 return Single.just(mutablePrefs); }); }4. 读取数据(结合LiveData,适配MVVM)
DataStore的data()方法返回RxJava的Flowable,是一个响应式流:数据变化时自动发射最新值。我们可以把他转换成LiveData,直接在ViewModel中使用。
在Repository层封装,返回LiveData:
public class SettingsRepository { private final SettingsDataStore dataStore; public SettingsRepository(Context context) { this.dataStore = SettingsDataStore.getInstance(context); } //读取要记住的账号 public LiveData<String> getRememberAccount() { return LiveDataReactiveStreams.fromPublisher( dataStore.getDataStore().data() .map(preferences -> { String value = preferences.get(SettingsDataStore.KEY_REMEMBER_ACCOUNT); return value != null ? value : ""; }) .onErrorReturnItem("") ); } //写入方法 public void saveRememberInfo(String account, boolean isRemember) { dataStore.saveRememberInfo(account, isRemember); } }5. ViewModel中调用,Activity观察
ViewModel层:
public class SettingsViewModel extends AndroidViewModel { private final SettingsRepository repository; public SettingsViewModel(@NonNull Application application) { super(application); repository = new SettingsRepository(application); } }Activity层:
public class SettingsActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_settings); SettingsViewModel viewModel = new ViewModelProvider(this).get(SettingsViewModel.class); Switch darkModeSwitch = findViewById(R.id.switch_dark_mode); // 观察配置变化,自动更新 UI viewModel.darkModeState.observe(this, isDark -> { darkModeSwitch.setChecked(isDark); // 可在这里自动切换应用主题 }); // 点击开关,修改配置 darkModeSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { viewModel.switchDarkMode(isChecked); }); } }