16 июля 2010 в 20:23

Основы программирования под Android на примере игры Судоку

В статье описаны основные трудности создания приложений под Android.
Рассматриваются базовые понятия программирования Android.
Для примера описано создание игры Sudoku из книги Hello, Android – Ed Burnette.
Осторожно много скриншотов.



1. Трудности разработки

Android это уникальная операционная система. Разработчик приложений должен знать ее особенности и нюансы для получения хорошего результата. Существуют некоторые трудности, которые нужно учитывать при разработке (хорошее описание здесь). Перечислим их кратко:
1) Приложение требует для установки в два раза (или даже в четыре) больше места чем оригинальный размер приложения.
2) Скорость работы с файлами на встроенной флэшке падает в десятки раз при уменьшении свободного места.
3) Каждый процесс может использовать до 16 Мб (иногда 24 Мб) оперативной памяти.

2. Принципы разработки производительных приложений под Android

Существует ряд рекомендаций по созданию производительных приложений под Android. Их также можно расширить на основе книг Effective Java — Joshua Bloch и Программирование мобильных устройств на платформе .Net Compact Framework — И.Салмре.
Перечислим основные принципы разработки производительных приложений под Android.

Стратегические:
1) Ресурсы нужно экономить.
2) Нужно мгновенно выдавать реакцию на действия и поддерживать обратную связь с пользователем.
3) Производительность приложения главная цель. Нужно постоянно в процессе разработки оптимизировать производительность не оставляя эту работу на потом.
4) Нужно измерять время выполнения, протоколировать и анализировать ход выполнения приложения, узкие участки кода, возникновения событий, выделение памяти, время жизни объектов. Что не измеряется, то нельзя оптимизировать.

Тактические:
1) Избегайте создания лишних объектов.
2) По возможности делайте методы статичными.
3) Используйте прямой доступ к полям вместо методов посредников.
4) Используйте static final для констант.
5) Не используйте enum там, где достаточно обычной переменной целого типа.

3. Ключевые особенности Android

Android основан на Linux. Между приложением и ядром лежит слой API и слой библиотек на нативном коде. Приложение выполняется на виртуальной машине Java (Dalvik Virtual Machine).
В Android можно запускать много приложений. Но одно из них есть главным и занимает экран. От текущего приложения можно перейти к предыдущему или запустить новое. Это похоже на браузер с историей просмотров.

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

image

Android использует специальный механизм описания действий основанный на Intent. Когда нужно выполнить действие (сделать звонок, послать письмо, показать окно), вызывается Intent.

Также Android содержит сервисы подобные демонам в Linux для выполнения нужных действий в фоновом режиме (например, проигрывание музыки).
Для обмена данными между приложениями используются Content providers (провайдеры содержимого).

4. Пример приложения – Судоку

Будем рассматривать готовый проект игры Судоку (полный код в папке Sudokuv4).

Для работы нужен Android SDK и Eclipse. О том как все установить и начать написано тут.

Для загрузки проекта в Eclipse ножно выполнить такую последовательность действий:
1) Разархивировать проект в отдельную папку в рабочем пространстве Eclipse.
2) Выбрать пункт меню File->New->Android Project.
3) В диалоге New Android Project выбрать опцию Create project from existing source.
4) В поле Location указать путь к папке с проектом. Нажать Next.

Меню программы

Игровое меню описано в файле res/layout/main.xml. Описание интерфейса можно редактировать в виде XML или в виде визуализированного интерфейса. Для переключения служат вкладки внизу области отображения содержимого.

image

image

Обычно элементы управления содержатся внутри контейнера, в нашем случае это LinearLayout. Он располагает все элементы в виде одной колонки.

Ресурсы

