Ручная сборка APK

Настройка и сборка проекта в Android Studio — дело довольно простое, так как gradle делает огромное количество работы за разработчика. Это, безусловно, хорошо и удобно, но знать, что там происходит под капотом будет полезно и интересно. Предлагаю собрать простой проект в apk без использования IDE и систем сборки.

Я буду работать в Linux, так как терминал там крайне удобный. Для Windows и macOS все действия с инструментами из Android SDK и JDK выполняются абсолютно так же, а загрузка файлов и настройка окружения отличаются — это можно сделать руками из UI.

Запускаем терминал и поехали.

Подготовка

Для начала нужно установить JDK 8, так как Android SDK не работает с более новыми версиями. Обновляем зависимости:

sudo apt-get update && sudo apt-get upgrade

Если нужно, удаляем существующую версию JDK:

sudo apt-get purge openjdk-\*

И устанавливаем восьмую:

sudo apt-get install openjdk-8-jdk-headless

Создаём директорию «android» и переходим в неё

mkdir android
cd android

Теперь нужно скачать последнюю версию Android SDK Tools, которую можно найти по ссылке.

Скачиваем файл:

wget --output-document=sdk.zip https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip

Распаковываем архив sdk.zip в директорию «sdk» и удаляем его:

unzip -q sdk.zip -d sdk
rm sdk.zip

Для дальнейшей работы c SDK необходимо согласиться с лицензией. Для этого надо создать специальный файл:

mkdir sdk/licenses
printf "8933bad161af4178b1185d1a37fbf41ea5269c55\nd56f5187479451eabf01fb78af6dfcb131a6481e" > sdk/licenses/android-sdk-license

Нужно установить необходимые компоненты Android SDK. Для этого есть специальная программа sdkmanager, которая находится в каталоге sdk/tools/bin. Обновляем существующие компоненты:

sdk/tools/bin/sdkmanager --update

И устанавливаем компоненты, которые нам понадобятся.

sdk/tools/bin/sdkmanager "platform-tools" "build-tools;28.0.3" "platforms;android-28" "emulator" "system-images;android-22;default;armeabi-v7a"

Установка производится вызовом sdkmanager со списком компонентов через пробел.

  • «platform-tools» — набор инструментов для работы с Android, включая adb и fastboot, знакомые всем, кто занимался перепрошивкой;
  • «build-tools;28.0.3» — последняя версия инструментов для сборки, включает в себя упаковщики, компиляторы и сопутствующие утилиты;
  • «platforms;android-28» — библиотеки и ресурсы для Android версии 28;
  • «emulator» — собственно эмулятор;
  • «system-images;android-22;default;armeabi-v7a» — образ для эмулятора. Версия api — 22, архитектура — armv7.

Все компоненты можно найти в соответствующих каталогах.

Зарегистрируем переменную ANDROID_HOME, которая будет указывать на каталог sdk:

export ANDROID_HOME=$PWD/sdk/

И несколько путей, чтобы не пришлось вбивать пути до инструментов вручную:

export PATH=$PATH:$PWD/sdk/platform-tools/
export PATH=$PATH:$PWD/sdk/emulator/
export PATH=$PATH:$PWD/sdk/build-tools/28.0.3/
export PATH=$PATH:$PWD/sdk/tools/bin/

Проект

Загружаем архив с проектом, распаковываем и удаляем архив:

wget --output-document=project.zip https://download.illuzor.com/blog/project.zip
unzip -q project.zip -d project
rm project.zip

Переходим в каталог проекта:

cd project

Структура проекта:

В res/layout лежит макет активити activity_main.xml:

Макет простейший — FrameLayout с кнопкой по центру.

В res/values — файл со строками strings.xml:

В scr — манифест AndroidManifest.xml:

Обратите внимание на нод uses-sdk. При работе в Android Studio мы прописываем эти версии в gradle конфиге, и уже gradle при сборке прописывает версии в манифест, а также сливает манифесты подключенных библиотек в основной AndroidManifest.xml.

И один класс в src/com/illuzor/buildtest под именем MainActivity.java:

Здесь только обрабатывается клик по кнопке и выводится тост.

Также есть несколько дополнительных каталогов:

  • apk — для apk файлов;
  • compiled_classes — для Java байткода (файлы с расширением .class);
  • compiled_res — для скомпилированных ресурсов;
  • dex — для dex байткода (байткод виртуальной машины андроида).

Сборка

Нужно скомпилировать класс MainActivity.java, но это невозможно, так как в нём используется класс R.java со ссылками на ресурсы. Сначала скомпилируем ресурсы, сгенерируем R.java и соберём сырой apk файл. Для этого нужна программа aapt2 (Android Asset Packaging Tool). Первое, что надо сделать — скомпилировать ресурсы(у нас это activity_main.xml и strings.xml) командой compile:

aapt2 compile res/values/strings.xml res/layout/activity_main.xml -o compiled_res

