数据持久化:Room数据库集成、DataStore替代SharedPreferences、文件存储、序列化与反序列化
数据持久化,说白了就是让App的数据活得更久。App一关,数据就丢?那用户肯定不干。今天我们来聊聊Android里那几套持久化方案,我会结合自己的踩坑经历,帮你理清思路。
一、Room数据库集成
Room是Google官方推荐的ORM框架,底层封装了SQLite。我个人习惯用Room,因为它编译期会检查SQL语法,少了很多运行时崩溃。
1. 添加依赖
// build.gradle.kts (Module)
plugins {
id("kotlin-kapt")
}
dependencies {
val roomVersion = "2.6.1"
implementation("androidx.room:room-runtime:$roomVersion")
implementation("androidx.room:room-ktx:$roomVersion")
kapt("androidx.room:room-compiler:$roomVersion")
}
2. 定义实体与DAO
@Entity(tableName = "users")
data class User(
@PrimaryKey val id: Int,
val name: String,
val age: Int
)
@Dao
interface UserDao {
@Query("SELECT * FROM users")
suspend fun getAll(): List<User>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(user: User)
@Delete
suspend fun delete(user: User)
}
3. 创建数据库
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getInstance(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"my_database"
).build().also { INSTANCE = it }
}
}
}
}
⚠️ 注意: 数据库版本升级时,记得写Migration,否则会丢数据。我曾经在线上版本直接fallbackToDestructiveMigration(),结果用户数据全没了……从那以后我再也不敢偷懒了。
二、DataStore替代SharedPreferences
SharedPreferences有个老毛病——同步读写容易卡UI线程,而且不支持类型安全。DataStore就是来替代它的。
1. Preferences DataStore
val Context.dataStore by preferencesDataStore(name = "settings")
val THEME_KEY = booleanPreferencesKey("dark_mode")
suspend fun saveTheme(context: Context, isDark: Boolean) {
context.dataStore.edit { preferences ->
preferences[THEME_KEY] = isDark
}
}
val themeFlow: Flow<Boolean> = context.dataStore.data.map { preferences ->
preferences[THEME_KEY] ?: false
}
2. Proto DataStore(类型安全)
如果你需要强类型的数据结构,用Proto DataStore更合适。它基于Protocol Buffers,定义一次schema,到处使用。
// 定义 proto 文件
syntax = "proto3";
option java_package = "com.example.datastore";
option java_multiple_files = true;
message UserPreferences {
bool dark_mode = 1;
int32 font_size = 2;
}
// 使用
val Context.userPreferencesStore by protoDataStore<UserPreferences>(
fileName = "user_prefs.pb",
serializer = UserPreferencesSerializer
)
💡 我的建议: 简单键值对用Preferences DataStore,复杂配置用Proto DataStore。别一上来就用Proto,过度设计也是坑。
三、文件存储
有些场景不适合数据库,比如存图片、日志、缓存文件。这时候就得用文件存储。
1. 内部存储 vs 外部存储
| 存储类型 | 路径 | 特点 |
|---|---|---|
| 内部存储 | context.filesDir | 私有,其他App无法访问,卸载即删除 |
| 外部存储 | Environment.getExternalStorageDirectory() | 可被用户和其他App访问,需要权限 |
2. 写入与读取示例
// 写入内部存储
fun writeToFile(context: Context, fileName: String, content: String) {
context.openFileOutput(fileName, Context.MODE_PRIVATE).use {
it.write(content.toByteArray())
}
}
// 读取内部存储
fun readFromFile(context: Context, fileName: String): String {
return context.openFileInput(fileName).bufferedReader().readText()
}
⚠️ 注意: Android 10以后,外部存储的访问方式变了。别再直接用Environment.getExternalStorageDirectory(),改用MediaStore或SAF(Storage Access Framework)。我有个项目就因为没适配,在Android 11上直接崩溃。
四、序列化与反序列化
数据要持久化,就得先变成字节流。这个过程就是序列化。反过来叫反序列化。
1. Serializable vs Parcelable
| 方案 | 优点 | 缺点 |
|---|---|---|
| Serializable | Java原生,使用简单 | 反射机制,性能差,容易产生大量临时对象 |
| Parcelable | Android专用,性能好 | 需要手写模板代码(可用插件生成) |
2. Kotlin序列化(kotlinx.serialization)
我个人更推荐用kotlinx.serialization,它编译期生成代码,性能接近手写,而且支持JSON、ProtoBuf等多种格式。
// 添加依赖
plugins {
kotlin("plugin.serialization") version "1.9.0"
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
}
// 定义可序列化类
@Serializable
data class Person(val name: String, val age: Int)
// 序列化
val json = Json.encodeToString(person)
// 反序列化
val obj = Json.decodeFromString<Person>(json)
💡 避坑指南: 我曾经用Serializable存一个复杂的对象图,结果序列化后文件巨大,反序列化还经常OOM。后来换成kotlinx.serialization,问题全解决了。记住:序列化不是越简单越好,要看场景。
五、知识体系总览
下面这张图,是我自己总结的数据持久化选型思路。你想想看,是不是这个理?
嗯,到这里数据持久化的核心内容就讲完了。记住一句话:没有银弹。Room适合结构化数据,DataStore适合配置项,文件存储适合大块数据。选型时多想想你的业务场景,别盲目跟风。