Разделение APK по ABI в Android Gradle Plugin: настройка и оптимизация

Узнайте, как с помощью Android Gradle Plugin настроить разделение APK по ABI, избавиться от лишних .so‑файлов и сократить размер приложения до минимума.

Бесплатная консультация юриста

+7 (800) 301-07-30

Реклама jurik.ru

С ростом количества устройств с разными процессорными архитектурами (ABI) размер APK‑файла становится одной из главных проблем. Универсальный APK, содержащий .so-библиотеки для всех поддерживаемых ABI, может весить несколько мегабайт больше, чем действительно необходимо пользователю. К счастью, Android Gradle Plugin (AGP) позволяет автоматически разбивать приложение на несколько APK, каждый из которых содержит только нужные нативные библиотеки. В этой статье мы подробно разберём, как правильно сконфигурировать build.gradle для генерации отдельных APK под каждый ABI, а также рассмотрим нюансы версии кода, именования файлов, CI‑интеграции и отладки.


Содержание

  1. Зачем нужны split‑APK по ABI?
  2. Базовая конфигурация splits.abi
  3. Управление versionCode для разных ABI
  4. Именование и вывод артефактов
  5. Разделение только для релиз‑сборок
  6. Интеграция в CI/CD (GitHub Actions, GitLab CI)
  7. Продвинутые настройки: фильтры, universal APK, exclude ABI
  8. Типичные ошибки и как их избежать

Зачем нужны split‑APK по ABI?

Основные причины:

  • Сокращение размера загрузки. Пользователь получает только те нативные библиотеки, которые соответствуют его устройству.
  • Уменьшение объёма трафика. При распространении через Google Play каждый APK будет иметь меньший размер, что положительно сказывается на конверсии.
  • Оптимизация кеша. Устройства кэшируют только нужный APK, а не весь набор библиотек.
  • Упрощённый анализ. При тестировании можно быстро собрать APK только под нужный ABI.

Пример: если ваш проект использует библиотеку Realm, то в универсальном APK она может занимать почти 5 МБ (только нативные .so‑файлы). При split‑APK размер загрузки может уменьшиться на 3‑4 МБ.


Базовая конфигурация splits.abi

Ниже, минимальная настройка, которая генерирует отдельные APK для armeabi‑v7a, arm64‑v8a, x86 и x86_64. Эта конфигурация подходит для большинства проектов.


android {
 // ... остальные настройки (compileSdkVersion, defaultConfig, etc.)

 // 1️⃣ Включаем split‑APK по ABI
 splits {
 abi {
 // Включаем генерацию отдельных APK
 enable true

 // Сбрасываем список ABI по умолчанию (все поддерживаемые)
 reset

 // Указываем, какие именно ABI хотим собрать
 include "armeabi-v7a", "arm64-v8a", "x86", "x86_64"

 // Если нужен универсальный APK, задаём true; иначе false
 universalApk false
 }
 }

 // 2️⃣ Настраиваем versionCode (см. раздел ниже)
 defaultConfig {
 // …
 versionCode 100
 versionName "1.0.0"
 }

 // 3️⃣ При необходимости задаём отдельные подписи для релиз‑сборок
 signingConfigs {
 release {
 // путь к keystore, пароли и т.д.
 }
 }

 buildTypes {
 release {
 signingConfig signingConfigs.release
 minifyEnabled true
 proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
 }
 debug {
 // Debug‑сборки обычно без split‑APK (см. раздел 4)
 }
 }
}

После выполнения команды ./gradlew assembleRelease в каталоге app/build/outputs/apk/release/ появятся файлы:

  • app-arm64-v8a-release.apk
  • app-armeabi-v7a-release.apk
  • app-x86-release.apk
  • app-x86_64-release.apk

Управление versionCode для разных ABI

Google Play требует уникального versionCode для каждого APK в наборе Multi‑APK. Самый простой способ – добавить к базовому коду небольшое смещение, зависящее от ABI.

