Уменьшение размера React Native-приложения на 60% за несколько простых шагов

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.
Я тружусь в компании Mutual. Она работает в Бразилии, в сфере равноправного кредитования. Мы помогаем заёмщикам и заимодавцам наладить связь друг с другом. Первые ищут хорошие ставки, а вторые — доходы, превышающие то, что может предложить им рынок. Наш продукт применяется широким кругом пользователей, мы работаем в большой стране. В результате наши приложения для iOS и Android, основанные на React Native, загружают на очень разные устройства.



Надо отметить, что основная масса наших пользователей устанавливает приложения на бюджетные устройства. Мы можем делать такие выводы, пользуясь данными библиотеки Facebook device-year-class. Эта библиотека, получив сведения о модели устройства, сообщает о том, в каком году это устройство считалось бы высококлассным флагманским телефоном. Например, самым популярным телефоном среди наших пользователей является Samsung Galaxy A10. Этот телефон, хотя он и выпущен в 2019 году, мог бы считаться флагманом лишь в 2013. Анализируя данные об устройствах пользователей, мы можем говорить о том, что 85% этих устройств можно было бы признать устройствами высокого класса лишь в 2015 году или раньше. Из-за этого мы предъявляем особые требования к оптимизации нашего мобильного приложения. Цель оптимизации заключается в том, чтобы даже пользователи со слабыми устройствами могли бы с удобством пользоваться нашим приложением.


Процент устройств, которые могли бы быть признаны флагманскими в определённом году

В этой связи мы обратили пристальное внимание на размеры приложения. В случае с его Android-версией это было 26.8 Мб. Хотя это — не такой уж и большой размер, это, определённо, больше медианного значения размера приложений наших пользователей. Этот показатель, по сведениям Google Play Console, составляет 16.3 Мб. Размер приложения может быть решающим фактором для пользователей, имеющих ограниченные тарифные планы, или для тех из них, на устройствах которых мало памяти, что заставляет пользователей тщательно выбирать приложения, которые будут у них установлены. В результате некоторые приложения приходится удалять. Это особенно важно в случае с приложением Mutual, так как заёмщики платят ежемесячные взносы через это приложение. Когда заёмщик деинсталлирует наше приложение, шансы того, что он вовремя сделает платёж, очень сильно падают. А это напрямую влияет на заработки инвесторов, пользующихся нашей платформой.


Размер приложения Mutual гораздо больше, чем медианный размер приложений наших пользователей

Размер приложения влияет не только на уровень деинсталляций. Размер влияет ещё и на коэффициент конверсии установок приложения. Вот хорошая статья об этом, написанная командой Google Play. В этой статье речь идёт о важности размеров приложения. В частности, там говорится о том, что каждые дополнительные 6 Мб размера APK-файла уменьшают коэффициент конверсии установок на 1%.

Там идёт речь и о том, что в развивающихся странах, где нормой являются бюджетные устройства, этот эффект проявляется даже сильнее. А именно, уменьшение размера APK-файла на 10 Мб на развивающихся рынках соответствует увеличению коэффициента конверсии установок примерно на 2.5%.


Увеличение коэффициента конверсии установок на каждые 10 Мб уменьшения размера APK-файла в разных странах (по внутренним данным Google)

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

Android App Bundle


Читая рекомендации, мы узнали о том, что самый простой способ уменьшения размера приложения заключается в использовании нового метода распространения приложений, который называется Android App Bundle (AAB). До этого момента мы распространяли приложение, собирая старый добрый файл Android Package (APK), который может быть запущен на большинстве Android-устройств, и загружая его в Google Play Console. А вот AAB-бандл содержит только скомпилированный код и ресурсы. В результате, при его загрузке, за генерирование оптимизированных APK-файлов для различных типов устройств, с учётом их спецификаций и архитектуры CPU, отвечает Google.

Получается, что внеся простое изменение в процесс сборки проекта, мы можем получить серьёзное уменьшение размера приложения, не прилагая к этому больше никаких усилий? Это слишком хорошо для правды!