Обратите внимание, что все текстовые надписи (android:text) берут данные из ресурсов. Например, запись android:text="@string/main_title" указывает, что текст нужно искать в файле res/values/string.xml в узле с именем main_title (Android Sudoku). Цвет фона также содержится в ресурсах (android:background="@color/background") но в файле color.xml (#3500ffff). При открытии файлов ресурсов в редакторе может возникнуть ошибка. Но всегда можно перейти к отображению XML.

image

image

Элементы управления к которым нужно получить доступ из кода должны иметь id. У кнопок есть id (android:id="@+id/continue_button") для того что бы было можно присоединить к кнопке обработчик нажатия. Знак плюс указывает, что для кнопки нужно создать идентификатор в файле /gen/org.example.sudoku/R.java (public static final int continue_button=0x7f0b000b;). Этот файл генерируется автоматически и не рекомендуется его изменять. Файл содержит класс R через него можно обратиться к любому элементу интерфейса и другим ресурсам.

image

Создание окон

Рассмотрим создание окна с информацией о программе. Разметка этого окна находится в файле /res/layout/about.xml. Activity класс описан в файле /src/org.example.sudoku/About.java. Activity связана с разметкой в файле AndroidManifest.xml. Этот файл можно просматривать или через редактор или в виде XML. На разных вкладках редактора можно выбрать различные разделы файла. В разделе Application находятся параметры Activity. Обратите внимание, что параметр Theme имеет значение @android:style/Theme.Dialog. Благодаря этому стиль окна больше похож на модальный диалог.

image

Вызов окна с информацией о программе осуществляется из класса Sudoku по нажатию кнопки About. Класс Sudoku написан так, что сам обрабатывает событие Click (public class Sudoku extends Activity implements OnClickListener). В методе public void onClick(View v) определяется какая кнопка вызвала событие и выполняется соответствующий код. Для показа окна About вызывается соответствующий Intent.
case R.id.about_button:
Intent i = new Intent(this, About.class);
startActivity(i);
break;


Обработчики событий также могут устанавливаться на конкретные элементы управления. Например в классе Keypad при создании класса в методе setListeners() устанавливаются обработчики для отдельных кнопок.
image

Простой диалог

Пользователю нужно дать возможность выбрать уровень сложности. Это маленький диалог, в котором нужно выбрать один из нескольких вариантов. Очень радует, что для этого не нужно создавать отдельный Intent, а достаточно воспользоваться классом AlertDialog.
Разберем процес старта новой игры. Пользователь нажимает на кнопку New Game. Обработчик нажатия это метод класса Sudoku – onClick. Далее вызывается метод openNewGameDialog, который показывает диалог выбора сложности и стартует игру с выбранным уровнем сложности. Это диалог строится с помощью класса AlertDialog.

private void openNewGameDialog() {
      new AlertDialog.Builder(this)
           .setTitle(R.string.new_game_title)
           .setItems(R.array.difficulty,
            new DialogInterface.OnClickListener() {
               public void onClick(DialogInterface dialoginterface,
                     int i) {
                  startGame(i);
               }
            })
           .show();
   }


Обратите внимание, что содержимое диалога (набор кнопок) строится из массива строк R.array.difficulty. Тут же назначается обработчик нажатия на кнопки диалога, который по номеру нажатой кнопки запускает новую игру с заданным уровнем сложности, вызывая метод startGame.

Графика

За игровую логику отвечает класс Game. Тут загружаются задания, проверяются условия выигрыша. Класс Game есть Activity но интерфейс описан не в XML, а создается кодом. В методе onCreate создается View:

puzzleView = new PuzzleView(this);
setContentView(puzzleView);
puzzleView.requestFocus();


PazzleView это класс производный от View, в нем рисуется игровое поле и происходит обработка событий касания экрана (метод onTouchEvent) и нажатия клавиш (метод onKeyDown).

Разберем процесс рисования в Android. Для рисования нужно перегрузить метод onDraw. Метод получает объект Canvas через который осуществляется рисование. Для задания цветов создаются обьекты класса Paint. Цвет задается в формате ARGB. Цвет лучше хранить в виде ресурсов (файл colors.xml). Paint это не только класс для хранения информации о цвете. Например при рисовании текста он содержит информацию о способе закрашивания, шрифте и выравнивании текста.

Canvas содержит набор методов для рисования графики (drawRect, drawLine, drawPath, drawText и другие).

Для оптимизации графики, лучше воздержаться от создания объектов и лишних вычислений внутри метода onDraw (рассматриваемый пример реализации графики не оптимален).

image

Музыка

Для воспроизведения музыки используется класс MediaPlayer. Музыка для игры добавлена в ресурсы. Нужно просто скопировать нужные файлы в папку /res/raw (форматы WAV, AAC, MP3, WMA, AMR, OGG, MIDI).
Для начала нужно создать экземпляр класса MediaPlayer:
mp = MediaPlayer.create(context, resource);
тут context это обычно класс, который инициирует запуск музыки, resource – идентификатор ресурса с музыкой. Для управления воспроизведением используют методы start, stop и release.

В игре музыка воспроизводится в главном меню (запуск из класса Sudoku) и в игровом процессе (запуск из класса Game). Для управления воспроизведением создан класс Music. Класс содержит статический экземпляр MediaPlayer, что позволяет не создавать отдельный проект для каждого запуска звукового ресурса.

В классах Sudoku и Game переопределены методы onResume и onPause, в которых запускается музыка при старте Activity и останавливается при деактивации.

Выводы

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

P.S. Большое спасибо пользователю malkolm за замечания.
+125
143062
871

Комментарии (48)

НЛО прилетело и опубликовало эту надпись здесь
+13
lesa80, #
вот ради таких постов и открываешь периодически хабр =)
+4
degratnik, #
Я старался. Может не все аспекты затронул. Постараюсь дополнить пост по результатам комментирования.
+3
hamsterksu, #
Тактические:
1) Избегайте создания лишних объектов.
2) По возможности делайте методы статичными.
3) Используйте прямой доступ к полям вместо методов посредников.
4) Используйте static final для констант.
5) Не используйте перечисления там, где достаточно обычной переменной целого типа.

