Сегодня в магазинах приложений для платформ iOS и Android существует ограничение на размер приложения в 100 МБ. Магазин Apple для приложений, которые не укладываются в этот лимит, запрещает закачку при помощи мобильного интернета. В  Google Play же это строгий лимит на размер APK – все, что не укладывается в него, должно быть вынесено в файлы дополнений. Для пользователей с платным трафиком закачка большого приложения может быть довольно затратной, поэтому его размер нужно стараться уменьшить всеми силами.


В рамках этой статьи мы расскажем, с помощью каких приемов мы смогли уложиться в это ограничение на проекте Gardenscapes для платформы iOS. Статья касается в основном мобильных игр, но методы сжатия универсальны и могут пригодиться для любых проектов с тяжелой графикой. Для того, чтобы говорить о методах сжатия, нужно определиться с тем, как формируется архив приложения.

Формат магазина приложений Apple


Приложения на платформе iOS распространяются в виде бинарных файлов с расширением .ipa. Эти сжатые .zip-папки, в которые входит исполняемый файл приложения (для процессора с архитектурой ARM), а также ресурсы приложения. Использование .zip-формата означает, что ваш ipa-файл можно открыть с помощью любой удобной утилиты для работы с архивами и изучить. Если точнее, то Apple использует свой собственный формат LZFSE, и так просто распаковать архив не получится. Тем не менее, оглавление архива прочитать можно без дополнительных усилий. Для примера давайте посмотрим на содержимое ipa в проекте Gardenscapes:

Диаграмма распределения ресурсов в .ipa файле

Большую часть занимают ресурсы игры, при этом исполняемый файл почти не уступает им. Системные ресурсы и ресурсы SDK при этом занимают достаточно мало места в архиве, поэтому оптимизировать их чаще всего нет смысла.

Оптимизация размера исполняемого файла


Исполняемый файл занимает так много места по двум причинам:

  1. В нем содержится бинарный код для двух архитектур (32 и 64 бита);
  2. Исполняемый файл в ipa зашифрован, что сильно ухудшает сжатие.

Начиная с iOS 9 первый пункт уже неактуален, поскольку Apple применяют технологию App thinning, собирая разные пакеты для разных архитектур. С точки зрения разработчика это работает прозрачно: вы отправляете на сабмит одно приложение как и раньше. А пользователю скачается только 32- или 64-битный исполняемый файл, в зависимости от устройства. Поэтому, если вы можете позволить себе не поддерживать устройства с iOS версии ниже 9, то размер исполняемого файла можно делить примерно пополам. При этом пользователи с версиями iOS 8 и ниже будут качать архив, содержащий обе архитектуры.

Пункт 2 актуален всегда. При расчете предполагаемого размера IPA нужно учитывать несжатый размер исполняемого файла. В лучшем случае он сожмется только незначительно. Именно поэтому Apple не рекомендуют хранить большие объемы данных (изображения, большие текстовые ресурсы и т.д.) в исполняемом файле, это может привести к аномально большому размеру приложения. Стоит также проверить настройки оптимизации в XCode и убедиться в том, что вы не подключаете лишние библиотеки.

Оптимизация ресурсов


Для мобильных игр можно выделить группы ресурсов, по списку от более тяжеловесных к менее тяжеловесным:

  1. Игровая графика;
  2. Анимации;
  3. Видео и звуки;
  4. Шрифты;
  5. Текстовые ресурсы и текстовые настроечные файлы.

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

Сжатие игровой графики


Размер графики можно уменьшить двумя способами:

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

