Pull to refresh

Пишем, собираем и запускаем HelloWorld для Android в блокноте

Reading time 10 min
Views 128K


Когда я начал изучать Android, захотелось полностью написать и скомпилировать Android-приложение вручную — без использования IDE. Однако эта задача оказалась непростой и заняла у меня довольно много времени. Но как оказалось — такой подход принёс большую пользу и прояснил многие тонкости, которые скрывают IDE.

По-сути эта статья является переводом и переработкой статьи Building Android programs on the command line под современные на данный момент JDK (7) и Android SDK (API level 19). Кроме того, я многое упростил и добавил кое-что от себя.

Используя только блокнот, мы напишем совсем маленькое учебное Android-приложение. А затем скомпилируем его, соберём и запустим на устройстве — и всё через командную строку. Заинтересовало? Тогда прошу.

Вступление


Я был поражён, насколько сложным и запутанным является шаблонное приложение в android studio. Оно просто нагромождено ресурсами. И в меньшей степени — кодом и скриптами. Хотя всё что оно должно делать — это выводить на экран HelloWorld! Кроме того, в книгах и руководствах, которые я просмотрел, объясняется, как с помощью диалоговых окон создать IDEA-шный или эклипсовый HelloWorld — и от него уже идёт дальнейшее повествование. А что происходит «под капотом» — остаётся только гадать.

Долгие поиски были почти безрезультатны — на русском языке статей по данной теме я не нашёл, а на английском — была устаревшая информация и кое-что пришлось дорабатывать напильником, гуляя по stackowerflow и документации. Когда я разобрался, то решил восполнить пробел — вдруг кому-то пригодится? Собственно, так и появилась эта статья.

Мы создадим свой шаблонный проект, который идеально использовать для обучения. Там не будет ничего лишнего, только всё самое необходимое. А потом детально разберём, как его собрать и запустить на вашем Android-устройстве. В конце статьи будет ссылка на скачивание архива с итоговым проектом — если возникнут какие-то вопросы — можете свериться с ним.

Таким образом, вы будете на 100% знать и понимать состав вашего проекта и процесс его сборки. Хотя этот тестовый проект предназначен для обучения, при небольшой доработке его можно будет использовать как прочный фундамент для ваших реальных проектов.

Подготовка


Для начала у вас должен быть установлен JDK 7 и android SDK. Последний кстати нужно чуточку настроить после установки.

Главное требование перед прочтением этой статьи — кроме установленного софта вы должны уже уметь запускать на вашем девайсе тот Helloworld, который поставляется вместе с Eclipse или Android Studio. Т.е. у вас должен быть настроен драйвер usb, включена отладка по usb на вашем девайсе и т.д… Или же создан и настроен эмулятор. Это совсем элементарные вещи, и их рассмотрение выходит за рамки данной статьи — в сети достаточно информации. Кстати прочитать пару глав из книг тоже будет не лишним — хотя бы понимать, как устроен манифест, ресурсы, да и вообще основы языка Java. А в этой статье я опишу то, о чём книги молчат.

Написание проекта


Для начала, создайте некоторую папку, где будет ваш проект. Назовём её mytestapp. В ней создайте ещё 4 папки — bin,obj,res и src.

Создайте пустой текстовый файл и измените его имя на AndroidManifest.xml. Если вы начинающий пользователь Windows, вам нужно включить показ расширений, иначе файл будет по-прежнему текстовым.

Добавьте в него следующее:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.testapp">
    <uses-sdk android:targetSdkVersion="19" />

    <application android:label="TestApp">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Тут всё просто. Мы намерены сделать приложение с именем TestApp, которое при старте запускает класс MainActivity. Осталось только написать этот небольшой класс — и приложение готово. Если нужно — отредактируйте в теге uses-sdk свойство android:targetSdkVersion — поставьте ту версию, которая у вас.

Далее — создадим простейший ресурс — строку Hello test app. Вообще-то мы могли обойтись и без ресурса, вставив эту строку прямо в Java код. Но некоторые шаги сборки работают с ресурсами, и чтобы увидеть интересные моменты — мы всё-таки поработаем с ними.

Давайте создадим в папке res папку values. Все ресурсы следует разбивать по папкам. Далее — в ней создадим пустой файл strings.xml. а в нём напишем
<resources>
    <string name="hello">Hello test app!</string>
</resources>

