[Перевод] Instant Run: как это работает?
Всем привет! Данная статья является переводом заметки Android-разработчика и автора книги «Android 4. Программирование приложений для планшетных компьютеров и смартфонов» Рето Майера. Над переводом работал Android-отдел компании Лайв Тайпинг. Оригинальная статья доступна здесь.
Большинство людей вполне довольны, когда им в руки попадает что-то простое и полезное. Но мы, программисты, не относимся к этому большинству.
Взять, к примеру, Instant Run. Это фича Android Studio, которая при помощи «магии» сокращает время, затрачиваемое на сборку и деплой инкриментальных изменений кода в процессе написания / тестирования / дебаггинга.
Я называю это магией потому, что со стороны всё выглядит именно так. После первого нажатия Run или Debug всё работает так, как того и следует ожидать. Однако каждый раз, когда в код вносятся изменения и снова нажимается кнопка Run или Debug (но в этот раз — с иконкой в виде молнии), изменения деплоятся на мой телефон настолько быстро, что я не успеваю это заметить.
Но давайте оставим магию для воскресных посиделок у телевизора.
Лично меня больше устраивает, когда магия приходит ко мне в комплекте с драконами, политическими интригами и неожиданными поворотами сюжета. Поэтому я встретился с командой инженеров, работавших над Android Studio, чтобы выяснить, как же Instant Run работает на самом деле.
Начнём с этой простой блок-схемы типичного цикла сборки приложения.
Сборка, деплой/установка, запуск приложения, запуск активити.
Цели, которые преследует Instant Run, предельно просты: убрать максимально возможное количество этих шагов и максимально ускорить те, которые останутся в результате.
На практике это означает:
· сборка и деплой только инкриментальных изменений;
· не переустанавливать приложение;
· не перезапускать приложение;
· не перезапускать активити.
Горячая, тёплая и холодная замены
Instant Run = сборка инткриментальных изменений + горячая, тёплая или холодная замена.
Горячая замена: изменения вносятся в приложение без необходимости его перезапуска и даже без необходимости перезапуска текущей активити. Может быть использовано для большинства простейших правок внутри реализации методов.
Тёплая замена: для того, чтобы правки вступили в силу, необходимо перезапустить активити. Обычно требуется при изменении ресурсов.
Холодная замена: перезапуск приложения (без необходимости переустановки). Требуется при внесении структурных изменений, таких, как наследование или изменение сигнатуры методов.
Когда вы нажимаете Run или Debug, происходит что-то наподобие этого:
Манифесты собираются в один файл, который вместе с ресурсами и .dex-файлами упаковываются в APK.
Ваши манифесты собираются и упаковываются вместе с ресурсами в APK. Точно так же ваши .java-файлы компилируются в байткод, конвертируются в .dex-файлы и отправляются в тот же APK.
В первый раз, когда вы нажимаете Run или Debug со включенным Instant Run, Gradle выполняет несколько дополнительных тасков.
Java Bytecode Instrumentation и App Server внедрены в ваш debug APK-файл.
Bytecode Instrumentation добавляется в ваши .class-файлы и новый App Server класс заинжекчен в ваше приложение.
Помимо этого, добавляется новое определение класса Application, которое внедряет кастомные загрузчики классов и запускает App Server. Для того, чтобы ваше приложение смогло использовать эти изменения, изменяется файл Android-манифеста. Если вы создавали свой собственный класс Application, Instant Run подменит его.
Теперь Instant Run запущен и отслеживает все изменения, которые вы вносите в код. Так что в следующий раз, когда вы нажмёте Run или Debug, Instant Run постарается максимально сократить процесс сборки, используя горячую, тёплую или холодную замену.
Перед тем, как применить изменения, Android Studio проверяет наличие открытого сокета в App Server, запущенном внутри приложения со включенным Instant Run. Он подтверждает, что приложение запущено, а его buildID — то, которое ожидает Android Studio.
Горячая замена
Android Studio отслеживает изменения в файлах в процессе разработки и запускает специальный Gradle-таск, чтобы сгенерировать .dex-файлы только для изменённых классов. Потом Android Studio подхватывает эти файлы и деплоит их в App Server, работающий внутри приложения.
Поскольку оригинальные версии наших классов уже существуют в запущенном приложении, Gradle при помощи Transformation API подменяет их на новые. После этого изменённые классы подгружаются App Server«ом, используя кастомные загрузчики классов.
С этого момента при каждом вызове метода внутри нашего приложения специальные инструменты, внедрённые в наши исходные классы, взаимодействуют с App Server«ом, чтобы узнать, были ли они обновлены.
Если это так, исполнение делегируется новым подменённым классам, и вместо старого метода запускается его новая версия.
Если вы установите точки отладки, то в стектрейсе увидите вызовы метода класса с именем «override».
Перенаправление методов хорошо срабатывает в случае изменения реализации самих методов, но что делать с теми вещами, которые загружаются во время запуска активити?
Тёплая замена
Тёплая замена перезапускает активити. Ресурсы загружаются до запуска активити. Поэтому любая их модификация требует перезагрузки активити для принудительной перезагрузки ресурсов.
В настоящий момент изменение любого ресурса приводит к тому, что все ресурсы перепаковываются и отправляются в ваше приложение. Но мы усиленно работаем над упаковщиком, который будет упаковывать и деплоить только новые или изменённые ресурсы.
Обратите внимание, что тёплая замена не будет работать для ресурсов, упомянутых в Android-манифесте, а также в случае изменений, внесённых в сам манифест, поскольку значения этого файла читаются в процессе инсталляции APK. Изменения, затрагивающие манифест, приведут к полному циклу сборки и установки приложения.
К сожалению, перезапуск активити не сможет волшебным образом применить структурные изменения. Добавление, удаление или изменение аннотаций, полей, сигнатур методов, изменение родительских классов или статических инициализаций требуют холодной замены.
Холодная замена
После деплоинга ваше приложение и его подпроекты разделены на несколько .dex-файлов. Классы распределены по этим файлам в зависимости от названий пакетов, в которых они находятся. При холодной замене .dex-файл, которому принадлежит изменённый класс, полностью перекомпилируется вместе с другими классами, входящими в него, и заново деплоится.
Этот подход опирается на способность Android Runtime загружать несколько .dex-файлов (фича, появившаяся в ART) и поэтому может быть использован только для устройств, работающих на Android версии 5.0 (API Level 21) и выше.
На устройствах, работающих на API Level 20 или ниже, а следовательно, использующих DALVIK, Android Studio деплоит APK целиком.
Хотя Instant Run очень умён, но обратить время вспять он не в силах. Изменения в коде, которые могли бы быть применены путём горячей замены, но которые влияют на инициализацию, запускаемую при первом старте приложения, потребуют от вас перезапуска всего приложения, чтобы вступить в силу.
Чтобы выполнить инкриментальную сборку и перезапустить приложение, нажмите Rerun (CTRL-CMD-r).
Советы и секреты
Instant Run контролируется Android Studio, поэтому запускайте/перезапускайте вашу debug-версию посредством IDE — не делайте этого напрямую с устройства, иначе весь ваш труд полетит коту под хвост.
Более детальный список советов доступен в Android-документации, но здесь приведены самые основные моменты, которые нужно всегда держать в памяти.
- Старайтесь выделить максимум ресурсов для Gradle. Если вы выделите как минимум 2 Гб памяти для Gradle Daemon JVM через jvmargs в файле gradle.properties, то будет включен dex-in-process, что значительно ускорит скорость всех сборок — как Instant Run, так и простых. Вы можете сами поэкспериментировать со значениями настроек и их эффектом на время сборки, чтобы найти оптимальный вариант.
- Возможности, которые предоставляет ART, позволяют вам выжать максимум из Instant Run, если вы присвоите minSdkVersion значение 21 или выше. Вы можете создать новый product flavor специально для дебаггинга и установить значение minSdk=21.
3.Не забывайте, что внесение изменений в манифест запустит полный цикл сборки и установки. Поэтому, если ваш процесс сборки автоматически обновляет любую часть манифеста (например, изменяет versionCode или versionName), вам нужно отключить эту опцию в debug build variants. - Instant Run работает только с основным процессом. Поэтому, если ваше приложение использует несколько процессов, во всех процессах кроме основного вместо горячей и тёплой замены будет применена холодная (либо полная пересборка, если ваш API Level меньше 21).
- Если вы работаете в Windows, Windows Defender может стать причиной медленной работы Instant Run. Вы можете избежать этого, добавив папку с вашим проектом в список исключений Windows Defender.
- На момент написания статьи Instant Run не поддерживал Jack Compiler, инструментальные тесты и деплоинг одновременно на несколько устройств.
Instant Run постоянно развивается, команда разработчиков исследует новые техники для увеличения количества кейсов, позволяющих применять горячую замену и уменьшения необходимость холодных замен или полной пересборки.