数据存储
Android 提供了多种数据存储方案,适用于不同的使用场景。本章将介绍 SharedPreferences、文件存储、SQLite 数据库以及现代的 Room 和 DataStore。
存储方案概览
| 存储方式 | 适用场景 | 特点 |
|---|---|---|
| SharedPreferences | 简单的键值对配置 | 轻量级,适合存储少量数据 |
| 文件存储 | 图片、文档等 | 灵活,适合存储大文件 |
| SQLite | 结构化数据 | 关系型数据库,适合复杂数据 |
| Room | SQLite 的封装 | 类型安全,推荐使用 |
| 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 的主要数据存储方案:
- SharedPreferences:简单的键值对存储,适合配置数据
- 文件存储:内部存储和外部存储,适合大文件
- Room:SQLite 的封装,类型安全,适合结构化数据
- DataStore:SharedPreferences 的现代替代,异步、类型安全
选择存储方案时,考虑数据类型、数据量、是否需要共享等因素。下一章将学习网络请求。