Первая часть, рассказывающая для чего все это нужно здесь.
Содержание
- Подготовка
- Maven
- Root
- Build profiles
- Plugins
- App
- Resource filtering
- Lib
- Test
- Root
- Заключение
- Ссылки
Пост рассчитан на читателей уже знакомых с основами maven’а и в ходе статьи акцент будет делаться на каких-то специфических, именно для андроида, моментах, а не на общих вопросах самого мавена. Если же вы до этого ни разу не работали с мавеном, то для начала можно почитать здесь и здесь.
Так же я не буду рассматривать установку и базовую настройку инструментов — JDK, Android SDK, Maven
и IntelliJ IDEA
должны быть установлены и работать. У вас должны быть настроены соответствующим образом переменные окружения JAVA_HOME, M2, M2_HOME
и ANDROID_HOME
. Так же, для удобства работы, рекомендую добавить в Path
директории %ANDROID_HOME%/tools
и %ANDROID_HOME%/platform-tools
.
Мне всегда удобнее сначала увидеть всю картину целиком, а потом разбираться в отдельных ее деталях. Поэтому предлагаю вам забрать шаблон проекта с github'а. Все дальнейшее повествование будет вестись на его примере.
Подготовка
Прежде чем приступить к рассмотрению самих POM-файлов для начала организуем структуру проекта и настроим его для работы в IntelliJ IDEA. Это даст нам возможность в дальнейшем использовать стандартные средства IDE для запуска и отладки приложений, да и вообще, полноценно пользоваться всеми ее преимуществами. IDEA прямо “из коробки” имеет встроенные плагины для Maven’а и Android’а, и настройка не вызывает почти никаких проблем. Достаточно открыть панель Maven плагина и выбрать соответствующий корневой POM-файл. На основе данных из него, IDEA создаст файлы проекта, которые, возможно немного придется донастроить вручную.
Проект состоит из четырех модулей:
Root |---- App | |---- src | |---- test | |---- JUnit | |---- Robolectric | |---- Lib | |---- src | |---- test | |---- JUnit | |---- Robolectric | |---- Test |---- src |---- Instrumentation |---- Robotium
Root
— корень проектаApp
— само приложениеTest
— instrumentation apk, он же модуль с Android и Robotium тестамиLib
— Android Library Project, собирается в APKLIB
Помимо отдельного модуля с тестами каждый модуль в проекте имеет папку test
, в которой хранятся юнит-тесты (JUnit или Robolectric).
Maven
Для меня одним из стимулов попробовать maven был меньший размер XML-ок по сравнению с Ant'ом, а как следствие, бОльшая читабельность и поддерживаемость. Однако, как это обычно и бывает, то, что в туториале выглядит компактно, на практике разрастается до неузнаваемых размеров. Так и в этот раз, скрипты обросли всеми хотелками и стали ничуть не меньше антовских. Но несмотря на это, по моему субъективному мнению, читать их значительно проще чем Ant. К тому же maven в отличие от скрипто-подобного Ant’a предлагает декларативную парадигму. Т.е. мы описываем не «как» именно мы хотим что то получить, а «что» именно, а как это будет получено нас уже не волнует.
Так же на практике довольно ощутимым плюсом оказалась возможность dependency management’a, хотя в Ant’е она, в принципе, тоже решаема с использованием Ivy. Ну и еще один тычок в сторону муравья, как сказал кто то из известных: “Программировать на XML-е — вообще странная идея” =)
Пройдемся по POM-файлам наших модулей
Root
Корневой модуль проекта. Содержит все остальные модули и общие для всех модулей куски конфигурации. Тип упаковки по умолчанию для таких модулей
<packaging>pom</packaging>
Далее объявляются различные мелочи вроде адреса баг-трекера, адреса репозитория, описание проекта и т.д.
Затем идет блок пропертей:
<properties>
<project.version.name.number>1.0.0</project.version.name.number>
<project.version.code>1</project.version.code>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.emulator.name><!-- TODO emulator name --></project.emulator.name>
<project.version.name>${project.version.name.number}-${project.version.name.qualifier}</project.version.name>
<project.verbosity>true</project.verbosity>
</properties>
- версия и
versionCode
apk-файла - кодировка исходников
- имя эмулятора, на котором будут запускаться тесты и куда будет деплоится готовое приложение. Значение используется дальше в скрипте
android-maven-plugin
'ом. Если конкретное значение не указывать, то по умолчанию будут использоваться все эмуляторы / устройства доступные в данный момент. Так же кроме имени устройства допустимыми значениями являются константыusb
иemulator
. - имя проекта. Версия + квалификатор, значение которого тоже берется из соответствующей property уникальной для каждого конкретного профиля (О профилях подробнее будет ниже)
- флаг -v, который передается многим утилитам из Android SDK, стимулирующий вывод различной дополнительной информации.
Затем блок объявляющий зависимые модули: App, Test и Lib. А дальше секция объявления профилей.
Build profiles
Как отмечалось в предыдущей части, приложение можно собирать в нескольких различных конфигурациях: production, test
и development
. Профили и управляют этим процессом. Каждый профиль позволяет перекрывать значения различных пропертей, настройки плагинов и другие параметры сборки. При компиляции проекта в зависимости от активного профиля будут взяты соответствующие настройки сборки и использованы в приложении. Например, используя процессинг ресурсов, таким образом, можно изменить ссылку на сервер, которая может отличаться для production
и test
конфигураций (Об этом подробнее ниже).
Активировать необходимый профиль можно в IDEA с помощью Maven плагина.
Либо, если POM запускается из консоли или build-сервером, то значение активного профиля можно передать указав дополнительный параметр. Например собрать production
сборку приложения из командной строки можно следующей командой:
mvn install -P production
По умолчанию активен development
профиль, таким образом, если ничего не указывать дополнительно, то будет собрана dev-конфигурация.
Помимо конкретных настроек приложения, вроде параметров подключения к серверу или базе данных, профиль определяет будет ли оптимизироваться и подписываться apk-файл, включен ли debug-режим и какой квалификатор получит конечный артефакт.
test
и development
профили небольшие по размеру и их содержимое задает только настройки приложения (параметры подключения к серверу). Немного больше них по размеру определение production
профиля. Оно включает в себя настройку процесса подписывания приложения сертификатом.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jarsigner-plugin</artifactId>
<executions>
<execution>
<id>signing</id>
<goals>
<goal>sign</goal>
<goal>verify</goal>
</goals>
<phase>package</phase>
<inherited>true</inherited>
<configuration>
<archiveDirectory/>
<includes>
<include>${project.build.directory}/*.apk</include>
</includes>
<keystore>${project.basedir}/keystore</keystore> <!-- TODO add keystore to project -->
<storepass><!-- TODO --></storepass>
<keypass><!-- TODO --></keypass>
<alias><!-- TODO --></alias>
<removeExistingSignatures>true</removeExistingSignatures>
<verbose>true</verbose>
</configuration>
</execution>
</executions>
</plugin>
В настройках maven-jarsigner-plugin
указывается в какой момент будет происходить подписывание пакета, путь к нашему keystore и его параметры. Кстати для отладки скрипта очень удобно запускать его с параметром -X
, например, mvn install -X
. При этом maven выдает большое количество отладочной информации, анализируя которую можно разобраться с проблемой.
Далее в android-maven-plugin
’e отключается debug-режим для production
сборки. И последний штрих — maven-compiler-plugin
включает оптимизации кода production
версии.
В build-секции POM’а задается имя результирующего apk-файла и настраиваются различные плагины.
Plugins
Затем идут настройки плагинов. Плагин для обработки ресурсов, указание maven-compiler-plugin
’у использовать 6-ую версию Java, настройка самого android-maven-plugin
’а и настройка maven-idea-plugin
’а.
maven-idea-plugin
позволяет скачивать документацию и исходники зависимостей проекта, что дает нам возможность заглянуть внутрь исходных кодов платформы или библиотеки или быстро ознакомиться с javadoc’ом (Ctrl+Q). Мелочь, а приятно! =)
Android-maven-plugin
Рассмотрим более подробно настройку андроид плагина. Плагин настраивается так же как и все другие maven-плагины в секции <plugins>/<plugin>/<configuration>
.
Задаем версию API которой будет компилироваться проект.
<sdk>
<platform>14</platform>
</sdk>
Далее параметры манифеста, которые мы задаем в пропертях, уникальных для каждого из профилей (см выше).
<manifest>
<versionName>${project.version.name}</versionName>
<versionCode>${project.version.code}</versionCode>
<debuggable>${project.debug.mode}</debuggable>
</manifest>
Настройки эмулятора: имя, таймаут ожидания запуска и другие опции. Т.к. скрипт используется TeamCity для сборки с последующим запуском тестов на эмуляторе в «безголовом» режиме, то указана опция -no-window
.
<emulator>
<avd>${project.emulator.name}</avd>
<wait>300000</wait><!-- 5 min -->
<options>-no-window</options>
</emulator>
Указываем плагину каждый раз удалять приложение прежде чем устанавливать заново.
<undeployBeforeDeploy>true</undeployBeforeDeploy>
Далее задаются параметры выравнивания финального apk-файла и задается имя конечного файла, которое будет состоять из номера версии с добавлением суффикса -signed-aligned
<zipalign>
<skip>false</skip>
<verbose>${project.verbosity}</verbose>
<outputApk>${project.build.directory}/${project.build.finalName}-signed-aligned.apk</outputApk>
</zipalign>
Затем в секции <executions>
задаются цели когда необходимо обработать манифест и выровнять приложение.
<executions>
<execution>
<id>zipalign</id>
<phase>package</phase>
<goals>
<goal>zipalign</goal>
</goals>
</execution>
<execution>
<id>update-manifest</id>
<goals>
<goal>manifest-update</goal>
</goals>
</execution>
</executions>
Оставшиеся POM-файлы модулей App, Test и Lib менее интересны, пройдемся вкратце по ним.
App
Как обычно вначале идут настройки артефакта собираемого модулем и указание родительского модуля. Указывается тип упаковки — APK
. Секция с репозиториями для зависимостей отсутствующих в maven-central, например, сюда можно добавить ваш корпоративный репозиторий, если такой есть.
Кстати в maven-central отсутствуют многие версии Android платформы и часто используемые библиотеки из SDK. Например, карты, compatibility package, AdMob SDK и т.д. А если и появляются то с большой задержкой. Для упрощения жизни разработчикам и в этом вопросе Manfried Moser, автор maven-android-plugin
'a, разработал еще один полезный проект — Maven Android SDK Deployer, который позволяет одной командой залить все необходимые артефакты в ваш личный репозиторий и уже от туда их смело использовать.
Дальше список необходимых проекту зависимостей. В качестве одной из зависимостей указывается наш модуль Lib
<dependency>
<groupId>com.devoxy.android</groupId>
<artifactId>template-project-lib</artifactId>
<version>1.0.0</version>
<type>apklib</type>
</dependency>
Обратите внимание на тип упаковки apklib
— тип для Android библиотек, содержащих ресурсы. В случае IntelliJ IDEA на каждую зависимость apklib автоматически будет создан IDEA-модуль имя которого будет начинаться с ~
.
Однако, с apklib-зависимостями все не так гладко. У меня к сожалению так и не получилось заставить работать в IDEA проект с 2-мя и больше apklib-ами. При сборке maven-ом такой проект собирается хорошо, но при сборке через IDEA полученный apk файл получается неработоспособным. Более детально проблема описана в багтрекере JetBrains и на stackoverflow. В качестве workaround'а можно создать maven конфигурацию как на рисунке.
Но существенный недостаток такого решения в том, что maven каждый раз пересобирает все исходники и ресурсы, не смотря на то, вносили ли мы в них изменения или нет, что делает сборку проекта длительной операцией и существенно тормозит процесс разработки в целом. Будем надеяться что JetBrains в скором времени исправят ошибку.
В секции <build>
указываем директории с исходниками и тестами.
<sourceDirectory>src</sourceDirectory>
<testSourceDirectory>test</testSourceDirectory>
И настраиваем процессинг манифеста и других ресурсов приложения.
Resource filtering
В файле, хранящем настройки приложения (в моем примере это application.properties
в /assets
, но это может быть любая XML-ка из /res
или вообще java файл из /src
), можно задавать проперти в виде ${property.name}
значение которой на этапе обработки ресурсов, Maven’ом будет заменено на значение проперти из нашего POM-файла. Таким образом, задавая различные значения для одних и тех же пропертей в различных конфигурациях и фильтруя ресурсы, можно добиться необходимого эффекта. Например, задать параметры подключения к серверу.
Так же фильтрация ресурсов здесь позволяет обойти еще одну небольшую проблему. android-maven-plugin
умеет изменять некоторые атрибуты в манифесте нашего приложения в частности versionName
и versionCode
, что нам и нужно. Но для этого плагин использует встроенный парсер и после любого изменения автоматически переформатирует манифест так как ему нужно, а не как удобно нам, убивая начисто многие пробелы и переносы строк, что превращает AndroidManifest.xml
в нечитаемую кашу. Что бы избежать этого эффекта и сохранить читаемость оригинального манифеста так же используется resource filtering. Во время процессинга создается копия манифеста и уже в ней происходят замены и переформатирование.
Сначала, все что касается обработки ресурсов у меня хранилось в Root модуле, но небольшой баг в плагине не позволяет использовать эту хитрость в APKLIB
модулях. Поэтому данные настройки были вынесены из Root в App модуль, дабы избежать лишних проблем.
Задаем путь к директории /asstets
.
<assetsDirectory>${project.build.directory}/filtered-assets</assetsDirectory>
Так как мы используем обработку ресурсов, то этот путь у нас отличается от пути по умолчанию и мы должны явно указать папку с готовыми ресурсами (см. часть про Resource filtering).
По той же причине указывается путь к отфильтрованному манифесту, отличный от пути по умолчанию.
<androidManifestFile>${project.build.directory}/AndroidManifest.xml</androidManifestFile>
Lib
Тип упаковки APKLIB
. Вообще здесь можно указать и jar
. Зависит от того что будет внутри вашей библиотеки. Если библиотека содержит только java-код, то хватит и jar, если же помимо кода библиотека предоставляет и ресурсы (лейауты, активити, стили и т.д.), т.е. представляет из себя Android Library Project, то ставим APKLIB
.
Далее как обычно ссылка на родительский модуль, список зависимостей и пути к папкам с исходниками и тестами.
Test
Последний модуль Тест, содержит набор интеграционных и функциональных тестов. Тип упаковки apk
. Секции аналогичны предыдущим модулям. Единственное, что в качестве зависимостей необходимо указать артефакты модуля приложения.
<dependency>
<groupId>com.devoxy.android</groupId>
<artifactId>template-project-app</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
<type>apk</type>
</dependency>
<dependency>
<groupId>com.devoxy.android</groupId>
<artifactId>template-project-app</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
<type>jar</type>
</dependency>
Заключение
Изначально я собирался хранить номер версии в виде Х.Х.Х
в качестве проперти в билд-скрипте, но, к сожалению, в случае проекта из нескольких модулей это невозможно. Maven не умеет резолвить проперти в заголовках версии артефакта. Поэтому был необходим способ менять номер версии во всех pom-файлах и в AndroidManifest.xml
. По хорошему для этого нужно использовать maven-release-plugin
или какие-нибудь костыли (ant-скрипт как у меня).
Так как мне необходимо было быстро найти работающее решение, а времени разбираться с релиз плагином не было, то я сделал временно на костылях, а как известно — нет ничего более постоянного чем временное =)
Номер версии проекта лежит в корне в файле version.properties
. Единственный ant-target выполняет обновление всех POM-файлов в соответствии с версией в файле.
Кроме этого хотелось бы сделать более гибкий механизм для запуска различных test-scopes в разных конфигурациях. Android Testing Framework предоставляет несколько различных аннотаций для ваших тестов @SmallTest
@MediumTest
и @LargeTest
, с помощью которых можно разделить все ваши instrumentation-тесты на несколько логических групп. Т.к. запуск и прогон integration-тестов, особенно на эмуляторе, очень долгая операция, то хотелось бы при сборке на TeamCity управлять тем, какие именно тесты и в какой сборке запускать. Например, development
сборка может включать только @SmallTest
’ы, а production
все вместе. Но, к сожалению, maven-android-plugin
позволяет указывать только один scope при выборе тестов, т.е. мы можем выбрать либо small
, либо medium
, либо large
, но не small и medium. См. testSize
Ну и по желанию можно добавить obfuscating с помощью ProGuard
Ссылки
И, как обычно, в конце немного ссылок на тему сборки Android проектов Maven'ом
- Maven Tutorial
- Android-maven-plugin: Getting Started Plugin Doc
- Maven: The Complete Reference from Sonatype. Глава про Android
- Maven Android Archetypes Готовый набор андроид архитайпов
- Android-maven-plugin Samples. Содержит кучу примеров, включая NDK и библиотеки
- Gaug.es Типичное приложение типа «веб-клиент», сделано очень качественно, можно посмотреть не только настройки maven'a
- GitHub for Android Ну и официальное приложение клиент для GitHub, собирается тоже maven'ом
Автор: TheDimasig