跳到主要内容

数据存储

Android 提供了多种数据存储方案,适用于不同的使用场景。本章将介绍 SharedPreferences、文件存储、SQLite 数据库以及现代的 Room 和 DataStore。

存储方案概览

存储方式适用场景特点
SharedPreferences简单的键值对配置轻量级,适合存储少量数据
文件存储图片、文档等灵活,适合存储大文件
SQLite结构化数据关系型数据库,适合复杂数据
RoomSQLite 的封装类型安全,推荐使用
DataStore替代 SharedPreferences异步、类型安全、数据一致性

SharedPreferences

SharedPreferences 用于存储简单的键值对数据,如用户设置、应用配置等。

获取 SharedPreferences

// 方式一:获取默认的 SharedPreferences
val prefs = getSharedPreferences("app_prefs", Context.MODE_PRIVATE)

// 方式二:使用 Activity 的 preferences
val prefs = getPreferences(Context.MODE_PRIVATE)

写入数据

val prefs = getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
val editor = prefs.edit()

editor.putString("username", "张三")
editor.putInt("age", 25)
editor.putBoolean("is_logged_in", true)
editor.putFloat("score", 98.5f)
editor.putLong("timestamp", System.currentTimeMillis())

// 同步提交
editor.commit()

// 异步提交(推荐)
editor.apply()

commit()apply() 的区别:

  • commit():同步写入,返回是否成功,可能阻塞主线程
  • apply():异步写入,无返回值,推荐使用

读取数据

val prefs = getSharedPreferences("app_prefs", Context.MODE_PRIVATE)

val username = prefs.getString("username", "") // 第二个参数是默认值
val age = prefs.getInt("age", 0)
val isLoggedIn = prefs.getBoolean("is_logged_in", false)
val score = prefs.getFloat("score", 0f)

删除数据

val editor = prefs.edit()

// 删除单个键
editor.remove("username")

// 清空所有数据
editor.clear()

editor.apply()

文件存储

Android 提供两种文件存储位置:内部存储和外部存储。

内部存储

内部存储是应用私有的,其他应用无法访问,应用卸载后数据会被删除。

// 写入文件
val filename = "my_file.txt"
val content = "Hello, World!"

openFileOutput(filename, Context.MODE_PRIVATE).use { output ->
output.write(content.toByteArray())
}

// 读取文件
val content = openFileInput(filename).bufferedReader().use { it.readText() }

// 删除文件
deleteFile(filename)

// 列出所有文件
val files = fileList()

外部存储

外部存储可以是共享的(如 SD 卡),需要申请权限。

添加权限:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<!-- 读取外部存储 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- 写入外部存储 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

</manifest>

动态申请权限:

private const val REQUEST_STORAGE_PERMISSION = 100

private fun checkStoragePermission() {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
REQUEST_STORAGE_PERMISSION
)
} else {
// 已有权限
writeToFile()
}
}

override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_STORAGE_PERMISSION) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
writeToFile()
} else {
Toast.makeText(this, "权限被拒绝", Toast.LENGTH_SHORT).show()
}
}
}

写入外部存储:

private fun writeToFile() {
if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
val dir = File(Environment.getExternalStorageDirectory(), "MyApp")
if (!dir.exists()) {
dir.mkdirs()
}

val file = File(dir, "my_file.txt")
file.writeText("Hello, External Storage!")
}
}

应用专属外部存储

应用专属的外部存储不需要权限,应用卸载后数据会被删除:

// 获取应用专属外部存储目录
val externalDir = getExternalFilesDir(null) // 根目录
val picturesDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES) // 图片目录
val documentsDir = getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) // 文档目录

// 写入文件
val file = File(externalDir, "my_file.txt")
file.writeText("Hello, App External Storage!")

Room 数据库

Room 是 SQLite 的抽象层,提供了类型安全的数据库访问,是 Android 推荐的数据库方案。

添加依赖

dependencies {
val roomVersion = "2.6.1"
implementation("androidx.room:room-runtime:$roomVersion")
implementation("androidx.room:room-ktx:$roomVersion")
ksp("androidx.room:room-compiler:$roomVersion")
}

创建实体类

@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,

@ColumnInfo(name = "username")
val username: String,

@ColumnInfo(name = "email")
val email: String,

@ColumnInfo(name = "created_at")
val createdAt: Long = System.currentTimeMillis()
)

创建 DAO(数据访问对象)

