Поворот экрана в Android на Kotlin: полное руководство

При вызове requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE в Activity на Kotlin экран приложения принудительно переключается в альбомный режим независимо от системной настройки автоповорота — и это первое, что стоит проверить, если интерфейс внезапно «залип» в одной ориентации. Проблема с поворотом экрана в Android-приложении почти всегда сводится к одному из трёх источников: явная фиксация ориентации в коде, атрибут android:screenOrientation в манифесте или некорректная обработка пересоздания Activity.

В этой статье разберём, как программно включать, блокировать и отслеживать поворот экрана, как сохранить данные при смене конфигурации и какие подводные камни ждут разработчика на устройствах с разными версиями Android.

Как Android обрабатывает поворот экрана

По умолчанию система при повороте устройства уничтожает текущую Activity и создаёт её заново, чтобы подгрузить ресурсы для новой ориентации — например, layout из папки res/layout-land. Это штатное поведение, а не ошибка. Однако из-за пересоздания теряется состояние полей, если оно не сохранено вручную.

Ориентация определяется на трёх уровнях: системная настройка автоповорота устройства, атрибут в AndroidManifest.xml и вызов requestedOrientation в коде. Приоритет у программного вызова выше, чем у манифеста. Если где-то в коде осталась фиксация ориентации, пользовательская настройка автоповорота будет проигнорирована.

Ключевой момент: поворот экрана — это событие изменения конфигурации (configuration change), и система реагирует на него пересозданием Activity, если разработчик не заявил иное.

Фиксация ориентации через манифест

Самый простой способ запретить или разрешить поворот — атрибут android:screenOrientation у тега <activity> в AndroidManifest.xml. Он подходит, когда ориентация известна заранее и не должна меняться в рантайме.

<activity

android:name=".MainActivity"

android:screenOrientation="portrait" />

Основные значения атрибута:

  • 📱 portrait — только портретная ориентация;
  • 🖥️ landscape — только альбомная;
  • 🔄 sensor — ориентация следует за датчиком устройства;
  • 👤 user — следует системной настройке пользователя;
  • 🔒 locked — фиксирует текущую ориентацию в момент запуска.
⚠️ Внимание: на устройствах с Android 8.0 (API 26) фиксация ориентации у Activity с прозрачной темой могла приводить к падению приложения с IllegalStateException. Если поддерживаете старые версии, проверьте сочетание темы и screenOrientation на реальных устройствах.

Программное управление поворотом на Kotlin

Когда ориентацию нужно менять динамически — например, разрешить альбомный режим только в видеоплеере — используется свойство requestedOrientation. Оно доступно в любой Activity и принимает константы из класса ActivityInfo.

// Принудительно включить альбомный режим

requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE

// Вернуть управление системе

requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED

Значение SCREEN_ORIENTATION_UNSPECIFIED снимает программную фиксацию и возвращает поведение по умолчанию. Это важно: если вы временно заблокировали поворот (например, на время загрузки данных), не забудьте снять блокировку после завершения операции, иначе экран останется «залипшим» до перезапуска Activity.

Определить текущую ориентацию можно через конфигурацию ресурсов:

val isLandscape = resources.configuration.orientation ==

Configuration.ORIENTATION_LANDSCAPE

📊 Как вы чаще всего управляете поворотом экрана в своих приложениях?
Только через манифест (screenOrientation)
Программно через requestedOrientation
Комбинирую оба способа
Использую Jetpack Compose и не задумываюсь об этом

Сохранение состояния при повороте

Поскольку Activity пересоздаётся, все локальные переменные сбрасываются. Чтобы пользователь не терял введённые данные, состояние нужно сохранять. Есть несколько рабочих подходов, выбор зависит от объёма и природы данных.

ПодходЧто сохраняетКогда использовать
onSaveInstanceStateНебольшие примитивы в BundleТекст полей, позиция скролла, флаги
ViewModelДанные экрана в памятиСписки, результаты запросов, состояние UI
onRetainCustomNonConfigurationInstanceПроизвольный объектРедкие случаи, когда ViewModel недоступна
Постоянное хранилищеЛюбые данные в БД/файлеДанные, критичные к потере процесса

Рекомендуемый современный вариант — ViewModel из Android Jetpack: объект переживает пересоздание Activity и автоматически очищается, когда экран закрывается окончательно. Для мелких значений удобно комбинировать с SavedStateHandle, который переживает и уничтожение процесса.