Пример функции‑расширения


def abiVersionCodeOffset = [
 "armeabi-v7a": 1,
 "arm64-v8a" : 2,
 "x86" : 3,
 "x86_64" : 4
]

// Функция, которая возвращает текущий ABI из имени задачи
def getAbiFromTask {
 def taskName = gradle.startParameter.taskNames.find { it.contains("assemble") }
 if (taskName == null) return null
 // Пример: assembleArm64V8aRelease → arm64-v8a
 return abiVersionCodeOffset.keySet.find { taskName.toLowerCase.contains(it.replace("-", "")) }
}

// Переопределяем versionCode только для релиз‑сборок с split‑APK
android.applicationVariants.all { variant ->
 if (variant.buildType.name == "release" && variant.productFlavors.isEmpty) {
 def abi = getAbiFromTask
 if (abi != null) {
 variant.outputs.each { output ->
 // Базовый versionCode из defaultConfig + смещение
 output.versionCodeOverride = defaultConfig.versionCode + abiVersionCodeOffset[abi]
 }
 }
 }
}

Что происходит?

  • Базовый versionCode задаётся в defaultConfig (например, 100).
  • Для каждого ABI к нему прибавляется своё смещение (1‑4). В итоге получаем коды 101‑104.
  • Google Play воспринимает их как отдельные версии, но пользователь получит только один APK, соответствующий его устройству.

Если вы используете versionCode в виде даты (20230802), то к нему также можно добавить смещение, например 20230802 * 10 + offset, чтобы сохранить порядок версий.


Именование и вывод артефактов

По умолчанию Gradle генерирует имена вида app--release.apk. При необходимости можно задать собственный шаблон, используя outputFileName:


android.applicationVariants.all { variant ->
 if (variant.buildType.name == "release") {
 variant.outputs.each { output ->
 def abi = output.getFilter(com.android.build.OutputFile.ABI)
 if (abi != null) {
 // Пример: myapp_v1.0.0_arm64-v8a.apk
 def newName = "myapp_v${defaultConfig.versionName}_${abi}.apk"

 outputFileName = newName
 }
 }
 }
}

Это удобно, если вы публикуете APK вручную (например, в корпоративный магазин) и хотите, чтобы имя сразу отражало версию и целевую архитектуру.


Разделение только для релиз‑сборок

Для отладки обычно удобно иметь один «fat‑apk», содержащий все ABI, чтобы не переключать устройство. Чтобы отключить split‑APK в debug‑режиме, добавьте проверку на текущий тип сборки:


android {
 splits {
 abi {
 // Включаем только если собираем релиз
 enable project.gradle.startParameter.taskNames.any { it.contains("Release") }
 reset
 include "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
 universalApk false
 }
 }
}

Или используйте системное свойство, которое удобно задавать из CI:


def splitApks = project.hasProperty('splitApks') && project.property('splitApks') == 'true'

android {
 splits {
 abi {
 enable splitApks
 reset
 include "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
 universalApk false
 }
 }
}

Тогда запуск ./gradlew assembleDebug создаст один универсальный APK, а ./gradlew assembleRelease -PsplitApks=true – набор split‑APK.


Интеграция в CI/CD

Ниже пример конфигурации GitHub Actions, который собирает отдельные APK для каждой архитектуры и загружает их в артефакты:


name: Build split APKs

on:
 push:
 branches: [ main ]
 pull_request:

