Обработка изменений в режиме выполнения

Некоторые настройки устройства могут меняться во время работы приложения. Вот некоторые из таких механизмов защиты:

  • Масштаб изображения в приложении
  • Разрешение экрана (масштабирование экрана)
  • Ориентация экрана
  • Размер и насыщенность шрифта
  • Региональные настройки
  • Светлая и темная темы
  • Доступность клавиатуры

Большинство изменений конфигурации происходит в результате действий пользователя. Например, при повороте или складывании устройства меняется размер экрана, доступного приложению. При подключении внешних дисплеев или перемещении приложения между экранами с разной плотностью пикселей меняется плотность пикселей и размер экрана. Аналогично, при изменении настроек устройства, таких как масштабирование экрана, размер шрифта, язык или предпочитаемая тема, меняются соответствующие значения в объекте Configuration.

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

Воссоздание действий

Система воссоздает Activity при изменении конфигурации. Для этого система вызывает onDestroy и уничтожает существующий экземпляр Activity. Затем создается новый экземпляр с помощью onCreate, и этот новый экземпляр Activity инициализируется с новой, обновленной конфигурацией. Это также означает, что система воссоздает интерфейс с новой конфигурацией.

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

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

Пример для сферы отдыха

Предположим, у вас есть composable-функция, которая показывает статический заголовок, используя строковый ресурс:

// In the res/values/strings.xml file
// <string name="compose">Jetpack Compose</string>

// In your Compose code
Text(
    text = stringResource(R.string.compose)
)

Когда создается Activity, компонуемый элемент Text считывает текущую конфигурацию (например, язык) и определяет подходящий строковый ресурс.

Если язык меняется, система воссоздает объект activity. В этом случае Compose воссоздает интерфейс. Поскольку stringResource считывает данные из текущей конфигурации, название автоматически обновляется до правильного локализованного значения.

При этом также удаляются все данные, хранящиеся в виде полей в Activity.

Чтобы сохранить состояние интерфейса при изменении конфигурации, используйте рекомендуемые шаблоны управления состоянием. Используйте ViewModel для данных и бизнес-логики, а rememberSaveable – для состояния на уровне интерфейса. Благодаря этим механизмам состояние сохраняется при воссоздании Activity, а интерфейс обновляется в соответствии с новой конфигурацией.

Подробнее о том, как сохранять состояние в Compose…

Ожидания пользователей

Пользователь ожидает, что состояние приложения будет сохранено. Если пользователь заполняет форму и открывает другое приложение в многооконном режиме, чтобы посмотреть информацию, то, вернувшись к форме, он не должен обнаружить, что она очищена или что он перенаправлен в другое место приложения. Как разработчик, вы должны обеспечить удобство использования приложения при изменении конфигурации и воссоздании объектов activity.

Чтобы проверить, сохраняется ли состояние в вашем приложении, выполните действия, которые приводят к изменению конфигурации, когда приложение находится на переднем плане и в фоновом режиме. Можно уведомлять подписчиков о следующих действиях:

  • Поворот устройства
  • Как перейти в многооконный режим
  • Изменение размера приложения в многооконном режиме или в режиме свободного окна.
  • Складывание складного устройства с несколькими экранами
  • Изменение масштаба изображения в системных настройках.
  • Перемещение приложения между экранами с разной плотностью пикселей (например, между подключенными внешними мониторами или экранами складного устройства).
  • изменение темы системы, например переход с тёмной темы на светлую;
  • Как изменить размер шрифта
  • Как изменить язык системы или приложения
  • Подключение и отключение аппаратной клавиатуры
  • подключение или отключение док-станции;

Чтобы сохранить нужное состояние при воссоздании Activity, можно использовать несколько подходов. Выбор зависит от того, какое состояние вы хотите сохранить:

  • Локальное хранилище для обработки завершения процесса при работе со сложными или большими объемами данных. Постоянное локальное хранилище включает базы данных или DataStore.
  • Сохраненные объекты, например экземпляры ViewModel, для обработки состояния, связанного с интерфейсом, в памяти, пока пользователь активно использует приложение.
  • rememberSaveable, чтобы сохранить временное состояние интерфейса при изменении конфигурации и завершении процесса, инициированном системой. Этот вариант подходит для состояний, которые зависят от ввода данных пользователем, положения прокрутки или навигации, но не относятся к ViewModel.

