Сучасна диджитал-освіта для дітей — безоплатне заняття в GoITeens ×
Mazda CX 30
×

Мы уменьшили размер приложения в пять раз: с 42 до 8 МБ. Зачем и как мы создали Lite-версию

Усі статті, обговорення, новини про Mobile — в одному місці. Підписуйтеся на телеграм-канал!

Мы, Сутковенко Максим и Ганин Владислав, Android-инженеры из Rozetka и Parimatch Tech. В одной из предыдущих компаний нам посчастливилось поработать над созданием Lite-версии приложения вызова такси, адаптированной под бразильский рынок. И сегодня мы хотели бы рассказать о том, с какими вызовами столкнулась наша команда, как они были преодолены и что в итоге из этого вышло.

Также приведенные ниже рекомендации применимы не только при разработке Lite, но и обычной версии продукта.

Бизнес-ценность

С каждым годом все большее количество пользователей приходит в мир мобильных приложений именно из развивающихся стран, к которым, кроме Бразилии, также относится Индия, Индонезия, Мексика и другие. Аналитическая платформа App Annie ожидает продолжения роста мирового рынка приложений в основном за счет адаптации существующих решений в развивающихся рынках. Уже сейчас четыре из пяти стран, лидирующих по количеству скачиваний приложений, относятся к растущим рынкам.

Анализируя ситуацию далее, можно прийти к заключению, что тенденция роста будет только продолжаться. Так, например, загрузка приложений в Индии выросла на 215% за последние годы, тогда как в США упала на 5%.

Скачивания приложений по странам. Источник

К тому же, пользователи из этих стран устанавливают большое количество приложений и проводят больше времени в них, чем пользователи в развитых рынках. Это может значить то, что в этих странах люди больше ориентированы на мобильные устройства, опять же, сравнивая с уже зрелыми рынками.

Количество часов, проведенных в день за использованием приложений в мире. Источник

Но читая все это, может появиться вполне справедливый вопрос «Приносят ли действительно все эти пользователи доход, учитывая специфику их рынка?».

Правильный ответ — «да». Из 30 стран, занимающих первое место по объему доходов для разработчиков Play Market в регионе EMEA (Европа, Ближний Восток, Африка), 6 являются экономически развивающимися. Хотя это может показаться не слишком впечатляющим, важно то, что развивающиеся рынки обеспечивают рост доходов более чем на 34% в год.

Поэтому если вы хотите увеличить доход своего приложения за счет экспансии на другие рынки, маркетинг вашего приложения в развивающихся странах может стать ключом к успеху. А если учесть крайне низкий CPM (Cost Per Mille) в этих странах, причин не воспользоваться такой возможностью почти не становится. Но так ли это?

CPM по странам. Источник

Почему нельзя так просто взять и..

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

Во-первых, актуальность смартфонов. Недорогие устройства довольно распространены в развивающихся странах, большинство пользователей владеет девайсами, которые были популярны в 2015 году или раньше. Из-за малого объема памяти владельцам приходиться выбирать, какое приложение удалить, а какое оставить. И логика тут не бинарная: чем меньше приложение будет занимать памяти, тем меньше вероятность оказаться в списке «обреченных». Так, если для развитых стран планка может стоять в районе 100MB, для развивающихся она куда ниже. Несмотря на это, приятной новостью может оказаться SD-карта, что достаточно популярна на бюджетных смартфонах. Она сможет забрать часть веса, состоящего, например, из кэша.

Во-вторых, интернет в этих странах зачастую дорогой, нестабильный и медленный. Так, большинство соединений в этих регионах до сих пор работает на 2G сети. Это вынудило нас, как разработчиков, относится гораздо щадяще к общению с сервером за счет многих оптимизаций, о которых будет сказано ниже.

Именно эти проблемы решила легковесная версия нашего приложения, спроектированная для поддержки более старых Android-девайсов (подавляющее большинство пользователей на развивающихся рынках используют устройства именно с такой ОС на борту) и в условиях низкого качества интернет соединения.

Наши цели

  • Уменьшить размер приложения до 10 мегабайт.
  • Спроектировать клиент-серверную архитектуру, которая минимизирует расход трафика и будет быстро работать в условиях 2G-сети.
  • Сделать приложения, которое будет хорошо работать на девайсах выпуска 2015 года или более ранних моделей.

Простота — залог производительности