jobs:
 build:
 runs-on: ubuntu-latest
 env:
 GRADLE_OPTS: "-Dorg.gradle.jvmargs='-Xmx3g -Dfile.encoding=UTF-8'"

 steps:
 ‒ name: Checkout repository
 uses: actions/checkout@v4

 ‒ name: Set up JDK 17
 uses: actions/setup-java@v4
 with:
 distribution: temurin
 java-version: 17

 ⎯ name: Cache Gradle packages
 uses: actions/cache@v3
 with:
 path: |
 ~/.gradle/caches
 ~/.gradle/wrapper
 key: ${{ runner.os }}-gradle-${{ hashFiles('/.gradle', '/gradle-wrapper.properties') }}
 restore-keys: |
 ${{ runner.os }}-gradle-

 ⎯ name: Build release split APKs
 run: ./gradlew clean assembleRelease -PsplitApks=true

 ⎯ name: Upload APKs as artifacts
 uses: actions/upload-artifact@v4
 with:
 name: split-apks
 path: app/build/outputs/apk/release/*.apk

Для GitLab CI процесс аналогичен – достаточно добавить переменную splitApks=true в скрипт сборки;


Продвинутые настройки

Добавление universal APK

Если вам всё же нужен «универсальный» APK (например, для тестов), установите universalApk true. При этом будет создан дополнительный файл app-universal-release.apk, содержащий все ABI.

Исключение конкретных ABI

Иногда требуется собрать APK только для arm64‑v8a и x86_64, исключив остальные. Делается так:


splits {
 abi {
 enable true
 reset
 include "arm64-v8a", "x86_64"
 universalApk false
 }
}

Комбинация с splits.density

Можно одновременно разбивать по плотности экранов и по ABI. В этом случае будет сгенерировано ABI × density комбинаций. Чтобы избежать экспоненциального роста количества APK, рекомендуется использовать только один из вариантов (обычно ABI) или ограничить набор плотностей.


splits {
 density {
 enable true
 reset
 include "mdpi", "hdpi", "xhdpi", "xxhdpi"
 universalApk false
 }
 abi {
 enable true
 reset
 include "armeabi-v7a", "arm64-v8a"
 }
}

Работа с Android App Bundle (AAB)

Если вы публикуете приложение в виде AAB, то Google Play автоматически делает split‑APK на стороне сервера. Тем не менее, иногда хочется протестировать локально отдельные ABI‑пакеты. Для этого можно использовать bundletool:



./gradlew bundleRelease

bundletool build-apks 
 --bundle=app-release.aab 
 --output=app.apks 
 --mode=universal 
 --device-spec=device-spec.json

unzip -p app.apks universal.apk > app-arm64-v8a.apk

Типичные ошибки и как их избежать

  • Неправильный versionCode → конфликт в Play Console. Убедитесь, что каждый ABI имеет уникальный код. Лучше использовать автоматическое смещение, как показано в разделе 3.
  • Отсутствие нативных библиотек в jniLibs → split‑APK не создаёт файлы. Проверьте, что .so‑файлы находятся в src/main/jniLibs/<abi> или собираются через CMake/NDK.
  • Слишком много комбинаций (ABI × density) → превышение лимита 100 APK. Google Play ограничивает количество APK в наборе. Оставляйте только необходимые варианты.
  • Отладка с split‑APK. При запуске из Android Studio IDE может выбрать неверный APK. Отключайте split‑APK для debug (см. раздел 5) или используйте Run > Edit Configurations > Deploy APK from app bundle.
  • Кеширование в CI. При изменении списка ABI необходимо сбрасывать кеш Gradle, иначе могут остаться старые артефакты.

Разделение APK по ABI – простой и эффективный способ уменьшить размер загрузки, ускорить установку и сэкономить трафик пользователей. Основные шаги:

  1. Включить splits.abi в build.gradle.
  2. Указать нужные ABI через include и отключить universalApk, если он не нужен;
  3. Обеспечить уникальный versionCode для каждой комбинации (см. функцию‑смещение).
  4. Настроить именование файлов, если требуется специфический шаблон.
  5. Отключить split‑APK для debug‑сборок, чтобы упростить отладку.
  6. Интегрировать процесс в CI/CD, используя параметры -PsplitApks=true для гибкого управления.

Следуя этим рекомендациям, вы сможете легко поддерживать несколько оптимизированных APK, улучшить пользовательский опыт и сократить расходы на доставку приложений.


Автор: ваш виртуальный помощник. Дата: 17:28:10