Подробнее о каждом из этих API и о том, когда их следует использовать, рассказывается в статье Сохранение состояния интерфейса.

Как ограничить воссоздание действий

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

По умолчанию при изменении конфигурации система уничтожает и воссоздает объект Activity, включая интерфейс и все объекты, производные от Activity. Если вы объявите, что ваше действие обрабатывает изменение конфигурации самостоятельно, система предотвратит это. Вместо этого обновляется только объект Configuration, а Compose перерисовывает интерфейс с новыми значениями.

Обработка изменений конфигурации непосредственно в Compose имеет несколько преимуществ:

  • Повышение производительности. Перекомпоновка интерфейса менее затратна, чем полный цикл воссоздания Activity, особенно при незначительных изменениях.
  • Плавные анимации. Если не перезапускать Activity, можно выполнять непрерывные анимации при изменении конфигурации, например плавно переключать макеты при повороте устройства.
  • Сохранение состояния. Сохранение экземпляра Activity снижает риск потери временного состояния интерфейса при таких событиях, как поворот экрана. Обратите внимание, что вам все равно нужно сохранять состояние при завершении процесса, инициированном системой.

Чтобы отключить воссоздание действий для определенных изменений конфигурации, добавьте тип конфигурации в android:configChanges в записи <activity> в файле AndroidManifest.xml. Возможные значения приведены в документации к атрибуту android:configChanges.

Следующий код манифеста отключает воссоздание Activity для MyActivity при изменении ориентации экрана и доступности клавиатуры:

<activity
    android:name=".MyActivity"
    android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
    android:label="@string/app_name">

Android 17

В Android 17 (уровень API 37) и более поздних версиях система больше не перезапускает действия по умолчанию при некоторых изменениях конфигурации, которые обычно не требуют полного воссоздания интерфейса. Вместо этого действия остаются активными и получают обновления через обратный вызов onConfigurationChanged(). В частности, были внесены следующие изменения:

Если для применения этих изменений приложению требуется полная перезагрузка ресурсов, вам нужно явно включить старое поведение, добавив атрибут android:recreateOnConfigChanges в файл манифеста. Этот атрибут позволяет указать, какие изменения конфигурации должны вызывать полную остановку, уничтожение и повторное создание действия, например:

<activity
    android:name=".MyActivity"
    android:recreateOnConfigChanges="keyboard|keyboardHidden|navigation|colorMode|touchscreen|...">
    ...
</activity>

Реагирование на изменения конфигурации

Jetpack Compose позволяет приложению проще реагировать на изменения конфигурации. Однако если вы отключите Activity для всех изменений конфигурации, где это возможно, ваше приложение все равно должно правильно обрабатывать изменения конфигурации.

Объект Configuration доступен в иерархии интерфейса Compose с локальной композицией LocalConfiguration. При каждом изменении значения функции, которые читают данные из LocalConfiguration.current, перекомпонуются. Подробнее о том, как работают локальные переменные CompositionLocal, рассказывается в статье Данные с локальной областью видимости с помощью CompositionLocal.

Пример

В примере ниже показано, как с помощью функции composable отобразить дату в определенном формате. Composable-функция реагирует на изменения конфигурации региональных настроек системы, вызывая ConfigurationCompat.getLocales с LocalConfiguration.current.

@Composable
fun DateText(year: Int, dayOfYear: Int) {
    val dateTimeFormatter = DateTimeFormatter.ofPattern(
        "MMM dd",
        ConfigurationCompat.getLocales(LocalConfiguration.current)[0]
    )
    Text(
        dateTimeFormatter.format(LocalDate.ofYearDay(year, dayOfYear))
    )
}