В наших проектах используются в основном png, webp и pvrtc:

  • png: формат сжатия без потерь. У него наилучшее качество, но он занимает очень много места на диске. Этот формат не рекомендуют использовать в приложении, поскольку lossless-вариант webp оптимальнее по размеру. Тем не менее, загрузочный экран и иконки приложения в ipa должны быть в формате .png. Для оптимизации размера .png можно воспользоваться утилитой optipng, доступной в Mac Ports.
  • webp: lossy-вариант c приемлемым размером. Позволяет менять качество для различных типов изображений, но достаточно долго распаковывается. Существует также и lossless-вариант.
  • pvrtc: аппаратно-поддерживаемый сжатый формат для iOS. Быстро загружается, занимает очень мало места. Однако, есть ряд ограничений. Во-первых, изображение обязательно должно быть квадратом со стороной, равной степени двойки.

    Во-вторых, pvrtc – это lossy-формат, и даже очень lossy. Его нужно применять осторожно, не стоит использовать с графикой, для которой требуется высокое качество. Плохо подходит для графики с прозрачными областями, на границе будут артефакты. Основная выгода от формата pvrtc – экономия оперативной памяти. В отличие от форматов png и webp, его не нужно распаковывать в RGBA для отрисовки.

Не стоит забывать о том, что у сжатых форматов есть свои настройки качества, и их тоже можно настраивать.
Webp 100
Webp 95
Webp 85



Пример сжатия

Пример одного и того же изображения в формате webp, сжатого с настройкой качества 85, 95 и 100. Разница заметна, но даже самое сжатое изображение выглядит приемлемо. При этом разница в размере между webp85 и webp95 примерно 3 КБ.

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

В таблице приведены размеры атласа 2048x2048 при сжатии в разных форматах:
Формат
png
pvrtc
webp
lossless
webp
95 quality
webp
85 quality
Размер, КБ
8057
2049
5593
2218
1579

Как видите, формат png не следует использовать вообще. Из форматов webp и pvrtc можно выбрать подходящий, с учетом требований к качеству графики и скорости ее загрузки

Анимации


В Gardenscapes в основном используются анимации с ключевыми кадрами, которые экспортируются из .swf. Для экономии места в таких анимациях нужно стараться оптимизировать число кадров в секунду (FPS). В нашем проекте стандартом выбрана частота 24 FPS, анимации не теряют в качестве и достаточно компактны. Помимо частоты кадров, на размер анимации влияет количество деталей. Если анимация содержит много деталей, ее размер также будет большим – поскольку для каждой детали будет хранится свой набор ключевых кадров. Для дополнительного сжатия мы используем специальный lossy-кодек для анимаций. Также мы используем FSE-кодирование, больше информации можно найти здесь.

Видео и звуки


Здесь нет каких-то общих рекомендаций, в целом все сводится к тонкой настройке форматов кодирования. Какого-то феноменального выигрыша тут получить нельзя, у нас получилось сжать видео на примерно 30% по сравнению с первоначальным вариантом и сэкономить порядка 10-20% на звуках. Все зависит от конкретного видео и аудио – нужно подбирать настройки экспериментально.

Шрифты


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

Текстовые ресурсы и текстовые настроечные файлы


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

Если все возможности исчерпаны


Размер билда – это действительно проблема для нас. Насколько все серьезно, можно понять по некоторым дополнительным способам сжатия, которые мы изобрели.

  1. Внутри ipa-файла приложение упаковано в так называемый пакет (package). Все файлы приложения находятся в папке «Payload/<имя_пакета.app>. При упаковке файла в zip-архив к оглавлению прибавляется его полный путь. Поэтому, <имя_пакета.app> будет в оглавлении всех файлов игры. И если файлов много, то как ни крути, а лишняя информация дублируется. Мы переименовали пакет так, чтобы его имя состояло ровно из одной буквы, и хоть чуть-чуть, но уменьшили размер.
  2. Просматривая содержимое исполняемого файла, мы увидели, что встречаются полные пути к исходным файлам C++. Откуда они взялись? Оказывается, некоторые макросы __FILE__, __LINE__, попадали в production-код, и компилятор заботливо раскрывал их для каждого исходного файла, где они использовались. Мы провели ревизию таких макросов, и еще немного уменьшили билд.
  3. У кого-то есть еще идеи? Напишите в комментариях.