@Dao
interface UserDao {

@Query("SELECT * FROM users")
suspend fun getAll(): List<User>

@Query("SELECT * FROM users WHERE id = :id")
suspend fun getById(id: Long): User?

@Query("SELECT * FROM users WHERE username LIKE :username")
suspend fun findByUsername(username: String): List<User>

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(user: User): Long

@Insert
suspend fun insertAll(vararg users: User)

@Update
suspend fun update(user: User)

@Delete
suspend fun delete(user: User)

@Query("DELETE FROM users")
suspend fun deleteAll()
}

创建数据库类

@Database(entities = [User::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao

companion object {
@Volatile
private var INSTANCE: AppDatabase? = null

fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app_database"
).build()
INSTANCE = instance
instance
}
}
}
}

使用数据库

class UserRepository(context: Context) {
private val userDao = AppDatabase.getDatabase(context).userDao()

suspend fun insertUser(username: String, email: String) {
val user = User(username = username, email = email)
userDao.insert(user)
}

suspend fun getAllUsers(): List<User> {
return userDao.getAll()
}

suspend fun updateUser(user: User) {
userDao.update(user)
}

suspend fun deleteUser(user: User) {
userDao.delete(user)
}
}

// 在 ViewModel 中使用
class UserViewModel(application: Application) : AndroidViewModel(application) {
private val repository = UserRepository(application)

private val _users = MutableLiveData<List<User>>()
val users: LiveData<List<User>> = _users

fun loadUsers() {
viewModelScope.launch(Dispatchers.IO) {
val userList = repository.getAllUsers()
_users.postValue(userList)
}
}

fun addUser(username: String, email: String) {
viewModelScope.launch(Dispatchers.IO) {
repository.insertUser(username, email)
loadUsers()
}
}
}

数据库迁移

当数据库结构变更时,需要创建迁移:

val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE users ADD COLUMN phone TEXT")
}
}

// 在创建数据库时添加迁移
Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app_database"
)
.addMigrations(MIGRATION_1_2)
.build()

DataStore

DataStore 是 SharedPreferences 的现代替代方案,使用 Kotlin 协程和 Flow 实现异步、类型安全的数据存储。

添加依赖

dependencies {
implementation("androidx.datastore:datastore-preferences:1.0.0")
}

Preferences DataStore

用于存储简单的键值对:

// 创建 DataStore
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")

// 定义键
object PreferencesKeys {
val USERNAME = stringPreferencesKey("username")
val AGE = intPreferencesKey("age")
val IS_LOGGED_IN = booleanPreferencesKey("is_logged_in")
}

// 写入数据
suspend fun saveUsername(context: Context, username: String) {
context.dataStore.edit { preferences ->
preferences[PreferencesKeys.USERNAME] = username
}
}

// 读取数据
fun getUsername(context: Context): Flow<String> {
return context.dataStore.data
.map { preferences ->
preferences[PreferencesKeys.USERNAME] ?: ""
}
}

// 在 Activity/Fragment 中使用
lifecycleScope.launch {
// 读取
context.dataStore.data.collect { preferences ->
val username = preferences[PreferencesKeys.USERNAME] ?: ""
binding.tvUsername.text = username
}
}

// 保存
lifecycleScope.launch {
saveUsername(context, "张三")
}

Proto DataStore

用于存储类型化的对象,需要定义 Protocol Buffers schema:

// src/main/proto/user.proto
syntax = "proto3";

option java_package = "com.example.myapp.data";
option java_multiple_files = true;

message UserSettings {
string username = 1;
int32 age = 2;
bool is_logged_in = 3;
}
// 创建 Serializer
object UserSettingsSerializer : Serializer<UserSettings> {
override val defaultValue: UserSettings = UserSettings.getDefaultInstance()

override suspend fun readFrom(input: InputStream): UserSettings {
return UserSettings.parseFrom(input)
}

override suspend fun writeTo(t: UserSettings, output: OutputStream) {
t.writeTo(output)
}
}

// 创建 DataStore
private val Context.userSettingsStore: DataStore<UserSettings> by dataStore(
fileName = "user_settings.pb",
serializer = UserSettingsSerializer
)

// 使用
suspend fun saveUserSettings(context: Context, username: String, age: Int) {
context.userSettingsStore.updateData { settings ->
settings.toBuilder()
.setUsername(username)
.setAge(age)
.build()
}
}

fun getUserSettings(context: Context): Flow<UserSettings> {
return context.userSettingsStore.data
}

小结

本章介绍了 Android 的主要数据存储方案:

  1. SharedPreferences:简单的键值对存储,适合配置数据
  2. 文件存储:内部存储和外部存储,适合大文件
  3. Room:SQLite 的封装,类型安全,适合结构化数据
  4. DataStore:SharedPreferences 的现代替代,异步、类型安全

选择存储方案时,考虑数据类型、数据量、是否需要共享等因素。下一章将学习网络请求。