Введение

«Почему при переключении тёмной темы пересоздаётся Activity?» — вопрос, на котором отсеивается половина кандидатов. Тёмная тема кажется простой, но за ней стоит целый механизм: DayNight-темы, ночные ресурсы и конфигурационные изменения.

Тёмная тема перестала быть «фичей для гиков»: начиная с Android 10 (API level 29) она встроена в систему, и пользователи включают её на уровне ОС. Это значит, что любое приложение без поддержки тёмной темы выглядит как белый прямоугольник в тёмном интерфейсе. А на собеседованиях вопросы про тёмную тему — это проверка того, как вы работаете с ресурсами и тем, а не зазубрили ли вы названия атрибутов.

В этой статье разберём по порядку: что такое тёмная тема и зачем она нужна, как подключить её через DayNight и values-night, как правильно переключать режим в приложении и на какие подводные камни чаще всего натыкаются кандидаты.

Что такое тёмная тема и почему она важна

Тёмная тема — это не «инверсия цветов», а отдельный набор ресурсов, который подгружается в зависимости от ночного режима устройства.

Официальная документация выделяет три причины, по которым тёмная тема важна: она экономит заряд на экранах, где это поддерживается, улучшает видимость для пользователей с низким зрением и снижает нагрузку на глаза в условиях слабого освещения. Система умеет включать её тремя способами: через настройки (Settings > Display > Theme), через плитку в шторке уведомлений и вместе с режимом экономии заряда на устройствах Pixel.

Частая ошибка на собеседовании: кандидат говорит «тёмная тема — это просто тёмные цвета вместо светлых». Нет. Тёмная тема — это переключение набора ресурсов и атрибутов темы. Приложение не «перекрашивается» автоматически: для этого используется либо DayNight-тема, либо ночные альтернативные ресурсы.

Для разработчика тёмная тема сводится к двум вещам. Первая — тема, которая умеет переключаться между светлым и тёмным вариантом (это DayNight). Вторая — ресурсы, которые система подставляет в зависимости от ночного режима: директории вроде values-night/ и drawable-night/. Квалификатор night существует с API level 8, так что он не привязан к Android 10 — ночная тема системы лишь дала пользователям удобный переключатель.

Ещё один момент, который стоит держать в голове: официальная документация прямо рекомендует избегать хардкода цветов и использовать атрибуты темы — например, ?android:attr/textColorPrimary (почти чёрный в светлой теме и почти белый в тёмной) или ?attr/colorControlNormal для иконок. Это ключ ко всей статье: тёмная тема работает ровно потому, что вы не зашиваете цвета в код.

DayNight и values-night: как включить тёмную тему

Вся магия начинается с одной строки в themes.xml: родитель Theme.Material3.DayNight.

Чтобы приложение поддерживало тёмную тему, тема должна наследоваться от DayNight-варианта: Theme.AppCompat.DayNight, Theme.MaterialComponents.DayNight или Theme.Material3.DayNight. Так тема привязывается к системным флагам ночного режима и получает тёмный вариант по умолчанию, когда он включён.

values/themes.xml
<style name="Theme.MyApp" parent="Theme.Material3.DayNight">
    <item name="colorPrimary">@color/brand</item>
    <item name="colorOnPrimary">@color/white</item>
</style>

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

values/colors.xml
<resources>
    <color name="brand">#6750A4</color>
    <color name="surface">#FEF7FF</color>
</resources>
values-night/colors.xml
<resources>
    <color name="brand">#D0BCFF</color>
    <color name="surface">#141218</color>
</resources>

Почему это спрашивают на собеседовании

values-night — это не «особая папка тёмной темы», а обычный механизм альтернативных ресурсов с квалификатором night (работает с API level 8). Если ресурс объявлен только в values-night/ и нигде больше, приложение упадёт на устройстве без ночного режима — об этом любят спрашивать, и к этому мы ещё вернёмся.

Та же логика работает для любых типов ресурсов: иконки в drawable-night/, стили в values-night/themes.xml, даже строки. Система сама выбирает нужный набор по текущей конфигурации устройства — вам не нужно писать ни строчки кода. На собеседовании стоит проговорить это вслух: «тёмная тема — это переключение ресурсов через квалификатор night, а тема DayNight лишь привязывает приложение к системному ночному режиму».

Переключение темы внутри приложения

Три режима: светлый, тёмный и «как в системе». Последний — рекомендованный вариант по умолчанию.

Если вы даёте пользователю выбор темы в настройках приложения, варианты обычно такие: Light, Dark и System default. В терминах AppCompat это MODE_NIGHT_NO, MODE_NIGHT_YES и MODE_NIGHT_FOLLOW_SYSTEM. В коде переключение выглядит так:

SettingsActivity.kt
private fun applyDarkMode() {
    AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
}

Здесь есть два нюанса, которые часто всплывают на собеседованиях. Первый: с версии AppCompat 1.1.0 setDefaultNightMode() автоматически пересоздаёт запущенные Activity — поэтому состояние экрана нужно хранить не в переменных, а там, где оно переживает пересоздание (например, во ViewModel или savedInstanceState). Второй: начиная с API level 31 система рекомендует использовать UiModeManager.setApplicationNightMode(), чтобы тема подхватывалась ещё на сплэш-экране.