Мы не хотели, чтобы пользователи чувствовали, как будто они используют неполноценный продукт. Поэтому для нас было очень важно не только увеличить производительность, но и сохранить высокий уровень пользовательского опыта главного приложения. Наш первый шаг состоял в том, чтобы выяснить наиболее и наименее используемый функционал на слабых девайсах. Следующий ход — согласование с заказчиком того, что стоит убрать, сохранить или изменить в приложении. И последние действие — реализация идей.

Водитель

Главное приложение объединяло в себе как пассажирские, так и водительские функции. Изначально мы хотели его разделить на две отдельные Lite-версии, но клиент настоял на одном приложении. Тогда мы решили воспользоваться возможностями Play Feature Delivery, позволяющими пользователю докачать отдельные функции приложения по необходимости. Таким образом, реализовав водительскую часть, мы пришли к консенсусу относительно размера и требований к функционалу приложения.

Корпоративный профиль

Корпоративный профиль оказался непопулярен среди целевой аудитории Lite-версии.

Мы не только реализовали его через Play Feature Delivery, но и убрали с главного экрана, заменив на групповые поездки, тем самым увеличив их конверсию на 32% в данном сегменте пользователей. Мне кажется, это отличный пример того, что при разработке Lite-версии стоит думать не только об инженерных вызовах, но и о продуктовых особенностях.

Карта

Карта достаточно требовательная как к оперативной памяти девайса, так и к сети. Мы выдвинули гипотезу о том, что большинство пользователей вводят конкретный адрес, не используя карту. Но взглянув на данные, мы поняли, что наше предположение неверное: значительное количество пользователей используют маркер для выбора пункта назначения. Раз мы не смогли убрать карту, мы решили отложить её появление подальше.

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

Сеть

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

gRPC

Мы выбрали gRPC фреймворк, чтобы добиться высокой скорости передачи данных и малого размера сообщений. gRPC работает поверх HTTP/2, который может bi-directional streaming, flow control, header compression и multiplexing. Последнее позволяет переиспользовать одно TCP-соединение для отправки нескольких запросов параллельно, расходуя ресурсы на рукопожатие только при первом обращении. Больше не надо поддерживать пул TCP-соединений, как это приходилось делать при использовании HTTP/1.x, где на каждый запрос только в лучшем случае соединение переиспользовалось, а в худшем создавалось новое. В случае 2G сети это особенно критично, так как в большинстве случаев рукопожатие занимает больше времени, чем сама передача данных.

В качестве протокола сериализации gRPC использует Protobuf. В отличие от текстового JSON, Protobuf является бинарным. За счет этого он имеет более быстрый маршалинг и меньший размер на выходе, а это в свою очередь позволяет уменьшить потребление интернет трафика пользователя. В качестве библиотеки мы использовали gRPC на базе okHttp и Java Protobuf Lite.

Кэш

Мы снизили количество сетевых запросов за счет кэширования большого количества данных. Для этого в Lite-приложении реализовали два слоя кэша: in-memory и on-disk, защиту от дублирования запросов и гибко конфигурированную систему правил инвалидации кеша.

Стратегия кэширования нашего приложения

Самый быстрый запрос — невыполненный запрос

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

Инвалидация кэша

Мы стремились сократить количество обновлений кэша, если пользователь использует мобильную сеть, и увеличить, если ставит смартфон на зарядку и подключает WIFI. Для этого мы применили WorkManager, который имеет гибкую настройку планирования задач.

Polling

В Lite-версии мы избавились от всех long и short polling запросов, что позволило нам избежать ненужной коммуникации между клиентом и сервером. Теперь бекенд сам присылал нам данные, если происходили какие-то изменения.

Дублирование запросов

На некоторых экранах у нас был одинаковый контент, и могла возникнуть такая ситуация, когда выполнялось параллельно несколько одинаковых запросов. Это приводило к неоптимальному расходованию мобильного трафика пользователя. Решение было достаточно простое и заключалось в том, чтобы вместо нескольких запросов, выполнять только один и результатом делится с другими.

Картинки

Огромная часть потребляемого трафика приходится на картинки. Первое, что мы сделали, это перестали загружать картинки неоптимального размера и напрямую обращаться к CDN. Вместо этого мы передаем нашему бекенду URL и точный размер изображения, нужного нам в данный момент. Сервер, в свою очередь, берет ближайшую по размеру картинку с CDN, обрезает ее до нужного размера, проделывает сжатие, кэширует и возвращает клиенту.