Вот и все ресурсы, нам необходимые. Просто, не так ли? Далее создадим внутри src папку com, в ней папку example, потом ещё ниже по иерархии папку testapp — а там уже наш класс MainActivity.java. Добавим туда код
package com.example.testapp;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends Activity {

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    TextView textView = new TextView(this);
    textView.setText(getString(R.string.hello));
    setContentView(textView);
  }
}

Структура каталогов должна получится такая
│   AndroidManifest.xml
├───bin
├───obj
├───res
│   └───values
│           strings.xml
│
└───src
    └───com
        └───example
            └───testapp
                    MainActivity.java

И это собственно всё, что нам было нужно для простейшего проекта. Для сравнения —
HelloWorld от android studio
│   .gitignore
│   AndroidStudioHWProject.iml
│   build.gradle
│   gradlew
│   gradlew.bat
│   local.properties
│   settings.gradle
│
├───.gradle
│   └───1.8
│       └───taskArtifacts
│               cache.properties
│               cache.properties.lock
│               fileHashes.bin
│               fileSnapshots.bin
│               outputFileStates.bin
│               taskArtifacts.bin
│
├───.idea
│   │   .name
│   │   compiler.xml
│   │   encodings.xml
│   │   gradle.xml
│   │   misc.xml
│   │   modules.xml
│   │   vcs.xml
│   │   workspace.xml
│   │
│   ├───copyright
│   │       profiles_settings.xml
│   │
│   ├───libraries
│   │       ComAndroidSupportAppcompatV71901_aar.xml
│   │       support_v4_19_0_1.xml
│   │
│   └───scopes
│           scope_settings.xml
│
├───AndroidStudioHW
│   │   .gitignore
│   │   AndroidStudioHW.iml
│   │   build.gradle
│   │
│   ├───build
│   └───src
│       └───main
│           │   AndroidManifest.xml
│           │
│           ├───java
│           │   └───com
│           │       └───example
│           │           └───androidstudiohw
│           │                   MainActivity.java
│           │
│           └───res
│               ├───drawable-hdpi
│               │       ic_launcher.png
│               │
│               ├───drawable-mdpi
│               │       ic_launcher.png
│               │
│               ├───drawable-xhdpi
│               │       ic_launcher.png
│               │
│               ├───drawable-xxhdpi
│               │       ic_launcher.png
│               │
│               ├───layout
│               │       activity_main.xml
│               │       fragment_main.xml
│               │
│               ├───menu
│               │       main.xml
│               │
│               ├───values
│               │       dimens.xml
│               │       strings.xml
│               │       styles.xml
│               │
│               └───values-w820dp
│                       dimens.xml
│
└───gradle
    └───wrapper
            gradle-wrapper.jar
            gradle-wrapper.properties

Собственно, автоматизация через gradle, работа с git и IDE — вещи очень важные, однако на этапе изучения Android мне бы очень хотелось от них абстрагироваться.

Сборка


Теперь же подходим к самому важному и сложному этапу. Мы будем много работать с командной строкой, поэтому рекомендую вам все команды, данные здесь, записывать в один файл и назвать его comp.bat. В конце файла после команд можете добавить pause, чтобы был виден результат и ошибки — если таковые возникнут.

Подготовка путей

Первое, что мы сделаем для удобства и краткости — создадим специальные переменные, в которых будем хранить пути. Для начала — определим наши основные директории. Вам нужно заменить пути к JDK и Android SDK на те, которые у вас.
set JAVA_HOME=C:\prog\sdk\jdk\1.7
set ANDROID_HOME=C:\prog\sdk\android-studio\sdk
set DEV_HOME=%CD%

Далее — пути непосредственно к программам. Я рекомендую вам просмотреть каталоги ваших SDK и убедится в том, что всё на месте. Также подкорректировать версии, которые присутствуют в путях.
set AAPT_PATH=%ANDROID_HOME%/build-tools/19.0.1/aapt.exe
set DX_PATH=%ANDROID_HOME%/build-tools/19.0.1/dx.bat
set ANDROID_JAR=%ANDROID_HOME%/platforms/android-19/android.jar
set ADB=%ANDROID_HOME%/platform-tools/adb.exe

Между прочим, в более старых версиях утилита aapt находилась в platform-tools — и я не исключаю что она и\или другие могут слинять куда-нибудь ещё. Так что будьте внимательны. Если вы всё правильно сверите сейчас — то остальная часть статьи должна пройти гладко.

И ещё — в пару переменных забьём наши пакеты и классы. Если заходите их сменить — вам не придётся бегать по коду — все настройки вначале.
set PACKAGE_PATH=com/example/testapp
set PACKAGE=com.example.testapp
set MAIN_CLASS=MainActivity

Подготовка к компиляции

А теперь самое интересное. Помните, как классические Java-программы компилируются и запускаются в 2 строчки? Здесь мы используем тот же самый javac, однако кроме него нам предстоит использовать ещё много утилит.

Для начала спрошу — а вы никогда не задумывались, как работает загадочный класс R? Собственно меня он сперва смутил из-за его сверхъестественных возможностей. Как на этапе компиляции можно через поля класса обращаться к XML-файлам в других каталогах? Я предположил, что тут орудует прекомпилятор — так оно и оказалось.

Собственно, есть специальная утилита AAPT — она проходится по каталогам ваших ресурсов и создаёт тот самый R.java. Оказывается, всё очень даже просто — это просто класс, в составе которого другие статические вложенные классы с целочисленными константами. И всё! Он выглядит примерно так
R.java
/* AUTO-GENERATED FILE.  DO NOT MODIFY.
 *
 * This class was automatically generated by the
 * aapt tool from the resource data it found.  It
 * should not be modified by hand.
 */

package com.example.testapp;

public final class R {
    public static final class attr {
    }
    public static final class string {
        public static final int hello=0x7f020000;
    }
}


Теперь давайте создадим его у вас. Для этого используем следующие команды:
call %AAPT_PATH% package -f -m -S %DEV_HOME%/res -J %DEV_HOME%/src -M %DEV_HOME%/AndroidManifest.xml -I %ANDROID_JAR%

Давайте разберёмся, что к чему. AAPT — Android Asset Packaging Tool — буквально «упаковщик андроид-имущества». Его опции:
  • package — говорит, что нам нужно именно упаковать ресурсы (а не добавить или удалить)
  • -f — перезапись существующего R.java, если таковой имеется
  • -m — разместить R.java в надлежащих пакетах, а не в корне указанного в -J пути
  • -S — после этой опции мы указываем каталог с ресурсами
  • -J — после этой опции мы указываем куда сохранить получившийся R.java
  • -I — после этой опции мы указываем путь к подключаемой библиотеке — включаем android.jar

После его выполнения в каталоге src должен появится тот самый файл R.java. Проверьте.

Теперь в нашем проекте нет никакой магии и он полностью синтаксически корректен для обычного Java-компилятора. Давайте скомпилируем его.
call %JAVA_HOME%/bin/javac -d %DEV_HOME%/obj -cp %ANDROID_JAR% -sourcepath %DEV_HOME%/src %DEV_HOME%/src/%PACKAGE_PATH%/*.java

Если вы ранее работали с java, то понять эту строчку для вас не составит труда. Однако я всё-таки объясню, что к чему:
  • -d — куда поместить итоговые пакеты и *.class-файлы
  • -cp — classpatch — укажем путь к библиотеке android.jar
  • -sourcepath — пути, где находятся (или могут находится) наши *.java файлы

После выполнения в папке obj должны находится пакеты с нашими классами в виде байт-кода (*.class). Но вот ведь незадача — виртуальная машина андроида с ним не совместима! Но так было задумано и на это есть причины. А мы используем конвертер, который делает из class-файлов dex-файл, понятный для нашего зелёного R2D2.
call %DX_PATH% --dex --output=%DEV_HOME%/bin/classes.dex %DEV_HOME%/obj

Опция --dex указывает на то, что нам нужно преобразовать из *.class в *.dex. Затем после опции --output мы задаём путь к итоговому файлу classes.dex. И последний аргумент в этой команде — путь непосредственно к пакетам, содержащим *.class файлы

Убедитесь в том, что в папке bin находится наш classes.dex. Теперь осталось только упаковать его вместе с ресурсами в APK-файл. Сделаем это:
call %AAPT_PATH% package -f -M %DEV_HOME%/AndroidManifest.xml -S %DEV_HOME%/res -I %ANDROID_JAR% -F %DEV_HOME%/bin/AndroidTest.unsigned.apk %DEV_HOME%/bin

Здесь опции аналогичны тем, которые мы использовали при создании R.java:
  • package — говорит, что нам нужно именно упаковать ресурсы (а не добавить или удалить)
  • -f — перезапись существующего AndroidTest.unsigned.apk, если таковой имеется
  • -M — после этой опции мы указываем путь к файлу манифеста
  • -S — после этой опции мы указываем каталог с ресурсами
  • -I — после этой опции мы указываем путь к подключаемой библиотеке — включаем android.jar
  • -F — после этой опции мы указываем куда сохранить получившийся AndroidTest.unsigned.apk
  • последний аргумент — путь к папке с dex — файлами

В папке bin теперь должен появится AndroidTest.unsigned.apk. И мы назвали его не просто так! У него нет цифровой подписи. Андроид запрещает устанавливать и запускать приложения без подписи. Но создать её не так-то трудно, как может показаться на первый взгляд
call %JAVA_HOME%/bin/keytool -genkey -validity 10000 -dname "CN=AndroidDebug, O=Android, C=US" -keystore %DEV_HOME%/AndroidTest.keystore -storepass android -keypass android -alias androiddebugkey -keyalg RSA -v -keysize 2048
call %JAVA_HOME%/bin/jarsigner -sigalg SHA1withRSA -digestalg SHA1 -keystore %DEV_HOME%/AndroidTest.keystore -storepass android -keypass android -signedjar %DEV_HOME%/bin/AndroidTest.signed.apk %DEV_HOME%/bin/AndroidTest.unsigned.apk androiddebugkey

Собственно, эти строчки запускают 2 Java-утилиты, которые не имеют никакого отношения к Android SDK — но они необходимы. Первая создаёт файл AndroidTest.keystore (проверьте его наличие), а вторая — этот файл соединяет с AndroidTest.unsigned.apk. Получается файл AndroidTest.signed.apk. Вот такой дикий крафт файлов. Но однажды создав bat-скрипт запускайте его — и он будет делать всё это в автоматическом режиме.

Я думаю, не стоит тратить время на детальный разбор опций этих утилит в пределах данной статьи. Просто нужно уловить суть — они берут AndroidTest.unsigned.apk, подписывают его файлом AndroidTest.keystore и сохраняют в AndroidTest.signed.apk. Если есть желание, можете почитать в документации.

У вас, скорее всего, будет предупреждение "Warning: No -tsa or -tsacert is provided and this jar...", но не обращайте внимание.

Запуск

Теперь, когда мы наконец собрали наш apk-файл — можем его запустить. Подключите по usb ваше устройство, или же запустите эмулятор. А затем выполните
call %ADB% uninstall %PACKAGE%
call %ADB% install %DEV_HOME%/bin/AndroidTest.signed.apk
call %ADB% shell am start %PACKAGE%/%PACKAGE%.%MAIN_CLASS%

Собственно, первая строчка удаляет программку, если она уже там есть. Для повторных запусков пригодится. Вторая — устанавливает APK на ваш девайс или эмулятор. Третья же — запускает. Давайте более подробно разберём её аргументы:
  • shell — мы хотим выполнить некоторые команды на нашем девайсе
  • am — используем для выполнения команд activity manager
  • start — мы хотим запустить некоторое Activity
  • имя пакета и полное имя класса (включая пакет), которые мы стартуем

Если всё прошло удачно, вы увидите что-то вроде этого:


Заключение


После сборки всех файлов дерево каталогов должно быть примерно таким.
│   AndroidManifest.xml
│   AndroidTest.keystore
│   comp.bat
│
├───bin
│       AndroidTest.signed.apk
│       AndroidTest.unsigned.apk
│       classes.dex
│
├───obj
│   └───com
│       └───example
│           └───testapp
│                   MainActivity.class
│                   R$attr.class
│                   R$string.class
│                   R.class
│
├───res
│   └───values
│           strings.xml
│
└───src
    └───com
        └───example
            └───testapp
                    MainActivity.java
                    R.java

Теперь вы можете наглядно увидеть и понять, как происходит сборка андроид-приложения на более низком уровне. Когда будете использовать IDE — если сборка вдруг пойдёт не так (а такое часто бывает) — сможете вырулить ситуацию как надо. Также обратите внимание на то, что итоговый apk-файл занимает всего около 4 килобайт.

Выкладываю архив проекта (Нажимайте Файл-Скачать). Обратите внимание, что я добавил туда ещё один маленький скрипт — clear.bat. Он удаляет все созданные при сборке файлы. И поставил его запуск на начало comp.bat. Также добавил комментарии после Rem — по шагам.

Таким образом, скрипт производит полную очистку и пересборку проекта, включая подпись, а также удаление его на устройстве, установку и запуск.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+86
Comments 42
Comments Comments 42

Articles