В каталоге compiled_res появятся два файла: layout_activity_main.xml.flat и values_strings.arsc.flat. Это скомпилированные в специальный бинарный формат ресурсы. При реальной работе в IDE каждый файл ресурсов компилируется отдельно, это нужно для ускорения инкрементальной компиляции, то есть, чтобы пересборки apk длились сильно меньше, чем первая сборка, когда собирается весь проект.

Командой link генерируем класс R.java и собираем сырой apk, который будет содержать ресурсы и манифест:

aapt2 link -o apk/unsigned_app.apk -I ../sdk/platforms/android-28/android.jar --manifest src/AndroidManifest.xml -R compiled_res/*.flat --java src --auto-add-overlay

В каталоге src/com/illuzor/buildtest появится файл R.java примерно такого вида:

В каталоге apk появится файл unsigned_app.apk. Так как apk представляет собой zip архив, можно его распаковать и посмотреть, что внутри.

Внутри архива лежит манифест, resources.arsc — ресурсы в бинарном формате, понятном андроиду и макет activity_main.xml. Установить этот apk на устройство не получится, так как он не подписан и не содержит байткода.

Теперь класс R.java у нас есть и можно скомпилировать его вместе с MainActivity.java. Делается это компилятором Java (javac):

javac src/com/illuzor/buildtest/*.java -d compiled_classes -cp ../sdk/platforms/android-28/android.jar

Ключём -cp указываем путь к android.jar, так как в классе MainActivity используются классы из этой библиотеки, например Activity, Button, View.

Код скомпилирован и лежит в каталоге compiled_classes:

Воспользуемся программой d8, которая компилирует Java байткод в байткод виртуальной машины андроида(ART). D8, он же dex compiler, он же dexer берёт файлы .class и компилирует их в один(или не один в случае с multidex) файл classes.dex. Ему не важно, откуда взялся Java байткод — он может быть выдан компилятором javac или kotlinc(компилятор Kotlin) или же вообще написан вручную. Собственно так это с котлином и работает, хотя в d8 и есть kotlin specific оптимизации. Вводим команду:

d8 compiled_classes/com/illuzor/buildtest/*.class --output dex

Появляется файл classes.dex в каталоге dex. Это и есть байткод, которого не хватает в apk.

А теперь странное. Я не смог понять, как средствами Android SDK собрать apk с classes.dex внутри. Возможно документация плохо написана, а может я не понимаю чего-то очевидного, но разобраться с этим вопросом у меня не получилось. Если кто-нибудь знает, подскажите. Самое простое и очевидное, что пришло в голову — просто зазиповать classes.dex в unsigned_app.apk:

zip -uj apk/unsigned_app.apk dex/classes.dex

Если сейчас распаковать unsigned_app.apk, внутри можно увидеть файл classes.dex.

Теперь apk нужно «подравнять» с помощью программы zipalign. Она оптимизирует zip(apk) файл так, чтобы Android работал с ним максимально эффективно.

zipalign -v -p 4 apk/unsigned_app.apk apk/unsigned_app_aligned.apk

В каталоге apk появился новый файл unsigned_app_aligned.apk. Осталось его только подписать. Для создания ключа для подписи воспользуемся программой keytool из JDK:

keytool -genkey -v -keystore apk/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testAlias

После ввода этой команды программа попросит придумать пароль и заполнить некоторые данные:

В каталоге apk появится файл key.jks, им нужно подписать apk. Это можно сделать с помощью jarsigner из JDK, но в Android SDK есть специальная программа apksigner:

apksigner sign --ks apk/key.jks --out apk/signed_app.apk apk/unsigned_app_aligned.apk

Программа попросит ввести пароль, который был задан при создании ключа. В каталоге apk появится signed_app.apk — это подписанное приложение, готовое к запуску на Android.

Также можно проверить подпись:

apksigner verify apk/signed_app.apk

Если нет сообщения об ошибке, значит всё в порядке

При работе в Android Studio для подписи debug сборок генерируется ключ debug.keystore, который хранится по адресу %username%/.android. Для windows — C:\Users\%username%\.android

Запуск

В первую очередь нужно создать виртуальное устройство (AVD — Android Virtual Device) с помощью программы avdmanager:

avdmanager create avd -n testDevice -k "system-images;android-22;default;armeabi-v7a"

На вопрос о custom profile отвечаем no. Это команда создаёт avd с именем testDevice, его можно найти в %username%/.android/avd

Теперь нужно запустить эмулятор с созданным avd:

emulator -avd testDevice

Появится окно эмулятора. Запуск может потребовать несколько минут, терпеливо ждём.

Открываем новое окно терминала. Перемещаемся в каталог android, регистрируем путь к adb и перемещаемся в каталог project:

cd android
export PATH=$PATH:$PWD/sdk/platform-tools/
cd project

Для установки приложения на устройство или эмулятор и запуска воспользуемся программой adb (Android Debug Bridge).

Устанавливаем apk на эмулятор:

adb install -r apk/signed_app.apk

И запускаем MainActivity:

adb shell am start com.illuzor.buildtest/.MainActivity

Проверяем работу приложения:

Заключение

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

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *