Обычно у разработчика есть свой любимый инструмент, которым ему пользоваться удобнее, чем другими. Однако бывает так, что платформа заставляет разработчиков брать в руки инструмент, который не так удобен, как ему хотелось бы, или просто чем-то не устраивает. Так получилось, что традиционно приложения под Android пишут при помощи Eclipse, поскольку Google приняли решение о том, что будут разрабатывать официальный плагин, ADT, именно для этого редактора. В результате тем разработчикам, которые им не пользовались, волей-неволей пришлось его освоить.
К счастью, Google также предоставляют систему сборки, которая работает независимо от имеющегося в наличии IDE. А это означает, что можно настроить любой редактор для работы с приложениями Android. Лично я предпочитаю писать код на Java в NetBeans IDE и хочу поведать о том, как его можно настроить для этого. Есть такие плагины, как nbandroid, но разрабатывается он нерегулярно, энтузиастами, так что есть смысл воспользоваться гибкостью NetBeans и задействовать официальную систему сборки напрямую из редактора.
Создание нового проекта
При создании нового проекта, к сожалению, придётся сделать больше действий, чем можно было бы сделать в Eclipse, но это нужно сделать всего лишь один раз. Создание проекта делается в три шага:
- Создание файлов для сборки из командной строки;
- Создание проекта в IDE;
- Добавление дополнительных команд (по вкусу).
Создание файлов для сборки
Прежде всего, необходимо создать новый проект через командную строку. Я буду исходить из предположения, что в PATH
уже затесалась папка <Android-SDK>/tools
. Проект создаётся следующей командой:
android create project -n <имя проекта> -t android-<уровень API> -p <путь к проекту> -k <пакет программы> -a <название основной активности>
На всякий случай поясню, что уровень API — это тот самый, с помощью которого мы будем компилировать проект. То есть если проект в целом рассчитан на уровень API 10, но есть некоторые возможности, которые используются только на аппаратах с уровнем 15 и выше, то нужно именно 15 и выставить. В AndroidManifest.xml
, кстати, эта 15 не засветится, там будет только 10 как минимально необходимый уровень API.
Предположим, что наш проект создаётся для Android 4.0.3 (это уровень 15) и называется KillerApp. Тогда нужно будет ввести следующее:
android create project -n KillerApp -t android-15 -p KillerApp -k com.damageinc.killerapp -a MainActivity
После этой команды в папке проекта завелись все нужные нам файлы: конфигурационные файлы и, самое главное, файл сборки. На этом работа с командной строкой закончена, и мы больше её не увидим. Теперь осталось поколдовать в IDE.
Создание проекта в IDE
- На экране создания нового проекта в NetBeans понадобится пункт Java Free-Form Project, с помощью которого мы растолкуем IDE, где брать файл сборки.
- Дальше нужно выбрать папку проекта. NetBeans сам найдет файл сборки и сообразит, как называется проект, так что после выбора папки можно спокойно идти на следующий экран.
- А вот теперь надо правильно прописать задания сборки, чтобы IDE знал, что запускать, когда мы заходим собрать проект. Сборка в системе осуществляется заданием debug. Это немного странно выглядит, но причина такого названия очень простая: это задание создаёт сборку для отладки, подписанную соответствующим сертификатом. Соответственно, есть задание release, до которого мы ещё доберёмся. Запуск проекта в нашем случае означает сборку и установку, что означает выполнение задания install после сборки. Там ещё приписано задание
launch
, которого в стандартной системе нет, но мы его сделаем и сами. Очистка — это, вполне ожидаемо, clean, а тестировать в Android нужно через отдельный, тестовый проект, поэтому то, что находится в том поле, можно смело стирать. - На следующем экране нужно добавить папку
gen
к папкам исходников, потому что именно в этой папке будет находится файлR.java
. - Теперь настраиваем подсказки по коду. Прежде всего, важно снять флажок разделения папок с исходниками, иначе IDE будет думать, что файл
R
не должен упоминаться в коде нашей программы, поскольку лежит в отдельной папке. Также нужно добавить правильную платформу Android в список библиотек, в нашем случае это<Android-SDK>/platforms/android-15/android.jar
. - И, наконец, последний шаг, добавляем папку
bin/classes
, чтобы IDE знал, где искать скомпилированный код. В принципе, этот шаг не обязателен, и на него можно смело наплевать. Но для полноты картины я сделаю и его, чтобы NetBeans показывал, какие файлы были недавно изменены и ещё не скомпилированы.
Добавление дополнительных команд
На самом деле, после последнего шага можно больше ничего не делать. Проект создан и уже нормально собирается. Но можно пойти дальше и наделать много удобств. В папку проекта стоит положить файл custom_rules.xml
, а в нём записать
<project name="CustomRules">
<target name="release-and-save" depends="release">
<xpath
input="AndroidManifest.xml"
expression="/manifest/@android:versionName"
output="manifest.versionName"
default="test"/>
<xpath
input="AndroidManifest.xml"
expression="/manifest/@android:versionCode"
output="manifest.versionCode"
default="test"/>
<copy
file="${out.final.file}"
tofile="releases/${ant.project.name}-release${manifest.versionCode}-${manifest.versionName}.apk"
overwrite="true"/>
<copy
file="${obfuscate.absolute.dir}/mapping.txt"
tofile="releases/mapping-release${manifest.versionCode}.txt"
overwrite="true"/>
</target>
<target name="rebuild-resources" depends="-set-debug-mode, -build-setup, -code-gen" />
<target name="-find-main-activity">
<xpath
input="AndroidManifest.xml"
expression="/manifest/@package"
output="project.app.package"
default="test"/>
<xpath
input="AndroidManifest.xml"
expression="/manifest/application/activity[intent-filter/category/@android:name = 'android.intent.category.LAUNCHER'][1]/@android:name"
output="project.app.mainactivity"
default="test"/>
<if>
<condition>
<matches pattern="..+|[^.].*..*[^.]" string="${project.app.mainactivity}"/>
</condition>
<then>
<property name="project.app.mainactivity.qualified" value="${project.app.mainactivity}"/>
</then>
<else>
<property name="project.app.mainactivity.qualified" value=".${project.app.mainactivity}"/>
</else>
</if>
<property name="project.app.launcharg" value="-a android.intent.action.MAIN -n ${project.app.package}/${project.app.mainactivity.qualified}"/>
</target>
<target name="launch" depends="-find-main-activity">
<exec executable="adb">
<arg line="shell am start"/>
<arg line="${project.app.launcharg}"/>
</exec>
</target>
</project>
Эти три задания нам позволяют делать кое-какие весьма полезные вещи. rebuild-resources
позволяет сгенерировать файл R
(который в Eclipse, кстати, нередко куда-то исчезает или не обновляется вовремя). launch
даст нам возможность запускать приложения, а release-and-save
позаботится о том, чтобы при сборке финальной версии она сохранилась в отдельной папке под соответствующим именем вместе с картой методов ProGuard. Ещё я люблю добавлять такие строчки, чтобы после сборки проигрывалось уведомление:
<target name="-post-build">
<sound>
<success source="C:WindowsMediaWindows Notify.wav"/>
</sound>
</target>
Разумеется, этот конкретный вариант звука годится только для Windows, для других ОС стоит выбрать другой файл. Теперь осталось добавить эти задания в контекстное меню в NetBeans. В свойствах проекта на вкладке Build and Run мы уже заранее доработали пункт Run Project заданием launch
в конце. В пользовательские элементы контекстного меню я добавил остальные только что сделанные задания:
Теперь в конекстном меню проекта весь букет необходимых нам команд. Можно запустить эмулятор или подключить смартфон, запустить Run и смотреть, как всё собирается и само запускается. Осталось сделать последние штрихи и включить обфускацию при сборке финальной версии, раскомментировав строчку в файле project.properties
:
proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
Также в ant.properties
стоит прописать строчки для подписи финальных сборок:
key.store = <путь к файлу ключей>
key.alias = <название ключа>
Вот теперь у нас проект готов к работе.
Как работает система сборки в Android
На самом деле, системы сборки в Android на данный момент сейчас уже две: одна основана на ant, а другая — на Gradle. В данном конкретном случае мы пользуемся системой ant. Система с Gradle разрабатывается параллельно с новым IDE, идущим на замену Eclipse — Android Studio. Эта система сборки, думаю, пригодится для отдельной статьи.
Возвращаясь к ant, в папке проекта лежат следующие файлы:
- ant.properties
- build.xml
- local.properties
- proguard-project.txt
- project.properties
Самый важный файл здесь — это, разумеется, build.xml
. На самом деле, это лишь маленький хвостик основной системы, и всё, что он делает — это загрузка свойства из файлов с расширением properties и вызов основной системы, располагающейся в самом SDK.
local.properties
содержит всего лишь одно свойство: расположение папки с SDK. Этот файл нужно занести в список исключений системы контроля версий, потому что он содержит настройки, специфические для отдельной машины. Например, у меня под Windows этот файл содержит строчку
sdk.dir=C:\Android-SDK
На самом деле, можно создать переменную окружения ANDROID_HOME
с содержимым переменной из этого файла и благополучно отправить файл в мусорку. Главное, не забудьте после этого перезапустить NetBeans.
С ant.properties
мы уже познакомились, там хранятся вспомогательные переменные вроде расположения хранилища ключей. Также есть файл project.properties
. После действий, описанных в создании проекта, там есть только строчки об уровне API Android, для которого собирается проект, и о том, где искать файл конфигурации ProGuard. Когда мы будем добавлять в проект библиотеки, строчки о них окажутся там же.
Наконец, файл proguard-project.txt
, который, как следует из названия, содержит указания ProGuard. Он изначально пуст, но это вовсе не значит, что ProGuard будет работать вхолостую, поскольку в папке SDK уже есть заранее записанная конфигурация (помните раскомментированную строчку о ProGuard?), а здесь мы можем её конкретизировать. Например, я лично люблю, помимо прочих, добавлять строчки
-renamesourcefileattribute MyProject
-keepattributes SourceFile,LineNumberTable
Они в дальнейшем помогают собирать отчёты об ошибках, поскольку теперь есть точные данные о том, какая строчка кода в проекте вызывает падение или исключение.
Также build.xml
загружает файл custom_rules.xml
, если он есть, в котором мы и добавили все необходимые нам задания. Стоит взглянуть на задания ещё раз.
<target name="rebuild-resources" depends="-set-debug-mode, -build-setup, -code-gen" />
Эти строчки взяты просто из системы сборки SDK. К сожалению, там они не вынесены в отдельное задание, поэтому пришлось делать это самостоятельно. Интереснее взглянуть на другие два задания:
<target name="release-and-save" depends="release">
<xpath
input="AndroidManifest.xml"
expression="/manifest/@android:versionName"
output="manifest.versionName"
default="test"/>
<xpath
input="AndroidManifest.xml"
expression="/manifest/@android:versionCode"
output="manifest.versionCode"
default="test"/>
<copy
file="${out.final.file}"
tofile="releases/${ant.project.name}-release${manifest.versionCode}-${manifest.versionName}.apk"
overwrite="true"/>
<copy
file="${obfuscate.absolute.dir}/mapping.txt"
tofile="releases/mapping-release${manifest.versionCode}.txt"
overwrite="true"/>
</target>
С помощью XPath здесь достаются параметры версии и дальше с их помощью генерируются имена файлов. В обычном ant поддержки XPath нет, так откуда же он тут взялся? В Android SDK Google добавили свои собственные инструменты для того, чтобы работать с приложениями было удобнее. Многие из их инструментов сводятся к запуску определённых файлов из SDK, но есть и те, которые облегчают написание файлов сборки, такие как xpath
. Ещё одним полезным инструментом, например, является if
, делающий именно то, что делает соответствующая конструкция в языках программирования: выполнение того или иного задания в зависимости от условия.
Второе задание тоже использует xpath
, на этот раз задача немного сложнее:
<target name="-find-main-activity">
<xpath
input="AndroidManifest.xml"
expression="/manifest/@package"
output="project.app.package"
default="test"/>
<xpath
input="AndroidManifest.xml"
expression="/manifest/application/activity[intent-filter/category/@android:name = 'android.intent.category.LAUNCHER'][1]/@android:name"
output="project.app.mainactivity"
default="test"/>
<if>
<condition>
<matches pattern="..+|[^.].*..*[^.]" string="${project.app.mainactivity}"/>
</condition>
<then>
<property name="project.app.mainactivity.qualified" value="${project.app.mainactivity}"/>
</then>
<else>
<property name="project.app.mainactivity.qualified" value=".${project.app.mainactivity}"/>
</else>
</if>
<property name="project.app.launcharg" value="-a android.intent.action.MAIN -n ${project.app.package}/${project.app.mainactivity.qualified}"/>
</target>
<target name="launch" depends="-find-main-activity">
<exec executable="adb">
<arg line="shell am start"/>
<arg line="${project.app.launcharg}"/>
</exec>
</target>
Необходимо найти активность, которая определяет в своем фильтре намерений категорию android.intent.category.LAUNCHER
— именно так в Android определяются активности, которые должны показываться в меню. Их может быть и несколько (хотя это бывает редко), поэтому задание берёт первую из них.
Есть и ещё одна загвоздка. Активности декларируются в AndroidManifest.xml
либо записью с полным именем, либо только именем класса с точкой впереди, если активность лежит в главном пакете. По крайней мере, так говорит документация. Только вот проблема в том, что Eclipse и прочий инструментарий позволяет точку опускать и просто писать имя активности, когда она находится в главном пакете. Android-то такое попустительство терпит, а вот команда, запускающая приложения, уже нет. Приходится добавлять точку, когда её не хватает. Вот тут нам и поможет задание if
, недавно мной упомянутое, и регулярные выражения.
Также было ещё совсем простое задание, которое воспроизводит звук. В нём был использован один из шести хуков, которые дёргаются из файла сборки SDK:
- -pre-build
- -pre-compile
- -post-compile
- -post-package
- -post-build
- -pre-clean
Хуки отражают этапы, через которые проходит сборка программы: компиляция библиотек, генерирование кода (RenderScript, aidl, R, BuildConfig), компиляция проекта, упаковкой APK, подпись и zipalign. Соответственно, -pre-build
вызывается прежде, чем начнутся все эти действия, -pre-compile
непосредственно перед компиляцией самого проекта, -post-compile
— между компиляцией и упаковкой, -post-build
— после упаковки, но до подписи, и -post-build
вызывается в самом конце. Ну а когда вызывается -pre-clean
, думаю, понятно из названия.
Вместо заключения
Сегодня мы посмотрели самое важное: создание проекта. Собственно, уже после этого проект вполне рабочий, можно садиться и строчить код. Но нам нужно будет добавлять в проект библиотеки, отлаживать, а также создавать тесты. Все эти действия тоже отлично получаются в NetBeans. Как это можно сделать, я опишу в следующей части статьи.
Автор: DrMetallius