class MainViewModel(private val state: SavedStateHandle) : ViewModel() {

var query: String

get() = state["query"] ?: ""

set(value) { state["query"] = value }

}

Обработка поворота без пересоздания Activity

Иногда пересоздание нежелательно — например, в играх или при активном сетевом соединении. Тогда в манифесте объявляют, что Activity сама обрабатывает изменение конфигурации:

<activity

android:name=".PlayerActivity"

android:configChanges="orientation|screenSize|screenLayout|keyboardHidden" />

При таком подходе система не уничтожает Activity, а вызывает метод onConfigurationChanged(), где можно вручную обновить интерфейс:

override fun onConfigurationChanged(newConfig: Configuration) {

super.onConfigurationChanged(newConfig)

if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {

// Перестроить UI под альбомный режим

}

}

⚠️ Внимание: атрибут configChanges — не замена нормальному сохранению состояния. Процесс всё равно может быть уничтожен системой при нехватке памяти, и тогда данные без onSaveInstanceState будут потеряны. Официальная документация Android прямо не рекомендует использовать этот атрибут как основной механизм.

☑️ Проверка корректной обработки поворота

Выполнено: 0 / 5

Типичные проблемы и их диагностика

Разберём частые симптомы, с которыми сталкиваются разработчики и пользователи.

  • 🧊 Экран не поворачивается вообще — проверьте системную настройку автоповорота, затем поищите в коде вызовы requestedOrientation и атрибут в манифесте;
  • 💥 Падение при повороте — чаще всего обращение к View до её инициализации после пересоздания или утечка контекста старой Activity;
  • 📉 Сброс данных формы — состояние хранится в полях Activity, а не в ViewModel или Bundle;
  • 🔁 Двойной сетевой запрос — запуск загрузки в onCreate() без проверки savedInstanceState.

Для отладки полезно логировать жизненный цикл: вызовы onDestroy() и onCreate() при повороте подтверждают пересоздание. Если вместо этого срабатывает onConfigurationChanged() — значит, где-то объявлен configChanges, возможно, в подключённой библиотеке.

Поворот экрана в Jetpack Compose

В Compose пересоздание Activity тоже происходит, но состояние UI удобно сохранять через rememberSaveable — он автоматически сериализует значения в Bundle. Для сложных объектов потребуется реализовать Saver. Фиксация ориентации делается тем же requestedOrientation, обычно через LocalContext.current как Activity.

Особенности на планшетах, складных устройствах и новых версиях Android

На устройствах с большими экранами и в режиме многооконности поведение отличается: система может игнорировать фиксацию ориентации, а приложение — менять размер без поворота. Начиная с Android 12, изменение размера окна в многооконном режиме также приводит к пересозданию Activity, если не объявлен соответствующий configChanges.

Для складных устройств и планшетов Google рекомендует адаптивные макеты вместо жёсткой фиксации ориентации. Практический вывод: тестируйте поворот не только на телефоне, но и в эмуляторе планшета и в режиме разделённого экрана — там всплывают сценарии, которых нет на обычном смартфоне.

Часто задаваемые вопросы

Как полностью запретить поворот экрана в приложении?

Укажите android:screenOrientation="portrait" для каждой Activity в манифесте либо вызовите requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT в onCreate() базовой Activity, от которой наследуются все экраны.

Почему Activity пересоздаётся при повороте и можно ли этого избежать?

Это штатный механизм: система подгружает ресурсы для новой конфигурации. Избежать пересоздания можно через android:configChanges="orientation|screenSize", но корректнее сохранять состояние через ViewModel и onSaveInstanceState.

Как узнать текущую ориентацию экрана в Kotlin?

Проверьте resources.configuration.orientation: значение Configuration.ORIENTATION_LANDSCAPE означает альбомный режим, ORIENTATION_PORTRAIT — портретный.

Данные из ViewModel пропадают после поворота — в чём причина?

Возможная причина — ViewModel создаётся не через viewModels() или ViewModelProvider, а вручную, поэтому не привязана к жизненному циклу владельца. Также проверьте, что Activity не объявлена с android:noHistory="true" и не завершается вызовом finish().

Как заблокировать поворот только на время загрузки?

Перед началом операции установите requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED, а после завершения верните SCREEN_ORIENTATION_UNSPECIFIED. Убедитесь, что сброс срабатывает и при ошибке — например, в блоке finally или в обработчике исключения корутины.