Як прискорити перший показ анімацій у Flutter-застосунку

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.

Добрий день! Мене звати Роман Капшук. Я Mobile Team Lead у компанії Work.ua. У мене за плечима вже майже 5 років нативної та кросплатформної розробки мобільних застосунків у продуктових та аутсорс-компаніях України. Понад рік тому ми випустили мобільний застосунок для шукачів роботи, написаний за допомогою Flutter, який показує стабільно високий рейтинг як у Google Play, так і в App Store.

Під час розробки мобільного застосунку однією з найважливіших проблем, з якою стикається розробник, є швидкість і плавність роботи UI. Адже від цього залежить те, наскільки приємно буде користуватися продуктом пересічному користувачеві та скільки нервових клітин буде знищено у перфекціоніста.

Завдання ускладнюється ще й тим, що розробник не може прогнозувати, на якому пристрої працюватиме застосунок: це може бути iPhone 12 Pro, Google Pixel перших поколінь або найдешевший «китаєць». А все-таки дуже важливою є саме перша сесія користувача, бо перше враження можна справити лише один раз. Від неї подекуди залежить те, як саме ваш застосунок запам’ятається юзеру, чи буде він очікувати в наступних сесіях плавні анімації, швидкий відгук інтерфейсу або ж щоразу буде переглядати слайдшоу.

Забігаючи наперед скажу, що проблем із продуктивністю у застосунків, написаних на Flutter, не так вже й багато, а деякі з них можна побороти доволі простими способами.

Проблема

У цій статті розглянемо проблему та шляхи розв’язання першого «гальмонутого» показу анімацій в застосунках, написаних за допомогою Flutter. Якщо ви відкрили цю статтю, то вас, імовірно, цікавить випадок, приклад якого ви можете побачити на анімації нижче:

Насамперед треба зрозуміти причину цього явища. А трапляється це через те, що шейдери, які потрібні для зображення анімації, ще не були скомпільовані графічним рушієм Flutter. Так, звичайно, не всі анімації будуть мати вигляд кіберпанку на релізі, деякі стандартні анімації вже скомпільовані та упаковані. Проте в більшості випадків все ж необхідно зробити це вручну на етапі, наприклад, підготовки збірки для тестування або ще краще за допомогою інтеграційних тестів.

У принципі можна не сильно і паритися через це. Тільки-но юзер хоча б раз дійде до анімації, що буксує, Skia скомпілює потрібні шейдери, й далі все буде як по маслу. Але, повторюючи вступне слово, маю сказати, що саме перший досвід користування продуктом закарбує в пам’яті ваших користувачів або (ще гірше) замовників загальне враження від застосунку. Тому таких просідань, хоч і короткочасних, все ж краще уникати.

Розв’язання

Офіційна документація пропонує нам вихід у вигляді прогріву або ж прекомпіляції шейдерів. Для цього достатньо одного девайса з можливістю під’єднати до вашого IDE в режимі Profile.

Стосовно платформозалежності є деякі нюанси. Якщо у вашому розпорядженні є лише Android або iPhone, то зібрані на них шейдери покращать ситуацію і на іншій платформі. Тобто зібрані на Samsung S20 або Xiaomi A2 шейдери будуть також працювати на IPhone 6 і навпаки. Але, як показують досліди комьюніті в інтернетах і власний досвід, все ж краще збирати та застосовувати шейдери в залежності від платформи. Для модельного ряду Android майже все одно, на якому девайсі їх збирати, а от щодо iPhone все не так гладко.

Apple поступово переходить з OpenGL на свій власний фреймворк Metal. Як вони самі кажуть, це нові, могутні API з низькими надлишковими витратами продуктивності та високою оптимізацією під платформу.

Що це дало нам як розробникам на флатері? Реалізація механізму кешування і компіляції поки що лише частково (читайте майже ніяк) підтримує Metal API, тож прогрів шейдерів може бути надлишковим кроком. Проте в репозиторії флатера на момент написання статті у Dev каналі вже було змережене розв’язання цієї проблеми (і, судячи з коментарів, воно допомагає). Стежити за прогресом можна, наприклад, тут.

Ручний варіант

Відразу розповім про один нюанс. Якщо ваш застосунок залежний від бібліотек, які ще не перейшли на null safety, то, імовірніше, ви запускаєте збірку з командою flutter drive —profile —cache-sksl —write-sksl-on-exit shadersFileName —driver=test_driver/integration_test.dart —target=pathToTests/testFileName.

Не забудьте, будь ласка, про цю команду і при виконанні збірки для прогріву.

Найпростіший варіант прогріву (так кажуть в офіційній документації) — зробити його вручну:

  1. Під’єднуємо девайс до студії (бажано перед цим видалити застосунок).
  2. Запускаємо через термінал з командою flutter run —profile —cache-sksl, після чого отримуємо файл з назвою на кшталт flutter_01.sksl.json.
  3. «Ходимо» по застосунку, «збираючи шейдери».
  4. Після того як «зібрали», в терміналі з активною flutter run командою натискаємо на клавішу M.
  5. Після чого можемо зробити збірку, використовуючи одну з 2-х команд для Android (для APK або appbundle) і для iOS відповідно:

flutter build apk —bundle-sksl-path flutter_01.sksl.json

