Что означают versionCode и versionName в Android

Короткий ответ: versionCode — целое внутреннее число, по которому Android/Google Play определяют, какая сборка новее; versionName — человекочитаемая строка, которую видят пользователи. Правильная схема нумерации и автоматизация versionCode предотвращают проблемы с апдейтами и публикацией.

Какие «версии» есть в Android и зачем их различать

  • API level (версия ОС) — управляет совместимостью и указывается через minSdkVersion / targetSdkVersion. Это не версия приложения.
  • versionName — свободная строка, видимая пользователю: "1.4.3", "2026.03.15", "1.0-beta".
  • versionCode — целое число, которое сравнивает Google Play и система обновлений: новая сборка разрешена только если versionCode > установленного.

Важно: изменив только versionName без увеличения versionCode, вы не обновите приложение для пользователей.

Как пользоваться versionCode и versionName на практике

  1. Всегда увеличивайте versionCode при каждой публикации в Play Console.
  2. Храните единую логику формирования versionCode (скрипт/CI/файл), чтобы никто не забыл инкремент.
  3. Для разных каналов (prod/beta/internal) используйте либо разные applicationId для внутренних билдов, либо схему кодов с «шлюзами», чтобы не пересекаться.

Для публичных релизов назначайте понятный versionName (семантика или дата), а versionCode делайте автоматически увеличиваемым числом — это простая и надёжная комбинация.

Распространённые схемы нумерации и примеры формул

  • Семантика MAJOR.MINOR.PATCH:
    • Формула в code: versionCode = MAJOR10000 + MINOR100 + PATCH
    • Примеры: 1.0.0 → 10000, 1.2.3 → 10203, 2.0.0 → 20000
    • Удобно восстанавливать версию по коду, но заранее планируйте разрядность.
  • Простая инкрементальная:
    • Каждый релиз: versionCode = previous + 1
    • Надёжно и просто, но по коду не видно масштаба изменений.
  • Дата + build:
    • versionCode = YYYYMMDD * 100 + BUILD
    • Пример: 20260315, build 2 → 2026031502
    • Удобно для CI, но следите за пределом int (~2_147_483_647).

Пример конфигурации Gradle (Kotlin DSL):

android {
  defaultConfig {
    val major = 1
    val minor = 4
    val patch = 3
    versionName = "$major.$minor.$patch"
    versionCode = major * 10000 + minor * 100 + patch
  }
}

Пример с BUILD_NUMBER из CI:

val build = System.getenv("BUILD_NUMBER")?.toIntOrNull() ?: 0
versionName = "$major.$minor.$patch ($build)"
versionCode = major * 1_000_000 + minor * 10_000 + patch * 100 + build

Частые ошибки

  • Забили инкремент versionCode — Play Console не примет сборку. Решение: инкремент в CI.
  • Понизили схему (перешли на другую формулу) и получили меньший код — добавьте смещение (offset), чтобы новые коды были > текущих.
  • Одинаковый versionName для разных билдов — запутывает поддержку. Для тестовых каналов добавляйте постфиксы: "1.0.0-beta (45)".
  • Переполнение int — проверьте диапазон выбранной формулы и рассчитайте запас лет вперёд.

Рекомендации для команды

  • Выберите схему заранее и зафиксируйте её в VERSIONING.md.
  • Автоматизируйте generation/versionCode в CI и храните единственный источник правды.
  • Привязывайте каждую публичную versionName к строке в changelog — так вы избегаете разницы поведения у пользователей с одинаковой versionName.

FAQ

  • Нужно ли менять versionName при каждом багфиксе? Нет — достаточно изменять versionCode; но для публичных релизов лучше отражать патчи в versionName.
  • Можно ли публиковать разные каналы с одним applicationId и перекрывающимися кодами? Нет: versionCode должен глобально расти внутри одного applicationId; для внутренних билдов часто используют отдельный applicationId suffix.
  • Как быстро восстановить корректную последовательность кодов при смене схемы? Найдите максимальный опубликованный versionCode в Play Console и добавьте смещение (константу) к новой формуле, чтобы все новые коды были больше этого числа.

Не меняйте логику формирования versionCode без плана миграции: это почти всегда приводит к блокировкам при загрузке в Play Console.