Чтобы избежать повторного создания Activity при смене локали, хост Activity, в котором размещен код Compose, должен отказаться от изменений конфигурации локали. Для этого задайте для параметра android:configChanges значение locale|layoutDirection.

Изменения конфигурации: основные понятия и рекомендации

Ниже приведены основные понятия, которые необходимо знать при работе с изменениями конфигурации.

  • Конфигурации. Конфигурации устройства определяют, как интерфейс будет отображаться для пользователя, например размер приложения, плотность пикселей, региональные настройки или тема системы. В Compose значения конфигурации можно получить с помощью LocalConfiguration.
  • Изменения конфигурации. Конфигурация меняется в результате действий пользователя. Например, пользователь может изменить настройки устройства или способ взаимодействия с ним. Запретить изменение конфигурации нельзя.
  • Воссоздание Activity. По умолчанию при изменении конфигурации Activity воссоздается. Это встроенный механизм для повторной инициализации состояния приложения для новой конфигурации.
  • Activity уничтожение. При воссоздании Activity система уничтожает старый экземпляр Activity и создает новый на его месте. Старый экземпляр устарел. Не сохраняйте ссылки на объекты с областью действия, ограниченной жизненным циклом, за пределами их области действия.
  • Статус. Статус в старом экземпляре Activity отсутствует в новом экземпляре Activity, поскольку это два разных экземпляра объекта. Вместо того чтобы связывать состояние с Activity, используйте рекомендуемые API для сохранения состояния приложения и пользователя, как описано в разделе Сохранение состояния интерфейса.
  • Отказ. Если вы откажетесь от воссоздания действий для определенного типа изменений конфигурации, ваше приложение должно будет правильно реагировать на новую конфигурацию.

Чтобы обеспечить удобство пользователей, следуйте приведенным ниже рекомендациям.

  • Будьте готовы к частым изменениям конфигурации. Не думайте, что изменения конфигурации происходят редко или никогда, независимо от уровня API, форм-фактора или набора инструментов для создания интерфейса. Когда пользователь изменяет конфигурацию, он ожидает, что приложения обновятся и продолжат работать правильно.
  • Сохранение состояния. Не теряйте состояние пользователя при воссоздании Activity. Сохраняйте состояние, как описано в разделе Сохранение состояния интерфейса, используя такие API, как ViewModel и rememberSaveable.
  • Не отключайте воссоздание в качестве быстрого решения. Не отключайте воссоздание Activity , чтобы избежать потери состояния. Если вы откажетесь от воссоздания активности, вам придется самостоятельно обрабатывать изменения. При этом вы все равно можете потерять состояние из-за Activity воссоздания при других изменениях конфигурации, завершения процесса или закрытия приложения. Полностью отключить Activity воссоздание невозможно. Сохраните состояние, как описано в разделе Сохранение состояния интерфейса.
  • Не избегайте изменений конфигурации. Не ограничивайте ориентацию, соотношение сторон или возможность изменения размера, чтобы избежать изменений конфигурации и воссоздания Activity. Это негативно влияет на пользователей, которые хотят использовать ваше приложение удобным для них способом.

Как обрабатывать изменения конфигурации на основе размера

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

Изменения размера бывают двух типов: значительные и незначительные. Значительное изменение размера экрана – это такое изменение, при котором для новой конфигурации применяется другой набор альтернативных ресурсов из-за разницы в размере экрана, например ширине, высоте или наименьшей ширине. К ним относятся ресурсы, определенные самим приложением, и ресурсы из его библиотек.

Ограничение воссоздания действий при изменении конфигурации на основе размера

Если отключить воссоздание Activity при изменении конфигурации на основе размера, система не будет воссоздавать Activity. Вместо этого он получает вызов в Activity.onConfigurationChanged. Все компоненты, считывающие значение LocalConfiguration.current, автоматически перекомпонуются, чтобы отразить новый размер.

Воссоздание Activity отключено для изменений конфигурации на основе размера, если в файле манифеста есть android:configChanges="screenSize|smallestScreenSize|orientation|screenLayout".

Дополнительные ресурсы

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

Документация

Просмотр контента