Как можно сохранить данные при пересоздании Activity?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Сохранение данных при пересоздании Activity
Activity пересоздаётся при поворотах экрана, смене языка системы, нехватке памяти и других причинах. Потеря данных при этом — плохой UX. Существует несколько способов сохранить состояние. Каждый подходит для разных сценариев.
1. ViewModel + LiveData/StateFlow (рекомендуется)
Это самый правильный способ в современном Android.
class MyViewModel : ViewModel() {
private val _userLiveData = MutableLiveData<User>()
val userLiveData: LiveData<User> = _userLiveData
private val _counterState = MutableStateFlow<Int>(0)
val counterState: StateFlow<Int> = _counterState.asStateFlow()
fun loadUser(id: Int) {
viewModelScope.launch {
_userLiveData.value = repository.getUser(id)
}
}
fun incrementCounter() {
_counterState.value += 1
}
}
class MyActivity : AppCompatActivity() {
private val viewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Данные сохраняются автоматически при пересоздании Activity
viewModel.userLiveData.observe(this) { user ->
displayUser(user)
}
// Состояние не потеряется при повороте
lifecycleScope.launch {
viewModel.counterState.collect { counter ->
counterText.text = counter.toString()
}
}
}
}
Плюсы:
- Автоматически выживает при повороте и других пересозданиях
- Lifecycle-aware, нет утечек памяти
- Асинхронность из коробки
- Современный способ
Минусы:
- Требует AndroidX
2. savedInstanceState (onSaveInstanceState)
Для простых данных (примитивы, Parcelable, Serializable).
class MyActivity : AppCompatActivity() {
companion object {
private const val KEY_USER = "user_key"
private const val KEY_COUNTER = "counter_key"
}
private var user: User? = null
private var counter = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Восстановить из Bundle
if (savedInstanceState != null) {
user = savedInstanceState.getParcelable(KEY_USER)
counter = savedInstanceState.getInt(KEY_COUNTER, 0)
} else {
loadData() // Новое создание
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
// Сохранить данные перед пересозданием
outState.putParcelable(KEY_USER, user)
outState.putInt(KEY_COUNTER, counter)
}
}
Плюсы:
- Встроено в Android Framework
- Подходит для простых данных
Минусы:
- Bundle ограничена по размеру (~1MB)
- Не подходит для больших объектов
- Много boilerplate-кода
- Надо вручную сохранять/восстанавливать
3. SharedPreferences
Для простых, долгоживущих данных (настройки, токены).
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val prefs = getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
// Восстановить
val savedCounter = prefs.getInt("counter", 0)
val savedUser = prefs.getString("user_name", null)
}
override fun onDestroy() {
super.onDestroy()
// Сохранить
val prefs = getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
prefs.edit().apply {
putInt("counter", counter)
putString("user_name", user?.name)
apply()
}
}
}
// Лучше использовать EncryptedSharedPreferences для конфиденциальных данных
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val encryptedSharedPreferences = EncryptedSharedPreferences.create(
context,
"secret_shared_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
Плюсы:
- Простой API
- Персистентное хранилище
- Подходит для глобальных настроек
Минусы:
- Не для сложных структур
- Медленнее чем оперативная память
- Без типизации (нужны обёртки)
4. Room (Database)
Для больших объёмов данных, которые нужны долгоживущие.
@Entity(tableName = "users")
data class UserEntity(
@PrimaryKey val id: Int,
val name: String,
val email: String
)
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE id = :id")
suspend fun getUserById(id: Int): UserEntity?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: UserEntity)
}
class MyViewModel(
private val userDao: UserDao
) : ViewModel() {
fun loadUser(id: Int) {
viewModelScope.launch {
val user = userDao.getUserById(id)
_userLiveData.value = user
}
}
}
Плюсы:
- Типизированное хранилище
- Мощные запросы
- Транзакции, индексы
- Автоматические миграции
Минусы:
- Overhead для простых случаев
- Требует dependencies
5. Файловая система (Files, Cache)
Для больших файлов (изображения, видео).
class MyActivity : AppCompatActivity() {
private fun saveData(data: String) {
val cacheDir = cacheDir // Очищается при удалении приложения
val file = File(cacheDir, "my_data.txt")
file.writeText(data)
}
private fun loadData(): String? {
val cacheDir = cacheDir
val file = File(cacheDir, "my_data.txt")
return if (file.exists()) file.readText() else null
}
}
Плюсы:
- Неограниченный размер
- Подходит для больших файлов
Минусы:
- Нет типизации
- Медленнее чем память
- Нужно обрабатывать I/O
Сравнительная таблица
| Способ | Размер | Скорость | Когда использовать | Сложность |
|---|---|---|---|---|
| ViewModel | Unlimited | Быстро | Текущее состояние экрана | Низкая |
| savedInstanceState | ~1MB | Очень быстро | Простые примитивы | Средняя |
| SharedPreferences | Unlimited | Быстро | Настройки, токены | Низкая |
| Room | Unlimited | Быстро | Кешированные данные, история | Средняя |
| Files | Unlimited | Медленно | Большие объёмы (фото, видео) | Средняя |
Лучшие практики
// ✅ Комбинированный подход (рекомендуется)
class MyViewModel : ViewModel() {
// Текущее состояние экрана
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
// Кешированные данные в базе
private val userRepository: UserRepository
// При создании, восстанови из кеша
fun loadUser(id: Int) {
viewModelScope.launch {
// Сначала показать из кеша
val cached = userRepository.getUserFromCache(id)
_uiState.value = UiState.Loaded(cached)
// Затем загрузить свежие данные
val fresh = userRepository.getUserFromNetwork(id)
_uiState.value = UiState.Loaded(fresh)
}
}
}
Современный stack для сохранения данных:
- ViewModel — текущее состояние UI
- Room — кеш и история
- SharedPreferences/EncryptedSharedPreferences — конфиденциальные настройки
- savedInstanceState — только если нужны примитивы
Эта комбинация обеспечивает надёжное восстановление при пересоздании Activity и сохранение данных между сеансами приложения.