Какие знаешь способы сохранения данных экрана при пересоздании процесса приложения?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Сохранение состояния экрана при пересоздании процесса приложения
В Android пересоздание процесса может произойти из-за изменения конфигурации (поворот экрана, изменение языка), нехватки памяти или принудительной остановки системы. Для сохранения данных экрана существуют несколько ключевых механизмов, которые я разделяю на три категории: сохранение состояния UI, сохранение бизнес-логики и постоянное хранение.
1. Сохранение состояния UI компонентов
ViewModel — основной архитектурный компонент для хранения данных, связанных с UI, которые должны переживать изменения конфигурации. Он сохраняется в специальном хранилище, управляемом системой, и очищается только когда связанный с ним жизненный цикл (обычно Activity или Fragment) завершается окончательно.
class UserViewModel : ViewModel() {
private val _userName = MutableLiveData<String>()
val userName: LiveData<String> = _userName
fun setUserName(name: String) {
_userName.value = name
}
}
// В Activity или Fragment
private val viewModel: UserViewModel by viewModels()
OnSaveInstanceState — механизм для сохранения небольших объемов данных (примитивы, Parcelable объекты) при временном уничтожении Activity. Восстанавливается в onCreate() или onRestoreInstanceState().
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString("KEY_USER_NAME", userName)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
userName = savedInstanceState.getString("KEY_USER_NAME")
}
}
Для Fragment используется setRetainInstance(true) (устаревший) или ViewModel с ViewModelProvider.
2. Сохранение бизнес-логики и более сложных данных
Repository Pattern в сочетании с корутинами/Flow — бизнес-логика должна храниться в репозиториях, которые независимы от жизненного цикла UI компонентов. Данные загружаются через корутины или Flow, которые могут пережить пересоздание.
class UserRepository {
private val apiService: ApiService
private val userDao: UserDao
fun getUserFlow(userId: String): Flow<User> {
return userDao.getUserFlow(userId)
.onEach { user ->
if (user == null) {
// Загрузка из сети
val networkUser = apiService.getUser(userId)
userDao.insert(networkUser)
}
}
}
}
Использование статических объектов или Dependency Injection — через Dagger/Hilt или Koin можно обеспечить существование ключевых компонентов вне жизненного цикла Activity/Fragment.
3. Постоянное хранение данных
Когда приложение полностью уничтожается системой (процесс убит), вышеуказанные механизмы не работают. Здесь нужны решения для постоянного хранения:
Room Database — рекомендуемая ORM для SQLite, обеспечивающая реактивное программирование через Flow/LiveData.
@Entity
data class User(
@PrimaryKey val id: String,
val name: String,
val email: String
)
@Dao
interface UserDao {
@Query("SELECT * FROM user WHERE id = :userId")
fun getUserFlow(userId: String): Flow<User?>
}
SharedPreferences — для хранения простых ключ-значение данных. Современная реализация через DataStore (предпочтительнее).
DataStore — замена SharedPreferences с поддержкой корутин и Flow, типобезопасность.
val Context.userPreferencesDataStore: DataStore<Preferences> by preferencesDataStore(
name = "user_prefs"
)
suspend fun saveUserToken(token: String) {
context.userPreferencesDataStore.edit { preferences ->
preferences[stringPreferencesKey("user_token")] = token
}
}
Файловая система — для больших данных, изображений, документов.
Сетевое хранилище — синхронизация с бэкендом для восстановления данных после переустановки приложения.
Стратегия комбинирования подходов
На практике я комбинирую несколько методов:
- Мелкие UI состояния →
onSaveInstanceState(текст в поле ввода, позиция скролла) - Данные для текущего экрана → ViewModel + LiveData/StateFlow
- Кэшированные данные → Room с Flow, чтобы при пересоздании UI автоматически получал актуальные данные
- Настройки пользователя → DataStore
- Критичные данные → синхронизация с бэкендом
Пример восстановления после убийства процесса:
class UserActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Восстановление из Bundle (мелкие данные)
val tempName = savedInstanceState?.getString("TEMP_NAME")
// Подписка на Flow из Room (основные данные)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.userFlow.collect { user ->
// Обновление UI
}
}
}
// Если данных нет нигде - загрузка из сети
if (viewModel.userFlow.value == null && tempName == null) {
viewModel.loadUserFromNetwork()
}
}
}
Ключевой принцип: чем важнее данные, тем более устойчивое хранилище нужно использовать. Для UI состояния хватит ViewModel, для пользовательских данных — база данных, для критичной информации — синхронизация с сервером. Современные архитектурные подходы (MVVM, MVI) с четким разделением ответственности значительно упрощают управление состоянием при пересоздании процесса.