Файлы DEX в Android

Обновлено: 31.01.2023


Создание и развертывание файла class.dex вручную позволяет не только добавлять библиотеки в ваше приложение, но и модифицировать встроенные Java-библиотеки RAD Studio для Android, или удалять те, которые вам не нужны . Если вам просто нужно добавить пользовательскую библиотеку Java в приложение для Android, см. раздел Добавление библиотеки Java в приложение с помощью диспетчера проектов.

Содержание

Создание файла class.dex

Файл class.dex — это исполняемый файл Dalvik, который должен быть у всех приложений Android. Этот файл содержит библиотеки Java, используемые приложением.

При развертывании приложения для Android RAD Studio включает файл class.dex, содержащий встроенные библиотеки Java RAD Studio. Чтобы использовать собственные библиотеки Java в приложениях RAD Studio, необходимо создать новый файл class.dex, который включает как встроенные библиотеки Java RAD Studio, необходимые вашему приложению, так и ваши собственные библиотеки Java.

Определение файлов JAR для включения в файл class.dex

Чтобы создать новый файл class.dex, вам потребуются JAR-файлы каждой библиотеки Java, которую используют ваши приложения, а также JAR-файлы библиотек, от которых зависят эти библиотеки.

Это также влияет на встроенные библиотеки Java RAD Studio. Например, если вашим приложениям требуется библиотека Java сервисов Google Play, ваш файл class.dex также должен содержать файл JAR библиотеки Java для лицензирования приложений Google Play, поскольку лицензирование приложений Google Play является зависимостью от сервисов Google Play. Если бы у лицензирования приложений Google Play были свои зависимости, их тоже нужно было бы включить.

В следующей таблице перечислены зависимости встроенных библиотек Java RAD Studio:

  • Огненная обезьяна
  • Лицензирование приложений Google Play
  • Огненная обезьяна
  • Лицензирование приложений Google Play

Эти файлы JAR можно найти в следующих папках в папке установки RAD Studio ( C:\Program Files (x86)\Embarcadero\Studio\22.0 ):

Вы можете либо включить все встроенные библиотеки Java RAD Studio в свой файл class.dex, либо определить на основе информации в приведенной выше таблице, какие встроенные библиотеки Java RAD Studio нужны вашим приложениям.

Предупреждение. Вы всегда должны включать библиотеки Java, необходимые для RAD Studio, в свой пользовательский файл class.dex.

Создание файла class.dex из файлов JAR

После того как вы определили, какие файлы JAR нужны вашим приложениям Android, вы можете создать из них файл class.dex.

Чтобы создать файл class.dex, необходимо использовать инструмент командной строки dx . Вы можете найти этот инструмент командной строки в C:\Users\Public\Documents\Embarcadero\Studio\22.0\CatalogRepository\AndroidSDK- .

Запустите dx с параметром --dex, параметром --output с выходным путем к файлу class.dex в качестве аргумента и разделенным пробелом списком путей к файлам JAR, которые вы хотите включить в сгенерированный файл class.dex. Например:

Примечание. Необходимо создать как отладочную, так и релизную версию файла class.dex. Функции отладки RAD Studio доступны только в том случае, если развернутый файл class.dex содержит отладочные версии включенных встроенных библиотек Java RAD Studio.

Развертывание файла class.dex

Предупреждение. Внимательно выполните следующие действия. Приложения для Android всегда должны включать действительный файл class.dex. Дополнительные сведения см. в разделе Неверный или отсутствующий файл class.dex ниже.

