В предыдущей статье, мы разобрали как можно проверять регрессию размера андроид приложения на CI в development ветке. И узнали какие могут быть минусы этого подхода.
Давайте теперь узнаем как можно эти недостатки исправить. Будем обнаруживать регрессию размера приложения в pull request, а не в development ветке. Так как предотвратить проблемы, лучше чем решать её.
Обнаружение регрессии в pull request (вариант 1)
Так как development ветка выступает единственным источником правды(размер приложения), будем сравнивать apk, собранный в pull request и apk, собранный в development ветке.
Со стороны development ветки, нам придется собирать apk и собирать метрики на каждый commit в development, чтоб в любой момент в pull request получить информацию из development ветки и использовать для сравнения.
Development flow
1. Собираем App Bundle вместо универсального apk. Если до этого мы собирали универсальный(legacy) apk через gradle команду ./gradlew assembleRelease, будем собирать app bundle:
./gradlew bundleRelease
Про все возможности App Bundle можно посмотреть в официальной документации гугла. Вкратце, app bundle позволяет собирать apk в зависимости от конфигураций(архитектура процессора, языка, размера экрана) конкретного устройства, что позволяет значительно уменьшить размер полученного apk. То есть, если вы устанавливаете приложение на смартфон, то вряд ли вам нужны ресурсы предназначенные для планшета. Обычно сборка таких apk происходит внутри google play, вам нужно просто загрузить ваш app bundle, и google play сам собирает apk, в зависимости от запрашиваемого устройства. И так как нам надо максимально приближенную к реальности разницу в размере доставляемого apk, нам надо реализовать тот же функционал, который google play делает за нас. И в этом нам поможет bundletool от гугла.
2. Собираем apk с помощью bundletool:
java -jar bundletool.jar build-apks
--bundle=bundle-release.aab
--output=release.apks
--device-spec=device-spec.json
--mode=system
Скачиваем бинарник bundletool, и вызываем команду build-apks
, и задать параметры, пройдемся по ним:
--bundle
- ваш собранный app bundle--output
- название архива, куда будет собираться наши apks(сет apk)--device-spec
- конфигурация девайса, для которого вы собираетесь собрать apk. Должен быть в формате json.
Пример device-spec.json
{
"supportedAbis": ["armeabi-v7a"],
"supportedLocales": ["en"],
"screenDensity": 640,
"sdkVersion": 27
}
--mode
- тип apk на выходе (default, system, universal, instant). Instant - instant apk(не требует установки), universal - тот самый универсальный apk(содержит ресурсы для все девайсов), default - сет master.apk + конфигурационные apk(механизмsplit-APK
, позволяющий собирать apks, в зависимости от конфига девайса), system - для системного образа(тот же default, только замержанный в один apk, за исключением dynamic feature модулей). В нашего кейса, подходят system и default.
3. Собираем метрики(размер полученного apk, текущий commit hash, дата и тд.) и загружаем в наше хранилище(может быть любое, мы используем все тот же gcloud)
gsutil cp "$build_size_info.json" "gs://custom_folder/build_size_info.json"
gsutil cp "$system.apk" "gs://custom_folder/system.apk"
Наш development flow на каждый commit будет выглядеть так:
Pull request flow
Теперь, когда у нас всегда есть данные в development, мы можем в любой момент обратиться к development данным и получить нужный нам apk для сравнения.
На каждый commit в пулл реквесте мы будем:
1. Собираем app bundle ./gradlew bundleRelease
2. Собираем конкретный apk, с таким же device-spec.json
, как и в development
3. Загрузка development apk. Здесь нужно понимать что нам нужен apk, из последнего актуального commit в development c которым была синхронизация. То есть если вы не синхронизировались с development(merge/rebase), то вам нужен commit от которого была создана ваша ветка, а если вы синхронизировались, то commit последнего merge/rebase с development. Чтобы получить такой commit, вызываем простую git команду:
git merge-base "development" "feature/task1"
В зависимости от этого commit, мы будем загружать нужный на apk, идем в хранилище(gcloud) и просим apk, который был собран по этому commitу.
4. Сравниваем текущий PR(pull request) apk и development apk. Используя тот же diffuse util и загружаем в свое хранилище:
java -jar diffuse-binary.jar diff $pr-system.apk $dev-system.apk > $report.txt
gsutil cp "$report.txt" "gs://custom_folder/report.txt"
5. Результат сравнения наших apk, мы можем запостить как комментарий со ссылкой на report.txt
, чтобы понять что послужило регрессией размера.
6. В зависимости от результата сравнения, можно заблокировать PR, чтобы запретить merge в development. Как именно это сделать, зависит от вас. Мы сначала думали попробовать намеренно выбрасывать ошибку при обнаружении регрессии. Но после решили использовать Github API(так как репо в github).
То есть при обнаружении регрессии - делаем request review changes, который будет отображаться в PR checks, и пока мы не аппурвнем или отклоним, PR нельзя будет замёржить. Если регрессия была исправлена следующим коммитом, то можем автоматически отклонить наш request review changes. А если все таки нельзя сделать никаких оптимизаций размера и придется мёржить с этими изменениями, то можно вручную отклонить, введя причину отклонения.
Опять же, реализация блокировки/разблокировки PR зависит от вас.
Наш pull request workflow будет выглядеть так:
7. Оптимизация - делать все эти шаги на каждый commit в PR очень затратно, так как нам не нужно каждый раз собирать сравнивать размер, если была изменена одна строчка кода. Поэтому перед тем как начать сравнение, нам надо добавить проверку измененных файлов в git. И если в этом списке файлов будут файлы, которые могут значительно повлиять на размер приложения, то только тогда делать сравнение. Мы написали простой shell script, который проверяет измененные файлы через pattern matching.
8. И последнее, если нам не нужно сравнивать размер, то надо проверить компиляцию release кода, так как зачастую бывает, что кто-то мёржит код, который может сломать development release сборку, а нам критично чтоб development сборки не ломались, так как они хранят актуальные данные о размере.
./gradlew app:compileReleaseJavaWithJavac
Итог
В результате, мы имеем:
Автоматизированное обнаружение регрессии размера приложения на уровне pull request
Полная видимость изменения размера приложения, так как мы собираем apk на каждый commit в development. Можно построить красивый график изменения размера.
Недостатки
Собирать apk на каждый commit в development - хорошо, с точки зрения видимости, но плохо с точки зрения оптимальности. Это может быть очень дорого, в зависимости от количества коммитов происходящих в development.
Вытекает из первого, могут быть apk в development , которые не понадобились для сравнения. То есть, если в начале, в шаге Проверка git измененные файлы нам не нужно проверять размер в данном PR, то и не нужно загружать development apk.
Эти недостатки будем решать в следующей статье.