После того, как мы почитали документацию, мы всего лишь поменяли сборочный скрипт React Native Gradle так, чтобы он, вместо текущего assembleRelease, запускал бы bundleRelease. Вот и всё, что нам понадобилось для создания AAB-файла. После ещё некоторых модификаций, внесённых в действие supply конфигурации Fastlane, касающихся автоматической выгрузки материалов прямо в Play Store, мы перешли на AAB, и новая версия нашего приложения появилась в Google Play Console.

Одно только это изменение привело к уменьшению размеров APK-файлов, передаваемых на устройства пользователей. Уменьшение составило от 9.1 до 12.4 Мб. Как оказалось, использование Android App Bundle — это действенная методика, которая, и правда, позволяет уменьшить размер приложения.


Старый APK-файл и новый AAB-бандл, использование которого приводит к тому, что размер приложения на разных устройствах может составлять от 14.4 до 17.7 Мб

Правда, тут стоит проявлять осторожность. Если вы используете React Native с Hermes, то вам может понадобиться обновить вашу зависимость soloader (подробности смотрите тут). Иначе есть риск отдать пользователем приложение, в котором будет содержаться критическая ошибка. Нам повезло, мы смогли выявить эту проблему в ходе тестирования альфа-релиза проекта. Но ошибка легко могла бы проскочить в продакшн, так как она не проявляется при локальном тестировании или при сборке APK-файла.

Оптимизация ресурсов приложения с использованием Android Size Analyzer


Следующим предложением по оптимизации приложений, которое мы нашли в документации, стало применение Android Size Analyzer. Это — инструмент командной строки, который анализирует Android-приложения и ищет возможности уменьшения их размеров. Мы запустили этот инструмент, воспользовавшись командой следующего вида:

size-analyzer check-bundle [BUNDLE].aab

В результате мы получили список больших ресурсов приложения и изображений, которые мы можем оптимизировать. Нам, кроме того, порекомендовали настроить ProGuard.


Отчёт size-analyzer

▍ProGuard


ProGuard — это инструмент для сжатия, обфускации и оптимизации Java-байткода. Мы пока не исследовали возможность применения этого средства, так как узнали о том, что оно может быть несовместимо с некоторыми Android-библиотеками. Так как мы стремились к тому, чтобы как можно быстрее уменьшить размер нашего приложения, и к тому, чтобы сделать это как можно проще, мы решили оставить этот метод оптимизации на будущее.

▍Большие ресурсы приложения


Запустив size-analyzer снова, с ключом -d, мы получили список ресурсов приложения, отсортированный по их размеру. Так как этому инструменту ничего не известно о том, как именно пользователи работают с приложением, он дал нам возможность самостоятельно принимать решения о том, какие ресурсы стоит удалить, а какие стоит загружать в приложение динамически.


Список больших ресурсов приложения, упорядоченный по их размеру

Первым и самым большим ресурсом в этом списке был JavaScript-бандл React Native. Сейчас мы не можем разделить этот бандл и загрузить его динамически. Но позже мы об этом подумаем. Далее в этом списке находятся большие файлы шрифтов (TTF) и изображений (JPG и PNG).

▍Ненужные изображения


Наше внимание сразу же привлекли огромные JPG-изображения, применяемые в Storybook. Мы пользуемся этой системой для разработки и тестирования компонентов. Это — 2 Мб мусора, который попадал в продакшн-версию проекта. Позорная ошибка! Когда происходит нечто подобное, мы чувствуем себя так, будто совершили серьёзную глупость. Но в сложном мире разработки ПО все совершают ошибки. Я верю в то, что если рассказывать о своих ошибках во всеуслышание, это поможет другим разработчикам учиться на этих ошибках. Есть вероятность того, что вы совершаете те же ошибки в том случае, если не анализируете структуру ресурсов приложения, размер которого постепенно увеличивается.

▍Шрифты


После того, как мы по-быстрому избавились от ненужных изображений, мы приступили к анализу других элементов списка ресурсов. Ясно было то, что в нём присутствовало очень много встроенных шрифтов. После того, как мы поговорили об этом с нашими дизайнерами, они сказали нам, что многие старые компоненты не отличаются строгим следованием типографским руководствам. Поэтому мы выяснили то, какие компоненты можно удалить, а в каких можно воспользоваться подходящими обновлёнными шрифтами. Благодаря этому мы смогли уменьшить количество используемых шрифтов с шести до четырёх.

