当前位置: 首页 > news >正文

【Android】DataStore

承接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 核心特性

  1. 完全异步无阻塞:读写默认在 IO 线程执行,基于响应式流,不会阻塞主线程,从根源避免 SP 的 ANR 问题
  2. 线程安全原子操作:写入操作具备原子性,多线程并发安全,不会出现脏数据
  3. 天然响应式:数据变化自动推送更新,配合 LiveData 可直接驱动 UI,无需手动监听
  4. 类型安全:Key 自带类型约束,编译期就能检查类型错误
  5. 支持事务:批量修改可在一个事务内完成,保证原子性
  6. 官方迁移支持:一键从旧 SP 迁移数据,无需手动读写适配
  7. 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); }); } }
http://www.cnnetsun.cn/news/4146005.html

相关文章:

  • 用 Whisper 搞定离线语音转文本:Flutter 跨平台完整指南
  • java连接linux Java秒连Linux!SSH远程控制,服务器管理爽到飞起
  • Python-数据类型-数字型
  • 2026全网实测|5大主流AI论文工具排行榜!学生党闭眼入✅
  • 中小企业销售自动化实战:Rox Teams API集成与线索评分系统构建
  • 考研初试专业课130+总分400+北京大学804原828电子信息基础软件与微电子考研北大软微集成数字电路考研,真题,大纲,参考书。博睿泽信息通信Jenny。
  • 家具商城系统-springboot + vue
  • Sqoop 从 MySQL 导入数据到 Hive:参数解析与实战指南
  • day2: booleans, string interpolation, and checkpoint1
  • 多无人机协同任务规划:从数学建模到算法实战
  • FanControl 完整指南:3 步驯服电脑风扇噪音
  • 泰克2465B模拟示波器深度评测:模拟时代的工程智慧与现代价值
  • 基于多仓群路由算法的中大件海外仓周末选品履约优化方案
  • 2026 游乐空间安装交付服务榜单可以参考哪些选型维度?
  • Yi.Abp.Admin 完整上手指南:从克隆到跑通 5 分钟
  • 开放智能体系统安全实践:从工具调用风险到可信架构设计
  • 蓝天治愈系
  • 最小二乘法:从原理到实践,掌握线性回归的核心算法
  • 数学建模竞赛实战:蒙特卡洛模拟与资源量评价模型解析
  • AI智能体灰盒验证:构建可观测、可测试的Agent质量防线
  • 一步找到全盘文件:EverythingToolbar 任务栏文件搜索完整指南
  • 智慧场馆解决方案小程序系统:从架构设计到实战部署
  • 防火门的耐火极限与哪些因素有关
  • 几何先验驱动视频生成:从3D一致性困境到Geometry-then-Appearance新范式
  • 谷歌TPU与Marvell深度合作:AI芯片变革下的开发者实战指南
  • 智算中心训练任务频繁中断,如何从算力卡查到网络与存储
  • 2026硕博生毕业论文季:多工具协同下的论文全流程处理方案
  • Iwara4A:免费 Iwara 安卓客户端完整指南
  • 数学建模竞赛实战:从问题解析到模型构建与求解全流程指南
  • Spring面试核心:IoC与AOP深度解析与实战