» » » Играем в APK-гольф. Уменьшение размера файлов Android APK на 99,9%

 

Играем в APK-гольф. Уменьшение размера файлов Android APK на 99,9%

Автор: admin от 12-10-2017, 18:50, посмотрело: 36

В гольфе выигрывает тот, у кого меньше очков.



Применим этот принцип в Android. Мы собираемся поиграть в APK-гольф и создать приложение минимально возможного размера, которое можно установить на Android 8.0 Oreo.



Базовый уровень



Начнём с дефолтного приложения, который генерирует Android Studio. Создадим хранилище ключей, подпишем приложение и измерим размер файла в байтах командой stat -f%z $filename.



Затем установим APK на смартфон Nexus 5x под Oreo, чтобы убедиться, что всё работает.



Играем в APK-гольф. Уменьшение размера файлов Android APK на 99,9%



Прекрасно. Наш APK весит примерно полтора мегабайта.

APK Analyser из Android Studio.



Играем в APK-гольф. Уменьшение размера файлов Android APK на 99,9%


Вопреки нашим первоначальным предположениям, похоже, что самый большой файл — Dex, а на ресурсы приходится всего 20% от размера APK.




























ФайлРазмер
classes.dex74%
res20%
resources.arsc4%
META-INF2%
AndroidManifest.xml<1%


Исследуем по отдельности, что делает каждый файл.



Файл Dex



classes.dex — главный виновник раздутого APK, он занимает 73% всего объёма и поэтому станет первой целью оптимизации. Этот файл содержит весь наш скомпилированный код в формате Dex, а также список внешних методов во фреймворке Android и библиотеку поддержки.



В пакете android.support перечисляется более 13 000 методов, что кажется излишним для приложения типа "Hello World".



Ресурсы



В директории res находится большое количество файлов шаблонов, чертежей (drawables) и анимаций, которые сразу не видны в интерфейсе Android Studio. Опять же, они вытянуты из библиотеки поддержки и занимают около 20% размера APK.



Играем в APK-гольф. Уменьшение размера файлов Android APK на 99,9%


Файл resources.arsc также содержит список всех этих ресурсов.



Подпись



В папке META-INF находятся файлы CERT.SF, MANIFEST.MF и CERT.RSA, которые нужны для подписи v1 APK. Если злоумышленник изменит код внутри APK, то подписи не совпадут, что защищает пользователя от запуска постороннего зловреда.



В MANIFEST.MF перечисляются файлы из APK, а CERT.SF содержит контрольные суммы манифеста и каждого отдельного файла. В CERT.RSA хранится открытый ключ, которым проверяется цельность CERT.SF.



Играем в APK-гольф. Уменьшение размера файлов Android APK на 99,9%


Здесь нет очевидных целей для оптимизации.



AndroidManifest



AndroidManifest очень похож на наш оригинальный файл. Единственное отличие — вместо ресурсов вроде строк и drawables здесь указаны их целочисленные идентификаторы, начиная с 0x7F.



Включаем минификацию



Мы ещё не пробовали включить опцию минификации и сжатия ресурсов в файле build.gradle для нашего приложения. Сделаем это.