Ещё одна вещь, на которую мы обратили внимание, заключалась в огромном размере самих файлов шрифтов. Размер каждого из них составлял примерно 670 Кб. Это означало, что четыре шрифта занимают в несжатом бандле умопомрачительные 2.7 Мб. Но, к нашему счастью, существует инструмент, называемый FontForge, который позволяет глубоко анализировать и модифицировать файлы шрифтов. Воспользовавшись этим инструментом, мы смогли узнать о том, что основной причиной большого размера файлов шрифтов являются символы расширенной кириллицы и ненужные глифы. Мы могли спокойно всё это удалить, так как текстовая часть нашего приложения полностью написана на португальском языке. Благодаря этому изменению мы смогли уменьшить размеры файлов шрифтов с 670 Кб до 70 Кб — на 90%.


Примеры некоторых глифов, включённых в наши шрифты

Благодаря тому, что мы избавились от ненужных шрифтов и оптимизировали оставшиеся, мы смогли снизить размер приложения на 3.8 Мб. Это привело к приятному уменьшению общего размера APK-файла на 2 Мб.


Список шрифтов и их размеры до оптимизации


Список шрифтов и их размеры после оптимизации

▍Оптимизация изображений


Проанализировав изображения, которые ещё оставались в приложении, мы обратили внимание на то, что некоторые из них довольно-таки велики. Мы обработали несколько таких изображений средством для оптимизации графики TinyPNG и увидели значительное уменьшение размера этих изображений. После этого мы решили оптимизировать все JPG- и PNG-изображения, используемые в приложении. Всего их было 41.


Изображение до и после оптимизации

Это дало нам возможность сократить размеры изображений с 2.5 Мб до 756 Кб, то есть, на 70%. Правда, из-за того, что изображения раньше оптимизированы не были, они, в процессе создания итогового APK-файла, сжимались. Оказалось, что наша оптимизация привела к уменьшению размера приложения, загружаемого пользователем, лишь на 500 Кб.

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

Оптимизация JavaScript-бандла React Native


После того, как мы разобрались с ресурсами приложения, специфичными для платформы Android, пришло время проанализировать JavaScript-бандл. Оптимизацию бандла можно счесть хорошей идеей по трём причинам. Во-первых, это уменьшает размер готового APK-файла. Во-вторых — это приводит к более быстрому запуску приложения, так как виртуальной машине JS приходится обрабатывать меньший объём кода. И наконец, что самое важное, это ускоряет OTA-обновления, которые мы делаем несколько раз в неделю, пользуясь CodePush.

▍Анализатор бандла и оптимизация кода


Для того чтобы принять решение о том, как уменьшить размер бандла, сначала, надо было разобраться с тем, что в бандле занимает больше всего места. Для этого мы воспользовались отличным опенсорсным инструментом react-native-bundle-visualizer. Проанализировав с его помощью проект, мы получили визуализацию, в которой присутствовала каждая папка и каждая зависимость приложения с указанием размеров соответствующих сущностей.


Снепшот библиотек и папок кодовой базы фронтенда приложения Mutual с указанием размеров

Мы выяснили, что общий размер бандла составляет 5.49 Мб. 57.8% этого объёма представляют зависимости из папки node_modules, 27.5% — код приложения. То, что осталось, используемому нами инструменту идентифицировать не удалось. В ходе процесса сборки бандла неиспользуемый код уже удалялся, поэтому то, что мы видели, представляло собой код, который реально используется приложением. Но, даже учитывая это, в бандле всегда можно найти что-то такое, что можно улучшить.

Самая большая из зависимостей проекта — math.js. Эта библиотека, как можно судить по её названию, реализует множество математических операций. У нас нет особой необходимости в этой библиотеке из-за того, что мы выполняем все важные вычисления на сервере, после чего просто отправляем результаты приложению. Когда мы присмотрелись к коду приложения, оказалось, что библиотека используется лишь для выполнения некоторых простых операций. Она, вероятнее всего, использовалась разработчиком, который работал и над серверным кодом, лишь в силу привычки. Мы быстро извлекли из библиотеки соответствующие методы и встроили их в нашу кодовую базу, полностью избавившись от этой зависимости. Это позволило уменьшить размер бандла до 4.64 Мб. Отказ лишь от одной библиотеки позволил уменьшить размер бандла на 15.5%!