Стоит отметить, что все общение происходит по gRPC, через ранее установленное соединение, а функция streaming позволяет передавать картинку чанками.

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

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

Клиент-серверная архитектура

Размер приложения

Если полистать Google Play, то можно легко обнаружить, что вес среднего приложение в нем варьируется на отметке в 30 MB. И это плохая новость для пользователей из развивающихся стран, так как 2G-сети с высокой вероятностью или не загрузят приложение, или время загрузки будет превышать все разумные границы. Маленький размер приложения, в свою очередь, позволяет также сравнительно быстро обновлять его. Наша цель, уменьшить размер приложения до 10 MB, позволила мгновенно загружать его даже при слабой сети.

Выбор подходящей архитектуры

Для решения проблемы мы рассмотрели некоторое количество подходов, о которых писали разработчики других легковесных приложений (Facebook, Spotify, Twitter, Pinterest):

  • Возможность использования кроссплатформенных фреймворков, таких как Flutter, Xamarin, React Native, сразу откинули из-за стартового веса приложения с этими подходами на борту.
  • Один из возможных вариантов был убрать весь продуктовый код и все ресурсы из приложения. Фактически это означало бы превратить легковесный клиент в VM, что предоставляет доступ к разным интерфейсам OS. Присылаемый из сервера UI в сумме с кешированием его частей на клиенте позволил бы снизить размер приложения до минимума и убрать необходимость обновлять клиент приложения почти на совсем. Такой подход обещает бесконечные возможности к масштабированию, но из-за высокой нагрузки на сеть при клиент-серверном общении и трудозатратности создания такой архитектуры в ограниченных по времени условиях, этот вариант тоже откинули.
  • Подход с использованием WebView как решение проблемы казался привлекательным до тех пор, пока мы не столкнулись с проблемами отображения на разных устройствах. А сложности взаимодействия с нативной Android-инфраструктурой только добавляли масла в огонь. Поскольку мы не хотели лишать пользователя приятного опыта пользования нашим приложением, решили использовать такой подход только на простых экранах.
  • Таким образом мы подходим к основному решению проблемы, что мы и избрали, а именно — использование нативновного подхода к написанию легковесного приложения. Дальнейшее повествование будет идти в контексте именно этого архитектурного выбора.

Очевидные утяжелители приложение

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

  • Мы начали использовать Vector Drawables вместо PNG, JPEG во всех возможных местах в приложении, во всех невозможных мы конвертировали файлы этих форматов в WebP (имеет лучшее сжатие, чем в JPEG и прозрачность как в PNG).
  • Мы удалили все ресурсы, что мы могли получить преобразованием существующих, такими тулзами Android SDK как ClipDrawable, InsetDrawable, RotateDrawable, ScaleDrawable, etc.
  • Мы очистили проект от вещей, что могут генерировать дополнительный код (к примеру, synthetic accessors).
  • Мы полностью убрали использования enums в проекте.
  • Переработка дизайна многих экранов помогла снизить их количество до минимума, так же как и их загруженность.

Необходимые библиотеки

Одним из важнейших шагов в оптимизации размера приложения стала очистка проекта от сторонних зависимостей. Мы почти всегда не можем обойтись без библиотек, что уже давно надежно решили самые популярные нужды приложения за нас, тем не менее, вес этих библиотек тянет на дно нашу идею легкого приложения. Даже описанные ниже инструменты для урезания многих частей этих библиотек на уровне байт-кода не способны очистить все, поэтому остается только самый сильный инструмент — наш выбор.

Мы осторожно выбрали только необходимые модули самых нужных решений. Все остальное мы удалили или переписали.

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

Низкоприоритетные экраны

Благодаря некой самостоятельности части функционала, нам удалось ее вынести в пару Play Feature Delivery модулей, что значительно снизило вес приложения, впредь до момента когда пользователю понадобится эта часть приложения.

Как уже упоминалось выше, такие экраны как политика конфиденциальности, помощь, настройки и тому подобные, были реализованы в виде WebView. Такой подход также способствовал уменьшению размера приложения, не жертвуя опытом пользования приложением.

Оптимизация APK