Чтобы настроить приложение Android для развертывания с помощью вашего пользовательского файла class.dex:

  1. Выберите «Проект» > «Развертывание», чтобы открыть диспетчер развертывания.
  2. Снимите флажок с файла class.dex по умолчанию.
  3. Нажмите кнопку и добавьте свой собственный файл class.dex в список файлов развертывания.
  4. Измените удаленный путь вашей новой записи на class\ .
  5. Измените платформы новой записи только на Android.
  6. DeploymentCustomClassesDex.jpg

    Устранение неполадок

    Неверный или отсутствующий файл class.dex

    Это сообщение об ошибке, которое вы видите, когда запускаете приложение на устройстве Android с помощью RAD Studio, и ваше приложение не имеет действительного файла class.dex:

    Пакет приложения Android (файл APK) всегда должен содержать допустимый файл class.dex. То есть:

    • В APK-файле должен быть файл class.dex.
    • Файл class.dex должен располагаться в classs/classes.dex в файле APK.
    • class.dex должен быть действительным исполняемым файлом Dalvik.

    При использовании пользовательского файла class.dex следует соблюдать осторожность. После того, как вы отключите классы по умолчанию.dex в диспетчере развертывания, и вы добавляете запись для своего пользовательского файла class.dex:

    • Убедитесь, что запись вашего пользовательского файла отмечена галочкой.
    • Убедитесь, что удаленный путь вашей новой записи — class\ .
    • Убедитесь, что ваша новая запись является файлом class.dex, а не другим файлом, который может быть недопустимым исполняемым файлом Dalvik.

    Если ваше приложение Android не настроено для развертывания с допустимым файлом class.dex, и вы запускаете свое приложение на устройстве Android из RAD Studio или устанавливаете на свое устройство APK-файл вашего приложения, созданный с помощью RAD Studio, Установка завершится ошибкой, но на вашем Android-устройстве останутся данные, которые не позволят вам устанавливать приложения с тем же именем пакета, что и у вашего приложения (см. Информация о версии Android).

    После того, как вы попытаетесь установить APK-файл без действительного файла class.dex, единственным известным решением будет выполнить сброс настроек вашего Android-устройства до заводских.

    Внимание! При сбросе к заводским настройкам с вашего устройства удалены все данные (личные данные, пользовательские настройки и т. д.). Тщательно подумайте, стоит ли устранение проблемы потери ваших данных. Если вы решите продолжить, не забудьте сделать резервную копию своих данных перед сбросом настроек.

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

    Что такое файл Dex?

    Файл Dex содержит код, который в конечном итоге выполняется средой выполнения Android. Каждый APK имеет один файл [CODE]classes.dex[/CODE], который ссылается на любые классы или методы, используемые в приложении. По сути, любое [CODE]действие[/CODE], [CODE]объект[/CODE] или [CODE]фрагмент[/CODE], используемые в вашей кодовой базе, будут преобразованы в байты в файле Dex, который можно запустить как Приложение для Android.

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

    Процесс дедексации

    Все исходные файлы Java в проекте Android сначала компилируются в файлы [CODE].class[/CODE], которые состоят из инструкций байт-кода. В традиционном Java-приложении эти инструкции будут выполняться на JVM. Однако приложения для Android выполняются в среде выполнения Android, которая использует несовместимые коды операций, и поэтому требуется дополнительный шаг расшифровки, когда файлы [CODE].class[/CODE] преобразуются в один [CODE].dex[/CODE]. файл.

    Поскольку большинство мобильных устройств имеют жесткие ограничения по объему памяти, вычислительной мощности и времени автономной работы, ART обеспечивает более высокую производительность, чем JVM. Одной из ключевых особенностей, которая помогает достичь этого, является то, что ART выполняет как предварительную, так и своевременную компиляцию. Это позволяет избежать некоторых накладных расходов JIT во время выполнения, но при этом позволяет повышать производительность по мере профилирования приложения.

    Как создать файл Dex

    Практический пример файла Dex значительно упрощает понимание. Давайте создадим минимальный APK, который содержит только один класс Application, так как это позволяет нам понять формат файла, не перегружаясь тысячами методов, присутствующих в типичном приложении.


    Мы будем использовать Hexfiend для просмотра нашего файла Dex в шестнадцатеричном формате, поскольку Dex использует некоторые необычные типы данных для экономии места. Мы скрыли все нулевые байты, поэтому пустые места на приведенном выше снимке экрана на самом деле представляют собой [CODE]00[/CODE].

    Структура файла Dex

    Полная структура нашего 480-байтового файла Dex показана ниже в шестнадцатеричном формате и кодировке UTF-8. Некоторые разделы мгновенно распознаются при интерпретации как UTF-8, например, единственный класс [CODE]BugsnagApp[/CODE], который мы определили в нашем исходном коде, а другие не очень:

    <Р>
    - КОД языка открытого текста -
    6465780A 30333800 7A44CBBB FB4AE841 0286C06A 8DF19000
    3C5DE024 D07326A2 E0010000 70000000 78563412 00000000
    00000000 64010000 05000000 70000000 03000000 84000000
    01000000 90000000 00000000 00000000 02000000 9C000000
    01000000 14010000 AC000000 CC000000 E4000000 EC000000
    07010000 2C010000 2F010000 01000000 02000000 03000000 03000000
    02000000 00000000 00000000 00000000 01000000 00000000
    01000000 01000000 00000000 00000000 FFFFFFFF
    00000000 57010000 00000000 01000100 01000000 00000000
    04000000 70100000 00000E00 69743E00 063C696E 194C616E
    64726F69 642F6170 702F4170 706C6963 6174696F 6E3B0023
    4C636F6D 2F627567 736E6167 2F646578 6578616D 706C652F
    42756773 01560026 6E616741 70703B00 7E7E4438 7B226D69
    6E2D6170 69223A32 362C2276 65727369 65727369 6F6E223A 2276302E
    312E3134 227d0000 00010001 818004CC 01000000 0A000000
    00000000 01000000 00000000 01000000 05000000 70000000
    020000 00 03000000 84000000 03000000 01000000 900000000 01000000 90000000
    05000000 02000000 9C000000000000000000 01000000 AC000000
    0120000 01000000 CC000000 02200000 05000000 E4000000
    00200000 01000000 57010000 00100000 01000000 64010000

    Интерпретация заголовка файла Dex

    На очень высоком уровне файлы Dex можно рассматривать как две отдельные части. Заголовок файла, содержащий метаданные, и тело, содержащее большую часть данных. Схема структуры заголовка файла показана ниже.


    Давайте последовательно пройдемся по каждому элементу в заголовке.

    Магия файлов Dex

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

    Мы видим, что первые 8 байтов должны содержать «dex», а номер версии — в настоящее время 38, когда наш [CODE]targetSdkVersion[/CODE] — API 26.

    Возможно, вы также заметили, что 4-й байт кодирует символ новой строки, а 8-й байт имеет значение null. Они проверяются Android Framework на наличие повреждений файлов — APK должен отказать в установке, если эта точная последовательность отсутствует.

    Контрольная сумма

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

    Подпись SHA1

    Заголовок также включает хеш SHA-1 файла (исключая все предшествующие байты). Это используется для уникальной идентификации файлов Dex, что может быть полезно в таких сценариях, как Multidex.

    Размер файла

    Это соответствует размеру файла в байтах, а также может использоваться для проверки при чтении файла Dex.

    Размер заголовка

    Размер заголовка должен быть 112 байт.


    Теперь мы можем выделить все оставшиеся поля в [CODE]header_item[/CODE].

    Постоянный порядок следования байтов

    Файлы Dex поддерживают кодировку как с прямым, так и с прямым порядком байтов. Это значение равно [CODE]REVERSE_ENDIAN_CONSTANT[/CODE], что указывает на то, что этот конкретный файл Dex закодирован с прямым порядком байтов, что является поведением по умолчанию.

    Идентификаторы и смещения

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

    - language lange - flainex -
    00000000 00000000 64000000 050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 / CC000000

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

    Стоит отметить, что [CODE]link_size[/CODE] и [CODE]field_ids[/CODE] равны 0, потому что наше приложение не связывает статически какие-либо библиотеки и не содержит никаких полей. Структура [CODE]map_off[/CODE] в разделе [CODE]data[/CODE] в значительной степени дублирует эту информацию в более простом формате для анализа файла Dex.

    В качестве примера мы можем видеть, что в нашем файле Dex есть 5 идентификаторов строк, закодированных между байтами 112–132. Каждый идентификатор в этой позиции также указывает на смещение в разделе [CODE]data[/CODE], которое кодирует фактическое значение строки.

    Список карт

    [CODE]map_list[/CODE] — это раздел в теле данных, который содержит информацию, аналогичную заголовку файла.


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

    Строки

    Хватит болтать, давайте посмотрим что-нибудь конкретное. Давайте выясним, на что указывает структура [CODE]string_ids[/CODE].

    Массив кодирует 5 целочисленных смещений, которые указывают на раздел данных.

    Если мы получим эти значения как UTF-8, нас поприветствуют несколько символов Java, которые будут знакомы всем, кто раньше использовал JNI, а также некоторые JSON, указывающие на то, что D8 создал файл Dex. Весь этот бизнес с идентификаторами, смещениями и несколькими заголовками может показаться немного бесполезным на данный момент. Почему бы просто не закодировать строковое значение прямо в заголовке?

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

    Типы

    Наш файл Dex определяет 3 типа Java. Каждое значение здесь является индексом в предыдущем массиве [CODE]string_id[/CODE] — поэтому мы можем определить, что типы в нашем файле следующие:

    Синтаксис TypeDescriptor может показаться несколько незнакомым, но L просто указывает на полное имя класса, а V — это тип [CODE]void[/CODE]. Наши типы включают наш собственный класс [CODE]BugsnagApp[/CODE] и класс [CODE]Application[/CODE] из платформы Android.

    Прототипы

    Прототип метода состоит из информации о типе возвращаемого значения метода и количестве параметров, которые он принимает. Раздел [CODE]proto_id[/CODE] использует индексы для получения информации о типе и смещении, что в данном случае не работает, поскольку наш метод не принимает никаких параметров.

    Методы

    В разделе "Метод" также используются индексы. Каждый метод ищет идентификатор класса, в котором он был определен, прототип метода и имя метода из таблицы строк.

    -- CODE language-opentext --
    00000000 00000000 01000000 00000000
    0, 0, 0, 1, 0, 0

    Landroid/app/Application "V"
    Lcom/bugsnag/dexexample/BugsnagApp; "В"

    Единственные методы в нашем файле Dex относятся к конструктору для [CODE]BugsnagApp[/CODE] — именно этого мы и ожидали.

    Определения классов

    Этот раздел содержит тип, иерархию наследования, метаданные доступа и другие метаданные класса, такие как аннотации и индексы исходного файла.

    -- CODE language-plaintext --
    01000000 01000000 00000000 00000000 FFFFFFFF 00000000 57010000 00000000
    1, 1, 0, 0, NO_INDEX,0, 343, 0
    < /блочная цитата>

    Это оценивается как [CODE]общедоступный класс[/CODE] [CODE]Lcom/bugsnag/dexexample/BugsnagApp[/CODE], который наследуется от [CODE]Landroid/app/Application[/CODE], чьи данные класса хранится из байта 343. Модификатор доступа [CODE]public[/CODE] определяется из битового поля. Давайте просмотрим данные класса.

    Данные класса

    Первые 4 байта данных нашего класса [CODE]BugsnagApp[/CODE] определяют количество статических полей и полей экземпляра, а также любых прямых или виртуальных методов.

    01 81 80 04 CC 01 00 00 00
    1 460

    Если бы наш класс определял поля и другую информацию, в этом разделе было бы закодировано больше данных. Между прочим, если бы идентификатор метода был больше 65 536, мы бы столкнулись с печально известным ограничением метода в 64 КБ.

    Структура кода

    Теперь мы анализируем метод конструктора, определенный в нашем классе, который имеет следующую структуру по смещению [CODE]460[/CODE]:

    -- CODE language-opentext --
    0100 0000 5701 0000 0010, 0000 01000000 64010000
    1, 0, 343, 0, 16, 0 1, 64,1
    >

    Это соответствует размеру регистра 1, 0 входящих аргументов, 343 исходящих аргументов и смещению 16, где хранится отладочная информация.

    Однако самая важная часть — это последние несколько байтов. У нас размер списка инструкций равен 1, что означает, что наш метод скомпилирован в один код операции: [CODE]64010000[/CODE].

    Новый компилятор Android — D8

    Мы особо не затрагивали процесс компиляции, но наш минимальный файл Dex был создан с использованием D8, нового компилятора, который будет развернут по умолчанию в Android Studio 3.1. Он предлагает преимущества в производительности за счет общего размера файла и скорости сборки, так что давайте проверим эти утверждения.

    Сравнение производительности D8

    Давайте создадим новое приложение с помощью Android Studio 3.0.1. Мы добавим поддержку Kotlin и панель навигации, но в остальном оставим все параметры по умолчанию, создадим подписанный APK и просмотрим его с помощью анализатора APK.


    Мы можем получить [CODE]classes.dex[/CODE] из APK, разархивировав APK с помощью [CODE]unzip app-release.apk -d app[/CODE], а затем измерив размер файла в байтах: [CODE]stat -f%z app/classes.dex[/CODE].

    Лучше быстрее меньше сильнее

    Наш файл Dex составляет примерно 88 % от его предыдущего размера при компиляции с помощью D8. Ваш пробег может отличаться, так как это очень простой пример проекта. Еще одна интересная вещь, на которую следует обратить внимание, это то, что при использовании D8 мы потеряли следующие две ссылки на методы:


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

    Почему минимизация делает приложение лучше

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

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

    Обфускация также уменьшит размер файла Dex, поскольку, если вы не относитесь к тем разработчикам, которые называют свои классы [CODE]aaA[/CODE] и [CODE]zzZ[/CODE], для каждого из них потребуется меньше символов. символ, который сэкономит место в целом. Существуют решения для сопоставления запутанных трассировок стека, которые позволяют легко диагностировать сбои в приложении.

    Наконец, меньший файл Dex приводит к меньшему размеру APK, что означает, что пользователи тратят меньше на мобильные данные и с меньшей вероятностью откажутся от загрузки. Если вы предлагаете приложение с мгновенным запуском, жесткое ограничение в 4 МБ означает, что размер APK должен быть небольшим.

    Хотите узнать больше?

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

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


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

    Исполняемые файлы Dalvik — это файлы разработчиков с расширением .dex. Эти файлы DEX используются для инициализации и запуска приложений, разработанных для мобильной ОС Android. Данные, хранящиеся в этих файлах DEX, включают скомпилированный код, который находит и инициализирует другие программные файлы связанного приложения, необходимые для запуска программы. Google разработал формат файла DEX, а ОС Android (также разработанная Google) представляет собой платформу на базе Unix для поддерживаемых мобильных телефонов. Виртуальная машина Dalvik — это прикладная служба, реализованная в ОС Android для интерпретации скомпилированного кода, хранящегося в файле DEX. Разработчики систем на базе Microsoft Windows или платформ Mac могут создавать приложения Android с файлами DEX с помощью программного обеспечения Google Android SDK. Скомпилированные приложения Java также могут быть переведены в программы Android с соответствующими исполняемыми файлами DEX. Несколько файлов DEX хранятся в распространяемом пакете, сохраненном в формате APK, и эти файлы APK являются фактическими приложениями Android.

    Запустите файл .dex или любой другой файл на своем ПК, дважды щелкнув его. Если ваши ассоциации файлов настроены правильно, приложение, предназначенное для открытия вашего файла .dex, откроет его. Возможно, вам потребуется загрузить или приобрести нужное приложение. Также возможно, что на вашем ПК установлено правильное приложение, но файлы .dex еще не связаны с ним. В этом случае, когда вы пытаетесь открыть файл .dex, вы можете сообщить Windows, какое приложение подходит для этого файла. С этого момента при открытии файла .dex будет открываться правильное приложение. Нажмите здесь, чтобы исправить ошибки сопоставления файлов .dex

    Присоединяйтесь к сообществу DZone и получите все возможности участника.

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

    Что такое файл Dex?

    Файл Dex содержит код, который в конечном итоге выполняется средой выполнения Android. Каждый APK имеет один файл class.dex, который ссылается на любые классы или методы, используемые в приложении.По сути, любое действие, объект или фрагмент, используемые в вашей кодовой базе, будут преобразованы в байты в файле Dex, который можно запустить как приложение для Android.

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

    Процесс дедексации

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

    Поскольку большинство мобильных устройств имеют жесткие ограничения по объему памяти, вычислительной мощности и времени автономной работы, ART обеспечивает более высокую производительность, чем JVM. Одной из ключевых особенностей, которая помогает достичь этого, является то, что ART выполняет как предварительную, так и своевременную компиляцию. Это позволяет избежать некоторых накладных расходов JIT во время выполнения, но при этом позволяет повышать производительность по мере профилирования приложения.

    Как создать файл Dex

    Практический пример файла Dex значительно упрощает понимание. Давайте создадим минимальный APK, который содержит только один класс Application, так как это позволяет нам понять формат файла, не перегружаясь тысячами методов, присутствующих в типичном приложении.


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

    Структура файла Dex

    Полная структура нашего 480-байтового файла Dex показана ниже в шестнадцатеричном формате и кодировке UTF-8. Некоторые разделы мгновенно распознаются при интерпретации как UTF-8, например, единственный класс BugsnagApp, который мы определили в нашем исходном коде, а другие не очень:

    На очень высоком уровне файлы Dex можно рассматривать как две отдельные части: заголовок файла, содержащий метаданные, и основную часть, содержащую большую часть данных. Схема структуры заголовка файла показана ниже.


    Давайте последовательно пройдемся по каждому элементу в заголовке.

    Магия файлов Dex

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

    Мы видим, что первые 8 байтов должны содержать «dex», а номер версии — в настоящее время 38, когда наша targetSdkVersion — API 26.

    Возможно, вы также заметили, что четвертый байт кодирует символ новой строки, а восьмой байт имеет значение null. Они проверяются Android Framework на наличие повреждений файлов; APK должен отказаться от установки, если эта точная последовательность отсутствует.

    Контрольная сумма

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

    Подпись SHA1

    Заголовок также включает хеш SHA-1 файла (исключая все предшествующие байты). Это используется для уникальной идентификации файлов Dex, что может быть полезно в таких сценариях, как Multidex.

    Размер файла

    Это соответствует размеру файла в байтах, а также может использоваться для проверки при чтении файла Dex.

    Размер заголовка должен быть 112 байт.


    Поэтому теперь мы можем выделить все оставшиеся поля внутри header_item .

    Постоянный порядок следования байтов

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

    Идентификаторы и смещения

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

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

    Тип Размер Смещение
    link_size 0 0
    map_off Н/Д 356
    string_id 5 112
    type_id 3 132
    proto_id 1 144
    field_id 0 0
    method_id 2 156
    class_defs 1 172
    данные 276 204

    Стоит отметить, что link_size и field_ids равны 0, потому что наше приложение не связывает статически какие-либо библиотеки и не содержит никаких полей. Структура map_off в разделе данных в значительной степени дублирует эту информацию в более простом формате для разбора файла Dex.

    В качестве примера мы можем видеть, что в нашем файле Dex есть пять идентификаторов строк, закодированных между байтами 112-132. Каждый идентификатор в этой позиции также указывает на смещение в разделе данных, которое кодирует фактическое значение строки.

    Список карт

    map_list — это раздел в теле данных, который содержит информацию, аналогичную заголовку файла.


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

    Строки

    Хватит болтать, давайте посмотрим что-нибудь конкретное. Давайте выясним, на что указывает структура string_ids.

    Массив кодирует пять целочисленных смещений, которые указывают на раздел данных.

    Если мы получим эти значения как UTF-8, нас поприветствуют несколько символов Java, которые кажутся знакомыми всем, кто раньше использовал JNI, а также немного JSON, указывающего на то, что D8 создал файл Dex. Весь этот бизнес с идентификаторами, смещениями и несколькими заголовками может показаться немного бесполезным на данный момент. Почему бы просто не закодировать строковое значение прямо в заголовке?

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

    Типы

    Наш файл Dex определяет три типа Java. Каждое значение здесь является индексом в предыдущем массиве string_id. Следовательно, мы можем определить, что типы в нашем файле следующие:

    Синтаксис TypeDescriptor может показаться несколько незнакомым, но L просто указывает на полное имя класса, а V — это тип void . Наши типы включают наш собственный класс BugsnagApp и класс Application из платформы Android.

    Прототипы

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

    Методы

    В разделе "Метод" также используются индексы. Каждый метод ищет идентификатор класса, в котором он был определен, прототип метода и имя метода из таблицы строк.

    Единственные методы в нашем файле Dex относятся к конструктору для BugsnagApp , чего мы и ожидали.

    Определения классов

    Этот раздел содержит тип, иерархию наследования, метаданные доступа и другие метаданные класса, такие как аннотации и индексы исходного файла.

    Это оценивается как общедоступный класс Lcom/bugsnag/dexexample/BugsnagApp , который наследуется от Landroid/app/Application , данные класса которого хранятся в байте 343 . Модификатор общего доступа определяется битовым полем. Давайте просмотрим данные класса.

    Данные класса

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

    В этом классе определен только один прямой метод. Он имеет идентификатор 1, что соответствует Lcom/bugsnag/dexexample/BugsnagApp; "V" и смещение кодовых данных 460 . Если бы наш метод был абстрактным или нативным , смещения данных кода не было бы.

    Если бы наш класс определял поля и другую информацию, в этом разделе было бы закодировано больше данных. Между прочим, если бы идентификатор метода был больше 65 536, мы бы столкнулись с печально известным ограничением метода в 64 КБ.

    Структура кода

    Теперь мы анализируем метод конструктора, определенный в нашем классе, который имеет следующую структуру со смещением 460:

    Это соответствует размеру регистра 1, 0 входящих аргументов, 343 исходящих аргументов и смещению 16, где хранится отладочная информация.

    Однако наиболее важной частью являются последние несколько байтов. У нас размер списка инструкций равен 1, что означает, что наш метод скомпилирован в один код операции: 64010000 .

    В таблице Dalvik Bytecode указано, что 64 соответствует операции sget-byte в регистре с использованием индекса ссылки на поле 1 . Похоже, это соответствует нашим ожиданиям, что для нашего приложения будет создано одноэлементное поле BugsnagApp, но глубокое погружение в Dalvik — это история для другого дня!

    Новый компилятор Android — D8

    Мы особо не затрагивали процесс компиляции, но наш минимальный файл Dex был создан с использованием D8, нового компилятора, который будет развернут по умолчанию в Android Studio 3.1. Он предлагает преимущества в производительности за счет общего размера файла и скорости сборки, так что давайте проверим эти утверждения.

    Сравнение производительности D8

    Давайте создадим новое приложение с помощью Android Studio 3.0.1. Мы добавим поддержку Kotlin и панель навигации, но в противном случае оставим все параметры по умолчанию, создадим подписанный APK и просмотрим его с помощью анализатора APK.


    Мы можем получить class.dex из APK, разархивировав APK с помощью unzip app-release.apk -d app , а затем измерив размер файла в байтах: stat -f%z app/classes.dex .

    Лучше быстрее, меньше сильнее

    Метрика DX D8
    Размер несжатого файла (МБ) 4,23 3,73
    Количество классов 2790 2790
    Количество методов 22038 22038
    Всего ссылок на методы 28653 28651

    Наш файл Dex составляет примерно 88 процентов от его предыдущего размера при компиляции с помощью D8. Ваш пробег может отличаться, так как это очень простой пример проекта. Еще одна интересная вещь, которую стоит отметить, это то, что при использовании D8 мы потеряли следующие две ссылки на методы:


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

    Почему минификация помогает улучшить приложение

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

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

    Обфускация также уменьшит размер файла Dex, поскольку, если вы не относитесь к тем разработчикам, которые называют свои классы a.a.A и z.z.Z , для каждого символа потребуется меньше символов, что в целом сэкономит место. Существуют решения для сопоставления запутанных трассировок стека, которые позволяют легко диагностировать сбои в приложении.

    Наконец, меньший файл Dex приводит к меньшему размеру APK, что означает, что пользователи тратят меньше на мобильные данные и с меньшей вероятностью откажутся от загрузки. Если вы предлагаете приложение с мгновенным запуском, жесткое ограничение в 4 МБ означает, что размер APK должен быть небольшим.

    Хотите узнать больше?

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

    Публикуется на DZone с разрешения Джейми Линча, DZone MVB. Смотрите оригинальную статью здесь.

    Читайте также: