Компиляция и сборка Android приложения (как это работает)

Дисклеймер: Я не 23 летний сеньор (мне 19 и до сеньора мне еще ой как далеко, года 4, поэтому супер статьи от меня не ждите.

Основа пути моего, разработчика как — обучение/изучение нового постоянное. Надеюсь, у вас тоже.


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

Конечно, можно было бы ограничиться инструкцией вроде:
  • Напишите код
  • Нажмите кнопочку Build & Run в вашей IDE
  • Продолжайте быть Android разработчиком

Но мы не ищем легких путей.

Примечание:
Это не перевод http://developer.android.com/tools/building/index.html, это статья, основанная в том числе и на данной документации.


В общих чертах процесс сборки приложения выглядит так:





(Оригинал здесь: developer.android.com/tools/building/index.html)

Нас особенно интересует второй этап (компиляция и сборка ресурсов), так что, рассмотрим его более подробно (21 этап как никак):

Пожалуйста, не прокручивайте диаграмму из-за того, что вам лень вникать, она не сложная, к тому же переведенная. Это основа статьи.

(Оригинал здесь: developer.android.com/tools/building/index.html)

Что есть что на диаграмме:

1. Ресурсы приложения — это все xml ресурсы из папки res вашего проекта + некомпилируемые бинарные ресурсы, например, картинки из res/drawable или файлы из /res/raw, а так же файлы из /assets/

2. aapt — утилита, которая ищет в вашем проекте компилируемые ресурсы, такие как AndroidManifest.xml и xml файлы из res/ и компилирует их в бинарное представление, а изначально бинарные ресурсы, такие как картинки, и файлы из /res/raw и /assets не компилируются. Далее, эта утилита генерирует важнейший класс R.java для вашего приложения, благодаря которому вы можете обращаться к ресурсам из вашего кода без всяких заморочек с чтением файлов, как скажем с /assets.

Кстати, кроме компиляции xml ресурсов, aapt создает файл resources.arsc, который представляет собой таблицу для маппинга ресурсов во время выполнения приложение, туда входят все ресурсы из /res/, в том числе и /res/raw/, содержимое /assets не включается в таблицу.

Полезно знать: Утилита aapt находится в папке platform-tools вашего Android SDK. Если вам захотелось сделать реверс-инжиниринг скомпилированных ресурсов приложения, вы можете воспользоваться apk-tool (https://code.google.com/p/android-apktool/)

3. R.java — класс, генерируемый утилитой aapt для того, чтобы вы могли обращаться к ресурсам из папки res без явной работы с файловой системой через библиотеки ввода/вывода.

Если кто-то еще не знал — всякие R.string, R.menu и прочее — это статические вложенные классы, а в R.string.app_name, app_name — public static final int поле класса. Получается, что правило-принцип CamelCase, применяемый в Java, для них нарушен, должно то быть: R.String, R.Menu, а с константами — R.String.APP_NAME, айайай Google.

4. Исходный код приложения — это ваши (или украденные форкнутые с других проектов) .java файлы с кодом проекта из папки src, все просто.

5. Java интерфейсы — это не те, обычные интерфейсы (обычные входят в состав исходного кода приложения, предыдущий пункт), которые вы используете в вашем коде, это интерфейсы, сгенерированные утилитой aidl (следующий пункт содержит пояснение).

6. .aidl файлы — это интерфейсы, которые вы можете написать на Java с немного необычным синтаксисом (на самом деле, язык называется AIDL — Android Interface Defenition Language). Такие интерфейсы нужны для описания взаимодействия между различными процессами, например когда вам нужно, чтобы сервис (Service) вашего приложения работал не просто в отдельном потоке, а именно в отдельном процессе (у такого подхода есть как преимущества, например, раздельные квоты на память для процессов, так и недостатки — необходимость использования aidl), на Хабре есть статья, в которой раскрыта тема использования aidl http://habrahabr.ru/post/139432/).

7. aidl — утилита, которая транслирует ваши .aidl файлы в Java код, она находится в папке platform-tools вашего Android SDK.

8. Java компилятор(наконец-то мы до него добрались!) это обычный javac из вашего JDK, которому дают на обработку исходный код приложения (4), R.java (3) и интерфейсы aidl, которые переведены в java код с помощью утилиты aidl

9. .class файлы — это «выхлоп» javac — байткод для JVM. Так как Dalvik VM не может интерпретировать java bytecode, а использует свой велосипед под название dex bytecode, то на этом компиляция проекта не заканчивается.

But why?

Разработчики Android выбрали регистровую архитектуру Dalvik VM, вместо привычной для JVM — стековой архитектуры из двух ключевых соображений:
1. Производительность регистровой ВМ выше (инфа 100%), особенно на процессорах с RISC-архитектурой, а это все ARM процессоры. Первые версии Dalviik VM даже не включали JIT (до Android 2.2), но давали терпимую производительность приложений, скажем я не испытывал особых проблем с HTC Desire на 2.1.
2. java bytecode транслируется в меньший по объему dex bytecode, что уменьшает размер скомпилированного приложения.


10. dex компилятор (а точнее транслятор) — он транслирует .class файлы в classes.dex (Java bytecode в Dalvik bytecode). Описание .dex вы можете прочитать здесь source.android.com/tech/dalvik/dex-format.html.

Сам компилятор находится в папке platform-tools вашего Android SDK, запускать его можно через dx.bat (для Windows), при этом будет задействован dx.jar из папки platform-tools/lib

11. Сторонние библиотеки и .class файлы — это все то, что вы подключаете в проект как библиотеку или включаете в Build Path. Так как в качестве основного ЯП для Android выбрана Java, вы можете без проблем использовать практически любые java библиотеки, они просто будут обработаны dex компилятором.

12. classes.dex — в данный файл dex компилятор записывает весь исполняемый код вашего проекта.

Да-да, все будет в одном файле, хотя в документации написано, что файлов .dex может быть несколько, но на практике, я не встречал, чтобы .apk содержал .dex файлы кроме classes.dex, может в комментариях меня поправят.

13. Скомпилированные ресурсы — xml ресурсы приложения, скомпилированные в бинарное представление.

14. Другие ресурсы — это реально другие ресурсы, которые не обрабатываются aapt — например файлы, которые вы зачем то хотите засунуть в .apk, так же туда попадают файлы из .jar`ов, которые добавлены в Build Path, но не являются компилируемыми.

15. apkbuilder — утилита, которой на вход подают скомпилированные ресурсы (2, 13), classes.dex (12) и другие ресурсы (14), а она собирает из этого наш вожделенный .apk файл. Утилита лежит в папке tools вашего Android SDK

16. Собранное приложение в файл .apk — это архив, содержащий скомпилированные и нескомпилированные ресурсы, classes.dex, resources.arsc, META-INF, AndroidManifest.xml и т.д.
Формат .apk это надстройка над .jar, а .jar — надстройка над zip, так что, .apk вы можете открыть zip архиватором, такая вот матрешка.

17. jarsigner — это Oracle`вская утилита для подписания .jar архивов. Он подписывает ваш .apk выбранным вами ключом.

Но не надо думать, что ваш .apk теперь защищен от декомпиляции, ничего подобного. В .apk только добавляется папка META-INF, в которой вы можете обнаружить публичную часть release (или debug) сертификата — файл CERT.RSA, а так же файл CERT.SF — который содержит контрольные суммы для всех файлов внутри .apk, кроме тех, что в папке META-INF

Пример содержания CERT.SF:
  Signature-Version: 1.0
  SHA1-Digest-Manifest-Main-Attributes: O1qITQssq6nv0FUt+eR1aLnqk5w=
  Created-By: 1.6.0_43 (Apple Inc.)
  SHA1-Digest-Manifest: OwzyFA/Qjd+5X1ZwaJQSxFgdciU=
 
  Name: res/drawable-mdpi-v4/ic_premium_pin.png
  SHA1-Digest: 8ksQB8osCHTnMlpL6Ho/GDc719Q=

  Name: res/drawable/round_bottom_white.xml
  SHA1-Digest: rQelve4dQmwCfkVlYZ2+9j5aW5w=

  и так далее


Еще немного важной информации о подписи приложения:
В Android уникальным идентификатором приложения является имя пакета приложения, например ru.habrahabr.android. Но чтобы злоумышленник не смог подменить ваше установленное приложение на свое с таким же пакетом, Android выполняет проверку, на то чтобы новый .apk был подписан тем же сертификатом, что и уже установленный.

Кроме того, если у вас есть выложенное приложение в Google Play, вы не сможете обновить его, если новая версия подписана другим сертификатом! Так что советую забекапить сертификат, а так же не забыть пароль к нему. Иначе вы не сможете обновлять свои приложения.

18. Debug или Release хранилище ключей — хранилище из которого jarsigner возьмет ключи для подписи приложения. Если вы собираете Debug версию (для запуска на эмуляторе или подключенном устройстве), то .apk подписывается debug ключем, в Windows он находится в папке пользователя/.android/.

Кстати, приложение подписанное debug ключом нельзя установить без подключенного отладчика. Так что, если хотите отправить .apk друзьям на тестирование — подпишите его Release ключем

19. Подписанный .apk — .apk файл вашего приложения, в который добавлена информация о подписи (см. пункт 17).

20. zipalign — утилита, которая оптимизирует ваш .apk для более быстрого запуска и меньшего потребления ОЗУ при работе приложения. Она выравнивает содержимое .apk для более эффективной разархивации (подробнее здесь: http://developer.android.com/tools/help/zipalign.html).

Важное замечание: приложение надо сначала подписать, а затем применить zipalign, т.к. подпись — это добавление папки META-INF в архив, а это изменение архива, следовательно нарушение его оптимизации. То есть, если вы сначала примените zipalign, а потом измените архив — смысла в zipalign не будет. Насчет нарушения контрольных сумм, которые рассчитала утилита jarsign, бояться не стоит, т.к. zipalign делает оптимизации по выравниванию данных в архиве, все это происходит на уровне zip, сами данные не изменяются.

Хозяйке на заметку: во время сборки debug версии проекта zipalign не вызывается, скорее всего, чтобы вы не ждали выполнения еще и этой операции (спасибо и на этом).

21. Подписанный (и, возможно, выравненный) .apk — вожделенный .apk вашего приложения. Конец.

Я думаю, что теперь понятно, почему сборка и запуск Android приложения происходит так долго :) Так что, советую поставить SSD, ну и процессор побыстрее и сэкономить себе нервы и время.

Немного полезного оффтопа:


1. Всегда используйте обфускацию вашего кода. Декомпилировать java приложение очень легко. Даже несмотря на то, что в .apk используется dex bytecode — его сначала транслируют обратно в java bytecode, а затем к нему применят обычные java декомпиляторы. Потратьте пару часов на выбор и настройку обфускатора, иначе можете просто выложить исходники проекта на гитхаб, секономите людям время, а может еще и пулл реквесты получите :)

2. Вы знали, что Android инстанциирует для каждого запущенного приложения отдельный экземпляр Dalvik VM? Это сделано для того, чтобы исключить ситуации, когда одно приложение валит Dalvik VM, а за ним тянет все другие запущенные приложения. Яркий пример подобного вмешательства — Facebook, они через reflection изменяют параметры Dalvik VM (не стоит так делать). Если бы Android использовал один инстанс Dalvik — это изменение затронуло бы все запущенные приложения.

3. Dalvik VM не является JVM, т.к. во-первых он не понимает java bytecode, а во-вторых не реализует спецификации для JVM, т.к. использует свой байт код. Так что, советую называть Dalvik VM именно Dalvik VM.

Надеюсь, вы не зря потратили свое время и узнали что-то новое.
Метки:
android, сборка приложения, aapt, aidl, dalvik vm, dex, очень долго собирается