Эти принцыпы всегда работали для мобильных устройст. — Это точно над делать когда пишете под не совем свежий телефон.

вот только пугать людей сильно не стоит — никто не заставляет отступать от ООП. и перебегать в процедурное програмирование.
можно и сетеры/геторы делать. ничего страшного не случится.
производительность утсройст славы богу нормальное — чего стоит только процессоры — меньше 600 нет, и память для вашего приложения тоже имееться.

так что по большому счету ООП надо юзать тут.

0
degratnik, #
Я хотел сказать, что ООП нужно использовать только там где это действительно нужно.
+1
hamsterksu, #
ООП надо почти везде :) как только вы отнаследовались от Activity — вы уже используте ООП
+3
hamsterksu, #
да и классы у Вас свалины в один пакет — хотя явно должны быть в разных
0
degratnik, #
Как их лучше распределить по пакетам?
0
hamsterksu, #
скрины лежат либо в корневом пакете либо можно сделать пакет activities
классы отвечающие за доступ к данных — пакет dao или data
вспомогательные классы — в utils
вьюхи — views
компонеты — components
диалоги — dialogs
+1
hamsterksu, #
обработчики кнопок и прочего нужно выносить из анонимных классов в отдельные методы, что бы код был нормально читаемый.
ну кроме обработчиков в 2-3 строчки.
+1
ivanrt, #
Чувствую меня бы вы совсем зачмырили с вложенными анонимными классами :)
Типа такого:
code.google.com/p/wifikeyboard/source/browse/src/com/volosyukivan/WiFiInputMethod.java#54
+1
hamsterksu, #
это просто правила хорошего тона — если потмо собираетесь разбирать то, что писали или комуто показывать :)
это облегчает чтение кода.

большая вложеность кода — нечитаема

+1
hamsterksu, #
по два вложенных класса :)
+1
hamsterksu, #
для About можно юзать AlerdDialog.Builder — ну или отнаследоваться от Dialog.
+1
hamsterksu, #
Правда, дальше в статье рассматривается такая возможность…
0
degratnik, #
Совершенно верно. Хотел описать разные пути.
+1
sigizmund, #
Отличный пост. Коротко, по делу и с примерами.
+1
mev, #
Запишите всё с помощью Камтасии Студио и цены вам не будет! :)
+2
degratnik, #
Хорошая идея. Постараюсь найти время.
+2
SkyLine, #
Когда мне надо было быстренько вьехать в программирование под Android, смотрел вот эти видеотуториалы. Достаточно четко и понятно, советую.
0
malkolm, #
Статья нормалек, но стилистически ужасна — порой язык заплетается от прочтения.
НЛО прилетело и опубликовало эту надпись здесь
+2
zvic, #
Кнопка Exit? Как бы идеология Android говорит что мы не должны принудительно закрывать приложение, что жизненным циклом каждого конкретного приложения управляет Android OS
–1
dmbreaker, #
Это игра — ей позволительно, игры обычно стандартный интерфейс ОС никогда не используют.
+2
zvic, #
При чем тут игра это или нет?
Первый же рисунок в статье дает нам однозначное толкование жизненного цикла ЛЮБОГО приложения на Андроиде. Если пользователь нажал Home или Back во время игры — все, мы подготавливаемся к выходу. А вот сам выход из приложения происходит только тогда, когда ОС сама это решит.
–1
hamsterksu, #
на Exit можно повесить банальный finish() и не килить процесс, и OS потом сама закроет приложение когда надо.
+1
glebkk, #
Скорее бы Android 2.2 с JIT вошел в массы, можно будет сильно не извращаться.
0
Davidov, #
>5) Не используйте перечисления там, где достаточно обычной переменной целого типа

Я не знаю, как это устроено в Java и в Davlik, но в C enum всегда при компиляции становится обычным int. Неужели для явы это не так?
0
degratnik, #
Это рекомендация с оф.сайта.
+1
hamsterksu, #
в офф гайдаг было напсиано, что enum тяжеловеснее чем просто константа.
но это было до 2.2 — как там в 2.2 пока сказать не могу

но использование enum, как известно, дает типизацию константам.

Но если посмотреть на стандартные класс — они нигде не юзают enum только константы
+1
thevery, #
не так — енумы превращаются в классы, причём достаточно большие — поэтому, собственно, их и не рекомендуют использовать. подробнее — на офсайте.
+1
grayscaler, #
Спасибо за статью, руки зачесались написать небольшой проектик под Android :)
0
degratnik, #
Пожалуйста.
+2
thevery, #
поздравляю, вы делаете успехи — в этой статье явных ошибок заметно меньше
замечания:
диалоги лучше показывать через showDialog

>Их также можно расширить на основе книг Effective Java — Joshua Bloch
многие советы Блоха для андроида, увы, не только неприменимы, но и вредны — например «preffer interfaces over implementation»

>Цвет задается в формате ARGB
и не только — возможны rgb, argb, rrggbb, aarrggbb

>Нужно просто скопировать нужные файлы в папку /res/raw (форматы WAV, AAC, MP3, WMA, AMR, OGG, MIDI).
wma может андроидом и не поддерживаться

>Класс содержит статический экземпляр MediaPlayer, что позволяет не создавать отдельный проект для каждого запуска звукового ресурса.
эээ… а я в коде вижу
mp = MediaPlayer.create(context, resource);
+код не thread-safe
+1
thevery, #
upd:
похвалил я вас, как вижу, зря — код-то не ваш, а из книги. Более того:
Copyrights apply to this code. It may not be used to create training material, courses, books, articles, and the like.
0
degratnik, #
Я указал, что код из книги.
+1
thevery, #
как по мне, так указано очень неявно
0
degratnik, #
В начале поста я указал, что пример из книги. (строчка 3)
Как это указать более явно? Что Вы порекомендуете?
+1
thevery, #
>В начале поста я указал, что пример из книги. (строчка 3)
Как это указать более явно? Что Вы порекомендуете?
описание из книги!=полный копипаст кода

>Как это указать более явно? Что Вы порекомендуете?
написать как есть — рассматриваемый код взят из такой-то книги под такой-то лицензией
0
degratnik, #
Это не совсем оригинальный код из книги. Я читал книгу, учился, писал.
0
degratnik, #
Скажите пожалуйста, а как правильно использовать код под таким копирайтом в своих постах?

Я думаю, что если я вносил в код изменения, то я могу его использовать.
+1
thevery, #
>Скажите пожалуйста, а как правильно использовать код под таким копирайтом в своих постах?
этот код — скорее всего никак, ибо у него явно максимально жёстка лицензия

>Я думаю, что если я вносил в код изменения, то я могу его использовать.
сильно сомневаюсь — если уж в чистом виде запрещено использовать, то вряд ли разрешено и в изменённом
0
degratnik, #
Большое спасибо. Буду внимательнее к копирайтам.
+1
hamsterksu, #
>диалоги лучше показывать через showDialog

как-то не совсем удобно его юзать.
если надо показать несколько диалогов — то приходится завести несколько констант
потмо написать switch в showDialog
и еще один в prepareDialog

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

+1
tum0rc0re, #
Это самый правильный путь, зато при смене ориентации экрана, эти диалоги остаются на экране, а если создавать обычным способом, придется отслеживать все это дело. Вообще лучше именно так создавать ))
НЛО прилетело и опубликовало эту надпись здесь
0
Le0, #
>При открытии файлов ресурсов в редакторе может возникнуть ошибка…

Гадский баг, который появился после обновления эклипса до версии 3.6. Вылечился добавлением неймспейса к тэгу ресурса:

<resources xmlns:android="ht_tp://schemas.android.com/apk/res/android">

ht_tp -> http, борьба с парсером.
0
dizel3d, #
Спасибо. В избранное.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.