Хотя мы смогли существенно снизить вес приложения за счет уменьшения сгенерированного кода, мы не можем провернуть те же действия над сторонними библиотеками, что мы используем. Для решения этой проблемы, а также дополнительных оптимизаций размера APK, следующим решением стало использование таких инструментов как R8 и Redex (перечень оптимизаций Redex).

Использование же App Bundles уменьшило размер загрузки при установке приложения к возможному минимуму.

Поддержка

Поддержка приложения в легковесном состоянии не менее важная задача, чем его создание. Думаю, никто не хочет узнать, что его работа была проделана зря, прямо на следующий релиз приложения. Теперь всякое сколько-нибудь значимое изменение в коде проекта сопровождается тщательным изучением приложения в APK Analyzer, а вежливый пайплайн на нашей CI всегда предупредит нас об утолщении приложения в каждом MR в develop.

Заключение

В итоге, мы сократили среднее время запроса в 6 раз, а размер приложения в 5 раз с 42 до 8 мегабайт. Можно было бы добиться лучших результатов? Да, например, команда Facebook уменьшила размер их приложения в 30 раз, а Twitter в невероятные 150. В итоге, мы выполнили поставленную задачу с учетом ограниченных ресурсов и сроков. Приложение быстро набрало большое количество скачиваний и получило высокую оценку, обогнав старшего брата в Бразилии.

Полезные материалы

👍ПодобаєтьсяСподобалось21
До обраногоВ обраному10
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

Спасибо за то, что поделились своим опытом. Хочется верить, что на dou будет больше подобного

навешивание ярлыков — наше все _) а потом возмущение, какого х***ра на ресурсе для инженеров нет инженерных статей ))))

Бачиш, деякі «інтелектуали» з контексту статті навіть не змогли зрозуміти, для чого написано додаток, то чого ти від них очікував? ))
P.S.
Взагалі, питання оптимізації коду/перформансу часто відкладають на останні етапи. Не те щоб це було взагалі невірно, але як показує практика, часто коли до нього доходять виявляється що велику частину того що написали варто було б робити не так. А часу на переробку вже немає. Буває ще гірше — коли до нього не доходять взагалі, бо проект мав бути зданий «на вчора» і т.п., і в продакшн випускають по-суті альфу.

Крута стаття, достатньо точно описали процес розробки продукту від проектування до реалізації

команда Facebook уменьшила размер их приложения в 30 раз, а Twitter в невероятные 150.

Они быдлокодеры, зачем было изначально писать такие огромные приложение, которые потом легко можно в 150 раз уменьшить?
Хорошими инженерами они бы были, если бы сразу написали с минимальным размером.

Кто вам сказал, что они плохие инженеры? Сначала они получили зарплату за оверинжиниринг, а потом за оптимизацию.

Затем, что пока одни допиливают идеальное приложение, другие захватывают клиентскую базу (если кратко). И делают легкую версию.

хорошая статья

В Lite-версии мы избавились от всех long и short polling запросов, что позволило нам избежать ненужной коммуникации между клиентом и сервером. Теперь бекенд сам присылал нам данные, если происходили какие-то изменения.

чего не избавились в обычной версии, если есть такая возможность?

Благодаря некой самостоятельности части функционала, нам удалось ее вынести в пару Play Feature Delivery модулей, что значительно снизило вес приложения, впредь до момента когда пользователю понадобится эта часть приложения.

как в целом опыт? у меня порядочно горело, поразительно сырая штука учитывая что ей уже года два точно.

но пожалуйста не «экономьте» больше на енамах, это давно моветон.

Главное приложение принадлежит отдельной от нас команде.

В целом нареканий на работу не было(странных крашей при скачке модуля не наблюдали, репортов о том что что-то не скачивается тоже не получали) Было много архитектурных сложностей с тем, что модули имеют инвертируваную зависимость и с тем как-это все тестировать.

например, команда Facebook уменьшила размер их приложения в 30 раз, а Twitter в невероятные 150

а где-то ангулярщик наоборот простую 50Х.html раздул на мегабайты
мир многогранен

а как энамы утежеляли проект?

enums влияют/ли на кольво генерируемого кода.
developer.android.com/...​uce-apk-size#remove-enums
www.youtube.com/watch?v=lTo03M2HzFY

В целом R8/Proguard решают эти проблемы. Но команда приняла более консервативное решение в пользу int.

Столкнулись ли с какими то трудностями во время интеграции с Redex?

Максиме, Владе, дякую, що ділитеся досвідом. Щоправда, можливо, ви забули згадати одну деталь у своєму представленні. Ви — студенти 4-го курсу і вже досягли таких значних успіхів. Навчатися із такими одногрупниками — справжня честь. Відчуваєш велику радість, коли бачиш, що розумним та здібним у цьому світі все можливо! Бажаю подальших успіхів, упевнений — попереду ще вищі горизонти:)

Спасибо за статью! На ДОУ редко пишут качественный материал по андроиду

Благодарю за статью! Полезная информация с удобной подачей

Интересная статья, спасибо за некоторые идеи

Уточните как сервер сам присылал обновления на клиент?

Вангую, что-то вроде Server Sent Events используется. Но может и другие варианты какие-то конечно.

Когда приложение используется grpc streaming, когда не используется Firebase Cloud Messaging.

т.е постоянный коннект который немеренно кушает батарейку?

Мы постоянно профайлили енергопотребление. Результат был оптимальный.

выходит байты по сети ходят, а батарейку не кушают, так?

Если у вас есть результаты своих замеров — поделитесь

Будь-яка імплементація push messaging працює точно так само.
Звісно, батарея з’їдається, але у відсотковому відношенні — мізер.
Мова ж йде про додатки

Rozetka и Parimatch Tech.

тобто людина періодично користується телефоном (власне, це її персональний пристрій), то практично вся енергія піде на підсвітку екрану та location.

Уже ж ніби практику пакетного використання «радіо» давно визнали deprecated. Як буде час — знайду відео з Google IO.

Эх, помнится MathCad под WindowsMobile 10 лет назад был- 0,8MB, а сейчас большое технологическое достижение, если апликушка которая json с котиками рендерит менее сотки.

Первое, что приходит в голову, создать ключ по URL

Не, ну таки Math.round(s/threshold)*threshold это и есть первое что приходит туда.

Доброго дня, Денисе. Дякую, що приділили час статті. Хотів би уточнити, що стаття ніяк не відноситься до продуктів компаній Rozetka або Parimatch. Ми описали минулий досвід розробки додатку для сервісу таксі в одній из попередніх компаніях. Мені важко відповісти на ваші питання, тому що додаток для виклику таксі не має списків товарів та багато картинок.

Ми мали достатньо мало картинок, які потрібно було викачувати з мережі. Але загрузка таких картинок, як фото водія, займала дуже багато часу у 2G. Тому довелося зробити ряд оптимізацій, які описані у статті.

Так Ви правильно помітили, що CDN нічим не допоможе. Так найкращий варіант позбутися картинок чи використовувати векторку, але ми не могли не показувати фото водіїв. Нам не тільки потрібно було швидко завантажувати аватарку, а ще й завантажувати ії в більш меньш якості, щоб rider міг розібрати, хто на ній. Тому ми вирішили передавати саме той розмір, який буде відповідати його Image View. Детальніше описано в статті. Результат користувачів та нас влаштував.

Parimatch

Дальше не читал, и вам не рекомендую.

Мы полностью убрали использования enums в проекте.

они то вам чем не угодили?

enums влияют/ли на кольво генерируемого кода.
developer.android.com/...​uce-apk-size#remove-enums
www.youtube.com/watch?v=lTo03M2HzFY

В целом R8/Proguard решают эти проблемы. Но команда приняла более консервативное решение в пользу int.

и сколько удалось сэкономить?

Сэкономить? Щща. Раздуть бюджет и заработать. Надо же хоть чем-то оправдывать раздутый штат.

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

Это не продукт, а фуфло. И фичи соответственно. Не думаю, что там на 5 лет, но если хотя бы на полгода навыдумали — уже умные парни.

В итоге, мы сократили среднее время запроса в 6 раз,

Тут цікавіше саме прискорення POST запитів. Тобто тих, які не потрапляють в кеш — адже GET запити мали би працювати швидше в разів 10 (залежить від TTL та умов зберігання...).

Ще раз хочу уточнити, що мова йдеться про додаток для виклику таксі. Ми мали достатньо мало картинок, які потрібно було викачувати з мережі. Але загрузка таких картинок, як фото водія, займала дуже багато часу у 2G. Тому довелося зробити ряд оптимізацій, які описані у статті.

Доброго дня, Олександр. Я звернуся до колишніх колег та відпишу Вам трохи згодом.

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