Как уже было сказано, мы, для разработки и тестирования компонентов, используем Storybook. Правда, соответствующие возможности должны быть доступны лишь в локальном и промежуточном окружениях. Конечным пользователям это не нужно. Благодаря этому мы воспользовались переменной ENVIRONMENT для того чтобы управлять включением соответствующей части приложения. Хотя это и подходит для ограничения доступа, бандлер не может знать о том, какое значение записано в эту переменную. Из-за этого ограничения весь код Storybook попадал в продакшн-бандл.

Для того чтобы исправить проблему, мы изолировали импорт этого раздела в пределах одного файла. Затем мы создали две версии этого файла: одну, которая включает Storybook, и другую, предназначенную для продакшна, содержащую лишь макет компонента. Для переключения между этими файлами при подготовке продакшн-версии бандла, мы написали скрипт, который выполняется перед сборкой проекта и меняет один файл на другой. Благодаря этому методу мы смогли полностью убрать код Storybook из продакшн-версии приложения, убрав и зависимость из node_modules, и обычный код, касающийся конфигурирования «историй» Storybook.


Обновлённая папка Storybook с двумя версиями файла index

Эти два изменения позволили сократить размер бандла с 5.49 Мб до 4.2 Мб. А это означает, что, кроме прочего, приложение будет быстрее загружаться и быстрее обновляться.


Размер итогового бандла составил 4.2 Мб

После всех этих улучшений мы снова загрузили приложение в Play Store. Теперь нам сообщалось о том, что размер готового APK-файла будет находиться в диапазоне от 10.5 до 13.7 Мб. Это, учитывая то, что изначально приложение имело размер в 26.8 Мб, означает просто невероятное сокращение размера проекта примерно на 60%! А значит, мы, как сказано в статье команды Google Play, вполне можем рассчитывать на то, что коэффициент конверсии установок увеличится на 3.75%.


Сравнение исходной APK-версии приложения с итоговой AAB-версией, в которой сделаны все вышеописанные улучшения

Итоги


Мы, как программисты, ориентированные на нужды бизнеса, знаем о том, что иногда компании, ради более быстрого развития проекта, вполне можно пойти на увеличение его технического долга. Это особенно актуально для молодых стартапов вроде Mutual, которые пытаются найти своё место на рынке. Но если не наблюдать за этим долгом, можно совершить досадные ошибки, вроде отправки в продакшн 2 Мб тестировочных изображений и неоправданного использования огромной библиотеки. Нельзя позволять техническому долгу выходить из-под контроля и доставлять неприятности.

Кроме того, часто бывает так, что разработчики просто упускают доступные им возможности по оптимизации проектов. Поэтому рекомендуется иногда критически анализировать свои проекты. Просто для того, чтобы проверить, не упущена ли какая-то возможность быстрой оптимизации размера, скорости, или какого-то другого аспекта приложения. У нас ушло всего два дня на то, чтобы проанализировать приложение, спланировать работу и внести в проект улучшения, которые позволили сократить размер приложения на 60%. Сложно придумать что-то другое, способное дать такие же результаты за столь короткое время.

А как вы оптимизируете свои React Native-проекты?

Источник: https://habr.com/ru/company/ruvds/blog/502422/


Интересные статьи

Интересные статьи

Доброго времени суток, друзья! В данном туториале я покажу вам, как создать фуллстек-тудушку. Наше приложение будет иметь стандартный функционал: добавление новой задачи в ...
Первая часть тут Продолжаем разработку нашего интернет магазина. В этой части будет: нормальная загрузка картинок по статическим адресам генерация хлебных крошек на клиенте страница това...
Сегодня я поделюсь с вами пошаговым руководством как написать свой babel плагин. Вы сможете использовать эти знания для автоматизации правок, рефакторинга или кодогенерации. Читать да...
Если Вы используете в своих проектах инфоблоки 2.0 и таблицы InnoDB, то есть шанс в один прекрасный момент столкнуться с ошибкой MySQL «SQL Error (1118): Row size too large. The maximum row si...
Реализация ORM в ядре D7 — очередная интересная, перспективная, но как обычно плохо документированная разработка от 1с-Битрикс :) Призвана она абстрагировать разработчика от механики работы с табл...