Введение

«Что такое ресурсы в Android?» — вопрос-фильтр. Кандидат, который отвечает «это картинки и тексты в папке res», и кандидат, который объясняет, как Android выбирает нужный ресурс под конкретное устройство, — это два разных уровня.

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

На собеседованиях тема ресурсов всплывает не «в лоб», а в десятке смежных вопросов: почему нельзя захардкодить строку, зачем нужен класс R, что такое drawable-hdpi, почему приложение падает на устройстве с другим языком. Если вы понимаете механику — отвечаете на всё это без шпаргалок.

Разберём по порядку: что такое ресурсы и зачем они нужны, как устроен res/, как работают квалификаторы и как обратиться к ресурсу из кода и XML.

Что такое ресурсы и почему их выносят из кода

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

Что это значит на практике? Строку «Подтвердить» вы не пишете прямо в Kotlin-файле — вы объявляете её в strings.xml и обращаетесь по имени. Картинку не кладёте в папку с кодом — кладёте в res/drawable. Зачем так делать, если можно проще?

Типичная ошибка новичка: хардкодить строки и цвета прямо в коде. Линтер подсветит это как HardcodedText, но главная проблема глубже: захардкоженную строку нельзя перевести, нельзя изменить в одном месте, и она не подстроится под конфигурацию устройства. Ресурсы — это разделение «данные» и «логика».

Вынос ресурсов из кода даёт три вещи, о которых стоит сказать на собеседовании:

  • Независимое управление. Строки, цвета, размеры меняются в одном месте, без правки логики.
  • Локализация. Один и тот же код + разные strings.xml под разные языки.
  • Адаптивность. Один и тот же идентификатор ресурса, но разные файлы под разные плотности экрана, ориентацию и ночной режим.

Именно третий пункт — то, что отличает «знаю, что папка есть» от «понимаю, как работает». Дальше разберём, как это устроено.

res/: структура и типы ресурсов

Все ресурсы лежат в каталоге res/ проекта, и каждый тип — в отдельной подпапке. Имя подпапки не произвольное: оно определяет, как ресурс компилируется и как к нему обращаться.

Вот базовые каталоги из документации:

  • drawable/ — растровые файлы (PNG, .9.png, JPG, GIF) и XML-графика: формы, state list, векторные картинки.
  • mipmap/ — иконки запуска под разные плотности экрана. Отдельный каталог, потому что лаунчер запрашивает иконку в большем разрешении, чем drawable.
  • values/ — «простые значения»: строки, цвета, размеры, стили. Один файл может содержать сколько угодно ресурсов: каждый дочерний элемент <resources> определяет один ресурс. <string> создаёт R.string, <color> — R.color.
  • raw/ — произвольные файлы, которые нужны как есть; читаются через openRawResource() как InputStream.
  • xml/ — произвольные XML-файлы конфигурации, читаются через getXml().
  • font/ — файлы шрифтов TTF, OTF, TTC или XML с <font-family>.
  • layout/, menu/, anim/, color/ — разметки, меню, анимации, списки цветов.

Ключевой факт

В values/ каждый ресурс определяется отдельным XML-элементом — поэтому файл может называться как угодно, а типы ресурсов внутри перемешиваться. А вот в drawable/ или layout/ один файл — это один ресурс с именем файла.

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

Квалификаторы: один ресурс — много версий

Самое интересное начинается здесь. Ресурсы по умолчанию лежат в «чистых» каталогах: res/values, res/drawable. А альтернативные версии — в каталогах с именем вида <имя>-<квалификатор>: values-en, drawable-hdpi, drawable-night.

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

res/
res/
    values/        strings.xml    // по умолчанию
    values-en/     strings.xml    // английский
    values-pl/     strings.xml    // польский
    drawable/      icon.png       // по умолчанию
    drawable-hdpi/ icon.png       // для высокой плотности
    drawable-night/ background.png // для ночного режима

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