android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile(
              'proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}


-keep class com.fractalwrench.** { *; }


Если установить minifyEnabled в значение true, то активируется Proguard, который очищает приложение от ненужного кода. А также обфусцирует имена символов, затрудняя обратную разработку приложения.



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



786 КБ (уменьшение на 50%)



Мы наполовину уменьшили размер APK без видимого изменения в работе программы.



Играем в APK-гольф. Уменьшение размера файлов Android APK на 99,9%



Если вы ещё не включили minifyEnabled и shrinkResources в своём приложении, это самая главная вещь, которую следует вынести из этой статьи. Можно легко сэкономить несколько мегабайт, потратив всего парочку часов на конфигурацию и тестирование.



Прощай, AppCompat, мы едва тебя узнали



classes.dex теперь занимает 57% всего APK. Основная часть списка методов из файла Dex принадлежит пакету android.support, так что мы собираемся удалить библиотеку поддержки. Для этого нужно сделать следующее:




  • Полностью удалить блок зависимостей из build.gradle.



    dependencies {
        implementation 'com.android.support:appcompat-v7:26.1.0'
        implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    }

  • Обновить MainActivity для расширения класса android.app.Activity.



    public class MainActivity extends Activity

  • Обновить наш шаблон для использования единого TextView.



    <?xml version="1.0" encoding="utf-8"?>
    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="Hello World!" />

  • Удалить styles.xml и аттрибут android:theme из элемента конвертировать их в WebP, более эффективный формат, который поддерживается в API 15 и более поздних версиях.



    К счастью, Google уже оптимизировала наши drawables, хотя в противном случае мы бы и сами могли оптимизировать их и удалить из PNG ненужные метаданные с помощью ImageOptim.



    Давайте поступим нешаблонно — и заменим все наши иконки запуска единственной однопиксельной чёрной точкой в папке res/drawable. Эта картинка весит 67 байт.



    6808 байт (уменьшение на 94%)



    Мы избавились почти от всех ресурсов, так что неудивительно, что размер APK уменьшился примерно на 95%. В файле resources.arsc по-прежнему упоминаются следующие ресурсы:




    • 1 файл шаблона

    • 1 строковый ресурс

    • 1 иконка лаунчера



    Пойдём сверху вниз.



    Файл шаблона (6262 байта, сокращение на 9%)



    Фреймворк Android раздувает наш файл XML и автоматически создаёт объект TextView, который используется как contentView для Activity.



    Попробуем обойтись без этого посредника, удалив файл XML и программно задав contentView. Объём ресурсов уменьшится, потому что исчезнет файл XML, но увеличится размер файла Dex, поскольку мы упоминаем там дополнительные методы TextView.



    TextView textView = new TextView(this);
    textView.setText("Hello World!");
    setContentView(textView);


    Выглядит как неплохой обмен.



    Имя приложения (6034 байта, сокращение на 4%)



    Давайте удалим strings.xml и заменим android:label в манифесте AndroidManifest буквой "A". Это кажется маленьким изменением, но удаление записи из resources.arsc уменьшает количество символов в манифесте и удаляет файл из директории res. Каждая мелочь идёт на пользу — мы только что сэкономили 228 байт.



    Иконка лаунчера (5300 байт, сокращение на 13%)



    Документация для resources.arsc в репозитории Android Platform объясняет, что каждый ресурс APK упоминается в resources.arsc с целочисленным идентификатором. У этих ID два пространства имён:



    0x01: системные ресурсы (предустановленные в framework-res.apk)



    0x7f: ресурсы приложения (в файле .apk приложения)


    Так что произойдёт с нашим APK, если мы поставил ссылку на ресурс в пространстве имён 0x01? По идее, мы получим более красивую иконку и одновременно уменьшим размер своего файла.



    android:icon="@android:drawable/btn_star"


    Играем в APK-гольф. Уменьшение размера файлов Android APK на 99,9%


    Само собой, вам никогда не следует доверять системным ресурсам вроде иконок в реальном рабочем приложении. Такой метод провалит валидацию в Google Play, а некоторые производители ещё и по-своему определяют белый цвет, так что действуйте осторожно.



    Манифест (5252 байта, сокращение на 1%)



    Мы ещё не трогали манифест.



    android:allowBackup="true"
    android:supportsRtl="true"


    Удаление этих аттрибутов экономит 48 байт.



    Хак Proguard (4984 байта, сокращение на 5%)



    Похоже, что классы BuildConfig и R ещё остались в файле Dex.



    -keep class com.fractalwrench.MainActivity { *; }


    Уточнение правила Proguard удалит ненужные классы.



    Обфускация (4936 байт, сокращение на 1%)



    Обфусцируем имя для класса Activity. Для обычных классов Proguard автоматически делает это, но поскольку имя класса Activity вызывается через Intents, его не обфусцировали по умолчанию.



    MainActivity c.java



    com.fractalwrench.apkgolf c.c


    META-INF (3307 байт, сокращение на 33%)



    В данный момент мы подписываем приложение одновременно подписями v1 и v2. Это кажется лишней тратой ресурсов, потому что v2 обеспечивает превосходную защиту и производительность, хешируя весь APK целиком.



    Подпись v2 не видна из APK Analyser, поскольку включена в бинарный блок в самом файле APK. Подпись v1 видна, в виде файлов CERT.RSA и CERT.SF.



    Давайте уберём галочку для подписи v1 в интерфейсе Android Studio и сгенерируем подписанный APK. Попробуем сделать и наоборот.
















    ПодписьРазмер
    v13511
    v23307


    Похоже, теперь мы будем использовать v2.



    Куда мы идём — там не нужны IDE



    Пришло время редактировать APK вручную. Используем следующие команды:



    # 1. Создать неподписанный apk
    ./gradlew assembleRelease
    
    # 2. Разархивировать архив
    unzip app-release-unsigned.apk -d app
    
    # Сделать необходимые правки
    
    # 3. Заархивировать архив
    zip -r app app.zip
    
    # 4. Запустить zipalign
    zipalign -v -p 4 app-release-unsigned.apk app-release-aligned.apk
    
    # 5. Запустить apksigner с подписью v2
    apksigner sign --v1-signing-enabled false --ks $HOME/fake.jks --out signed-release.apk app-release-unsigned.apk
    
    # 6. Проверить подпись
    apksigner verify signed-release.apk


    Детальный обзор процесса подписи APK см. здесь. В общем, Gradle генерирует неподписанный архив, zipalign делает выравнивание по границе байта для несжатых ресурсов, чтобы оптимизировать потребление RAM после загрузки APK, и в конце запускается криптографическая процедура подписи APK.



    Неподписанный и невыровненный APK весит 1902 байт, то есть процедура добавляет примерно 1 килобайт.



    Несоответствие размеров файлов (2608 байт, сжатие на 21%)



    Странно! Если разархивировать невыровненный APK и подписать его вручную, то пропадает файл META-INF/MANIFEST.MF, что экономит 543 байта. Если кто-то знает, почему так происходит, то дайте знать!



    Теперь у нас в подписанном APK осталось три файла. Но ведь мы можем ещё избавиться от файла resources.arsc, потому что не устанавливаем никаких ресурсов!



    После этого остаётся только манифест и файл classes.dex, оба примерно одинакового размера.



    Хаки со сжатием (2599 байт, сокращение на 0,5%)



    Теперь изменим все оставшиеся строки на ‘c’, обновив версии до 26, а затем сгенерируем подписанный APK.



    compileSdkVersion 26
        buildToolsVersion "26.0.1"
        defaultConfig {
            applicationId "c.c"
            minSdkVersion 26
            targetSdkVersion 26
            versionCode 26
            versionName "26"
        }


    [code]

    HexFiend.



    В заголовке файла угадываются некоторые интересные элементы — первые четыре байта кодируют 38, что совпадает с номером версии файла Dex. Следующие два байта кодируют 660, что совпадает с размером файла.



    Попробуем удалить один байт, установив targetSdkVersion на 1, и изменив размер файла в заголовке на 659. К сожалению, система Android отвергает новый файл как неправильный APK. Похоже, тут всё устроено как-то посложнее…



    Непонимание манифеста (1777 байт, сокращение на 9%)



    А попробуем набросать случайных символов по всему файлу, а затем установить APK, не изменяя указанный размер файла. Так мы проверим, осуществляется ли проверка контрольной суммы, и как наши изменения повлияют на смещения в заголовке файла.



    Удивительно, но такой манифест воспринят как валидный APK на Nexus 5X под Oreo:



    Играем в APK-гольф. Уменьшение размера файлов Android APK на 99,9%


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



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



    Манифест UTF-8



    Вот важные компоненты Manifest, без которых APK не установится.



    Играем в APK-гольф. Уменьшение размера файлов Android APK на 99,9%



    Некоторые вещи очевидны, такие как теги манифеста и пакета. В пуле строк видны versionCode и название пакета.



    Шестнадцатиричный манифест



    Играем в APK-гольф. Уменьшение размера файлов Android APK на 99,9%



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



    Но вряд ли здесь можно найти другие варианты для оптимизации.



    Готово? (1757 байт, сокращение 1%)



    Изучим окончательный APK.



    Играем в APK-гольф. Уменьшение размера файлов Android APK на 99,9%



    В течение всего этого имени в APK было указано моё имя в подписи v2. Создадим новое хранилище ключей, в котором используется хак для сжатия.



    Играем в APK-гольф. Уменьшение размера файлов Android APK на 99,9%



    Мы сэкономили 20 байт.



    Шаг 5: Признание



    1757 байт — это очень мало, чёрт возьми. И насколько я знаю, это самый маленький существующий APK.



    Однако я разумно полагаю, что кто-нибудь из Android-сообщества способен выполнить дальнейшие оптимизации и ещё улучшить результат. Если вы умудритесь уменьшить файл с нынешних 1757 байт, присылайте пулл-реквест в репозиторий, где хостится самый маленький APK, или сообщайте в твиттере. (С момента публикации статьи файл уже уменьшили до 820 байт — прим. пер.)


Источник: Хабрахабр

Категория: Операционные системы » Ubuntu

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

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
Введите два слова, показанных на изображении: *