flutter build appbundle —bundle-sksl-path flutter_01.sksl.json

flutter build ios —bundle-sksl-path flutter_01.sksl.json

Докладніше можна прочитати тут.

Плюси:

  • не потребує складних налаштувань;
  • знання автотестів не потрібні.

Мінуси:

  • ручна робота;
  • довго;
  • нудно;
  • великий шанс пропустити якусь анімацію. Якщо не пропустили її в цьому релізі, обов’язково пропустите в наступному.

Також вкажу на певний мінус прекомпіляції шейдерів. Річ у тім, що прекомпіляція відбувається на етапі «першого запуску» завдяки інструкціям, які ми саме і збираємо. Тобто якщо у вас відверто багато шейдерів, які потрібно компілювати, перший запуск може бути доволі довгим (подекуди до 30 секунд).

Також потрібно розуміти, що саме являє собою «перший запуск». «Перший запуск» для Android — це запуск застосунку після установки або перезавантаження. Для iPhone це відбувається кожного холодного старту застосунку — запуск щоразу після «чистого запуску». Тобто коли ми викидаємо застосунок з трею і запускаємо заново.

Також варто зауважити, що ви можете використовувати один і той же згенерований сет шейдерів декілька разів. Звичайно, якщо:

  1. Анімації не змінювалися.
  2. Не було нових анімацій.
  3. Ви не оновлювали Flutter.

Добрим тоном все ж таки буде перед кожним релізом збирати новий сет і вже свіжачком іти в реліз.

Автоматизація

Ну що ж, з тим, як збирати шейдери вручну і як їх використовувати, ми розібрались. Але, що робити, якщо немає жодного бажання витрачати стільки часу перед кожним релізом? Вихід є — автотести. Якщо у вас у команді ще немає людини, яка б їх писала, саме час таку людину знайти. Від цього ви отримаєте потрійну користь: менше навантаження на QA, краща якість застосунку, результатом виконання автотестів будуть ті ж самі файли інструкції для прекомпіляції шейдерів.

Якщо вже зовсім коротко:

  1. Пишемо будь-які автотести, щоб програвались анімації, які нас цікавлять.
  2. Запускаємо їх на реальному девайсі з командою flutter drive —profile \ —cache-sksl —write-sksl-on-exit shadersFileName \ —driver=test_driver/integration_test.dart \ —target=pathToTests/testFileName

Команда використовується саме для нового пакета integration_test. Тому вона трішки відрізняється від представленої в документації.

Бонус: збір шейдерів із декількох сесій прогріву

Та все ж є один невеликий нюанс. Збір шейдерів за допомогою автотестів наразі може бути виконаний лише з одного тестового файлу. Докладніше про це написано в issue на флатеровському гітхабі. Запит на цю фічу висить з початку 2021 року і на момент написання статті ще не закритий.

Тому я користуюсь обхідним шляхом з моєї ж відповіді в тому самому issue. По суті, нічого складного — просто копіюємо 2 файли в корінь вашого проєкту і запускаємо. Принцип роботи скриптів обмежується почерговим запуском всіх ваших тестів у директорії integration_test, створенням файлу з відповідним набором шейдерів і результуючого файлу, в якому міститься сума всіх пар ключ-значення з раніше згенерованих файлів.

Висновок

Прогрів шейдерів буде для вас корисним, якщо ваші клієнти страждають від анімації, яка лагає при першому запуску застосунку. Прогрівати їх можна вручну, під час процесу QA або цілеспрямовано. Шейдери можна почати збирати автоматизовано за допомогою інтеграційних тестів. Якщо вони вже є — добре, вам залишиться трішки проапгрейдити команди запуску на вашому CI (чи де ви їх запускаєте), і все.

В іншому разі вашій команді потрібно буде змінити процеси розробки, додати до них автотести, в ідеальному випадку налаштувати CI для автоматизації збірки шейдерів і надсилання вже «упакованих» білдів.

Якщо вам цікава ця тема і вам потрібно більше інформації, як проводити автоматизацію процесів та тестів, залишайте коментарі. Можливо, наступна тема статті буде саме про автоматизоване тестування.

Список літератури

👍НравитсяПонравилось13
В избранноеВ избранном8
LinkedIn
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

а хіба під андроїд теж треба прогрівати шейдери? Проблема ж була лише в айос

Проблема є як в iOS так і в Android. Skia API працює приблизно однаково для обох платформ. Просто для iOS з переходом на рушій Metal з’явилась ще одна проблема пов’язана з тим, що у Skia не було реалізації API прекомпіляції шейдерів саме для Metal. Тому крім проблеми з першим запуском з’явилась проблема, що для iOS девайсів на Metal була відсутня можливість збирати їх навіть вручну.

Детальніше можна почитати в цих лінках:
github.com/...​70#issuecomment-803165038
github.com/...​tter/flutter/issues/79298
www.reddit.com/...​nt_ios_first_launch_jank

дякую за посилання. я, мабуть, мало анімацій юзаю кастомних, тому на андроїді не бачив перших лагів ніколи...на айосі дійсно інколи підглючувало.

Очень интересно, но лично мне не все понятно(опыта мало). Приложение Work.ua, работает действительно очень круто! Роман, спасибо за бесценные знания.

все що написано на флатер працює круто!

Подписаться на комментарии