Итого


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

  • Менеджмент ресурсов: как избавиться от дубликатов ресурсов и найти все неиспользуемые ресурсы;
  • Система загрузки ресурсов из интернета, если мы не влезли в 100 МБ. Как организовать дозагрузку ресурсов так, чтобы пользователь испытывал минимум дискомфорта, а дизайнерам было бы удобно с ней работать;
  • Как минимизировать расход трафика, особенно при обновлении версии игры.

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

Post scriptum


Пока статья готовилась к публикации Apple увеличила максимальный объем билда до 150 МБ. Казалось бы, стало лучше. Но в Google Play все еще остается лимит 100 МБ. В результате все получается еще сложнее. Если мы при подготовке билда для iOS будем ориентироваться только на ограничение магазина Apple в 150 МБ, то после этого при подготовке билда для Android точно не сможем уложиться в 100.

Нужно делать разные билды для iOS и Android, чтобы оптимально использовать возможности магазинов приложений. Сейчас мы все еще в процессе поиска лучшего решения. И очень ждем аналогичного шага по увеличению лимита от компании Google. После этого можно будет просто заменить в тексте статьи число 100 на 150, и в ней снова все будет правильно.

Комментарии (32)


  1. sumanai
    12.11.2017 18:04
    +1

    Исполняемый файл в ipa зашифрован, что сильно ухудшает сжатие.

    А они не пробовали сначала сжимать, а потом шифровать? Просто странно как-то.


    1. s_shestakov Автор
      12.11.2017 21:19

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


  1. KonH
    12.11.2017 18:18

    У Gardenscapes размер в App Store 190 мб, но при этом по сотовой сети скачивается успешно. Это связано с разделением бинарников, что было описано в статье? Но такой размер это конечно не предел, можно ужаться и больше. Интересно было бы узнать, какие именно ресурсы у вас идут в комплекте с билдом (включаете ли вы туда первые уровни и все такое).


    1. s_shestakov Автор
      13.11.2017 09:07

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

      Иногда, несмотря на все усилия, даже ресурсы, требуемые для старта, не помещаются в 100 Мб. Тогда приходится делать так называемый «стартовый пак», который скачивается сразу после установки.


  1. AgaFonOff
    12.11.2017 19:52
    +1

    Оптимизация PNG — очень обширная тема, рекомендую поинтересоваться, чем поможет pngquant. Можете очень удивиться результатам (зависит от исходного материала, конечно).


    1. saintp16
      12.11.2017 20:02

      Взял на заметку себе. Спасибо


    1. s_shestakov Автор
      13.11.2017 09:09

      У нас практически вся графика конвертируется в другие форматы. Есть даже специальный тест, который проверяет, что в билде нет лишних PNG. Самая лучшая оптимизация PNG, по нашему мнению, — заменить его на WEBP (прошу не считать рекламой).


  1. tronix286
    12.11.2017 20:49

    В догонку про PNG — еще рекомендую optipng с ключем -o7. Иногда работает вместе с pngout. Либо сначала pngout а потом optipng, либо наоборот.


    1. sumanai
      12.11.2017 21:06

      PNGZopfli забыли, раз уж начали за оптимизацию.


      1. Denai
        12.11.2017 23:19

        В любом случае проще всего прогнать через весь набор софта и забрать итоговый самый маленький файл.


        1. Alexmaru
          13.11.2017 09:14

          а для этого на саке бксплатный ImageOptim.


  1. mickvav
    13.11.2017 00:11

    Кто бу еще объяснил, как отучить google play требовать 300mb свободного места для обновления приложения объемом в 5 mb. любого…


    1. apachik
      13.11.2017 02:05

      никак. такова суть. Сначал приложение скачивается, потом распаковывается из zip (тут уже может быть сильно больше 5мб), а потом устанавливается в конечное место как я понимаю


  1. LonerD
    13.11.2017 00:28

    Хорошо, хотя бы в Amazon AppStore не додумались до таких бессмысленных ограничений.


  1. digitalman
    13.11.2017 00:28

    В таблице, где сравниваются png, pvrtc и webp, правильней брать размер pvrtc сжатого zip, ведь сравниваются размеры файлов, какими они будут в пакете приложения.


    1. eri
      13.11.2017 20:19

      энтропия у файлов после кодека обычно высока. они не сжимаются практически никак


  1. sim-dev
    13.11.2017 08:00

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

    Какая трогательная забота! Я рыдаю…
    Более лицемерного правила я не встречал, разве что еще «бесплатная медицина»…
    О какой экономии трафика пользователя идет речь, если Google PlayMarket (для примеру) обновляется еженедельно (в среднем) не взирая на запрет автоматические обновления приложений, причем делает это и через «мобильные данные»?
    Сейчас у меня, наконец, данная ситуация «самовыпилилась»: ежедневные попытки обновиться заканчиваются ошибкой «не хватает места» — ну хоть так…


    1. kaftanati
      13.11.2017 08:54

      Галочка «Обновляться только по Wi-Fi» в GP решает и никогда не подводила. Вы ее включали? Тогда и размер приложений нипочем!


      1. sim-dev
        13.11.2017 10:45

        Ничего она не делает с теми вещами, которые гугл считает «системными»…


  1. Antervis
    13.11.2017 08:44

    Для Qt приложений можно воспользоваться Qt Lite


  1. Sklott
    13.11.2017 09:11

    А расскажите пожалуйста, вашей игре реально надо 1.5Гб на iOS, или вы просто не умеете мусор чистить? Игра стоит чуть не с самого начала и при каждом обновлении все растет и растет…


    1. s_shestakov Автор
      13.11.2017 11:30

      Мы стараемся чистить мусор. Но да, в первых версиях были с этим проблемы. Можно попробовать переустановить игру.


  1. Dmitri-D
    13.11.2017 09:14

    похоже на ошибку в размещении примеров webp. 100% выглядит наихудшим — посмотрите на фон, там виден муар или что-то вроде этого — переходы не плавные. На 90 и 85 этого нет.


  1. Andrew2016
    13.11.2017 11:38
    +1

    В запасе еще есть как минимум 2 способа для iOS:
    1) Положить все ресурсы в zip без сжатия и сменить расширение полученного архива на что-то другое (dat). Будет экономия на подписях — вместо сотни файлов только один. Плюс некий аналог solid архива для ipa.
    2) Положить все ресурсы в .7z. Придется проверить приемлемость скорости запуска. Важен баланс…


  1. rrrrex
    13.11.2017 12:20

    У ленивых разработчиков нередко можно заметить то, что они используют целые библиотеки ради 1-2 функций, а библиотека может прилично весить.


    1. s_shestakov Автор
      13.11.2017 16:20

      По идее, при линковке удалится все, кроме этих 1-2 функций


      1. apachik
        15.11.2017 11:36

        это если библиотеки собираются как статические библиотеки. А если как фреймворки, то шиш


        1. s_shestakov Автор
          15.11.2017 14:02

          Для iOS почти все сторонние фреймворки статические


          1. apachik
            15.11.2017 14:36

            для свифта — динамические


  1. da411d
    13.11.2017 14:06

    Я не специалист в этой области, но нельзя ли засунуть весь код в ресурсы и подгружать его динамически?
    А в самом исполняемом файле будет только загрузчик? Или на IOS это запрещено?


    1. s_shestakov Автор
      13.11.2017 16:25

      Да, Apple запрещает скачивание исполняемых файлов.
      developer.apple.com/app-store/review/guidelines/#software-requirements


  1. varton86
    15.11.2017 13:51

    On-Demand Resources Essentials не подходят в этом случае?