MainActivity.kt
private fun applyDarkMode() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        getSystemService(UiModeManager::class.java)
            .setApplicationNightMode(UiModeManager.MODE_NIGHT_YES)
    } else {
        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
    }
}

Типичный промах: кандидат уверенно рассказывает про setDefaultNightMode(), но не может объяснить, почему Activity пересоздаётся. Ответ: смена темы — это конфигурационное изменение uiMode. Activity пересоздаётся так же, как при повороте экрана. Если нужно обработать смену вручную, можно объявить android:configChanges="uiMode" и переопределить onConfigurationChanged() — но это частный случай, а не стандарт.

Проверить текущий режим можно через Configuration — этот код есть в официальной документации, и его стоит помнить:

MainActivity.kt
fun isDarkTheme(configuration: Configuration): Boolean {
    val nightMode = configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
    return nightMode == Configuration.UI_MODE_NIGHT_YES
}
  • DayNight-тема связывает приложение с системным ночным режимом.
  • values-night/ — альтернативные ресурсы, которые система выбирает сама.
  • MODE_NIGHT_FOLLOW_SYSTEM — рекомендуемый вариант по умолчанию.
  • Смена темы = изменение конфигурации uiMode = пересоздание Activity.
  • На API 31+ для переключения используем UiModeManager, на более старых — AppCompatDelegate.

Force Dark и подводные камни

Force Dark — это «костыль» Android 10: система сама инвертирует светлую тему, но полагаться на него нельзя.

Когда тёмная тема появилась в Android 10, Google добавила механизм Force Dark: приложение-«незнакомец» со светлой темой система автоматически переводит в тёмные цвета перед отрисовкой. Для этого нужно явно включить атрибут android:forceDarkAllowed="true" в теме Activity. Звучит удобно, но есть принципиальное ограничение: если ваша тема наследуется от DayNight, Force Dark не применяется — потому что переключение уже работает через ресурсы. Так что Force Dark — это временное решение для легаси, а не стратегия.

Главная ошибка на собеседовании: кандидат предлагает «реализовать тёмную тему» через Force Dark или через код вида if (dark) setBackgroundColor(black). Это антипаттерны: первый не работает с DayNight, второй — хардкод, который убивает всю идею ресурсов. Правильный путь — атрибуты темы и ночные ресурсы.

Из той же серии — классические грабли, перечисленные в документации: жёстко заданный белый фон иконки или текста, setTextColor(Color.BLACK) в коде, статичные по цвету drawable. В тёмной теме всё это превращается в нечитаемый UI. Решения два: использовать атрибуты темы (?attr/colorSurface, ?attr/colorOnSurface) или выносить значения в ресурсы с ночными альтернативами.

Способа включить тёмную тему системой

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

И ещё один нюанс из практики: уведомления и виджеты. Для уведомлений используйте системные шаблоны (например, MessagingStyle) — система сама применит нужную тему. А вот для своих виджетов на рабочем столе придётся проверить оба режима вручную. Это та самая конкретика, которая выделяет кандидата: знание документации + реальный опыт.

Кто я и как помогу подготовиться

Меня зовут Рустем Бикбулатов, я senior Android-разработчик и провожу технические собеседования. Помогаю Android-разработчикам с опытом 1–2 года переходить на уровень middle за 4–8 недель: закрываю пробелы в Kotlin, Coroutines, архитектуре и тренирую ответы на собеседованиях в Яндекс, Авито и других сильных компаниях.

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

Нужна помощь в переходе на middle?

Бесплатная диагностика: разберу ваши пробелы и составлю план роста за 30–40 минут. Без обязательств — если менторство вам не подойдёт, честно скажу.

Итоги

Подведём итоги по тёмной теме:

  • Тёмная тема — это ресурсы и тема, а не перекраска в коде. DayNight-тема привязывает приложение к системному режиму, а values-night/, drawable-night/ и другие ночные директории подставляют нужные значения автоматически.
  • Смена темы — это конфигурационное изменение uiMode. Activity пересоздаётся, как при повороте экрана. Переключение делаем через AppCompatDelegate.setDefaultNightMode() или UiModeManager.setApplicationNightMode() на API 31+.
  • Хардкод цветов — главный враг. setTextColor(Color.BLACK) и статичные по цвету иконки ломают тёмную тему. Используйте атрибуты темы и ночные ресурсы — это же ответ на вопрос «почему Activity пересоздаётся при смене темы».

Главное, что нужно запомнить

DayNight + ночные ресурсы + атрибуты темы вместо хардкода — это и есть ответ про тёмную тему. Если на собеседовании вы расскажете про пересоздание Activity через изменение конфигурации uiMode и про MODE_NIGHT_FOLLOW_SYSTEM как рекомендуемый дефолт — вы уже сильнее большинства кандидатов.

Если чувствуете, что такие связки между темами и жизненным циклом «плавают» в голове — не нужно зубрить дальше. Напишите в Telegram, разберём вашу ситуацию и составим план подготовки.