Два правила, на которых сыплются кандидаты:

  • Порядок квалификаторов фиксирован. Если их несколько — они идут строго в порядке таблицы из документации. Нарушили порядок — ресурс будет проигнорирован.
  • Ресурсы по умолчанию обязательны. Если строка есть только в values-en, а система на устройстве — русская, приложение упадёт с ResourceNotFound. Ресурс по умолчанию — страховка от любой непредусмотренной конфигурации.

Частая ошибка на собеседовании: «Ну я добавил значения в values-en, зачем ещё values?». Затем, что Android не обязан угадать. Нет подходящей альтернативы — нет ресурса. Документация подчёркивает: всегда указывайте ресурсы по умолчанию, иначе приложение аварийно завершается.

Доступ к ресурсам: R-класс, @string и @drawable

Идентификатор ресурса — это всегда связка «тип + имя». На этапе компиляции инструмент aapt генерирует класс R: для каждого типа — подкласс (R.string, R.drawable, R.color), внутри — статическое целое для каждого ресурса (R.drawable.icon).

Из кода обращение выглядит так:

MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val title = getString(R.string.app_title)
        val icon = ContextCompat.getDrawable(this, R.drawable.ic_logo)
    }
}

В XML-файлах — тот же механизм, но в синтаксисе @тип/имя:

activity_main.xml
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/app_title"
    android:drawableStart="@drawable/ic_logo" />

Три детали, которые показывают глубину:

  • Через Context. Вне UI-иерархии — в ViewModel, Repository, Service — ресурсы разрешаются через Context: getString(), getColor() и другие методы. Это ещё одна причина, почему Context нужно прокидывать в низкие слои аккуратно.
  • Ресурсы платформы. Можно ссылаться на системные ресурсы через android.R.drawable.ic_menu_info_details — так берут стандартные иконки и стили.
  • Compose работает с теми же идентификаторами. stringResource(R.string.app_title) и painterResource(R.drawable.ic_logo) — безопасный доступ с учётом рекомпозиции.
  • Ресурсы — статическое содержимое, вынесенное из кода: строки, картинки, цвета, шрифты.
  • Каждый тип — своя подпапка в res/; в values/ один файл может содержать много ресурсов.
  • Квалификаторы в имени каталога подбирают версию под конфигурацию устройства, порядок квалификаторов фиксирован.
  • Идентификатор генерируется в R-класс: из кода — R.string.name, из XML — @string/name.
  • Ресурсы по умолчанию обязательны, иначе — ResourceNotFound.

Почему это важно для собеседования? Потому что вопросы про ресурсы — это проверка системного мышления. Кандидат, который связывает res/, R-класс, квалификаторы и конфигурацию устройства в одну картину, отвечает на смежные темы — плотность экрана, локализацию, dp — без натуги.

Когда я провожу собеседования, я спрашиваю про ресурсы именно так: не «что лежит в res/», а «что произойдёт, если строки есть только в values-en». И это отсеивает тех, кто заучил структуру, от тех, кто понимает механику. Если такие пробелы накапливаются — это сигнал, что нужна системная подготовка, а не заучивание новых вопросов.

Разбираете платформенные темы перед собеседованием?

Ресурсы, жизненные циклы, интенты — это фундамент, на котором держатся ответы уровня middle. На мок-собеседованиях тренируем их до уверенных ответов.

Итоги

Ресурсы в Android — это не «папка с картинками», а продуманная система: статическое содержимое отделено от кода, каждый тип лежит в своём каталоге res/, а альтернативные версии под конфигурации устройств описываются квалификаторами в именах каталогов.

Три тезиса, которые стоит унести с собой:

  • Идентификатор — это «тип + имя». Класс R генерируется на этапе компиляции: из кода — R.string.name, из XML — @string/name. Один идентификатор — разные версии ресурса.
  • Квалификаторы — это адаптивность. values-en, drawable-hdpi, drawable-night: Android выбирает лучшую версию в рантайме. Но без ресурсов по умолчанию приложение падает.
  • Ресурсы — часть архитектуры. Они причина, по которой Context прокидывают аккуратно, и причина, по которой локализация и адаптация под устройства не превращаются в простыню условий.

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