Jasmin Java-ассемблер.
Tutorial
Введение
Этот короткий практический учебник, больше рассчитанный на простое,
практическое программирование - без вникания в тонкости реализации Java-ассемблера.
Т.к Jasmin это Java-ассемблер, то и объяснения будут на основе языка Java.
Поэтому - начальные познания в Java очень желательны.
Для удобства я буду опираться на текст и примеры из книги Е.Буткевича "Пишем программы и игры для сотовых телефонов".
Возможно, опытным Java-программистам приведенные объяснения покажутся не совсем корректными и неполными
- это потому, что этот учебник рассчитан больше на новичков.
Для получения основной справочной информации читаем Manual!
Глава 1 Оборудование
Для создания Мобильных Java-приложений с помощью Jasmin'a вам понадобятся такие программы:
1.Jasmin - собственно сам Java-ассемблер;
2.Preverifier – программа, осуществляющая пре-верификацию class-файлов для мобильных приложений;
3.TextEditor - текстовый редактор для написания исходных файлов;
4.JZipMan - ZIP упаковщик, для создания JAR-файла приложения;
5.Jar2Jad - генератор JAD-файлов.
Состав мобильного приложения
Приложение для мобильного телефона состоит, как правило, из двух файлов:
1.JAD-файл. Это простой текстовый файл-описатель с расширением .jad,
для первоначальной проверки совместимости с устройством и настройки различных атрибутов.
2.JAR-файл. Это по сути ZIP-архив с расширением .jar, содержащий байт-код (в виде файлов *.class)
исполняемой части приложения и его ресурсы.
Глава 2 Hello, World!
В этой главе мы рассмотрим структуру программы для мобильного телефона, а также все этапы ее создания.
Для создания полноценного мобильного приложения необходимо выполнить следующие этапы разработки:
1.написание кода (текста) программы на JASM'e;
2.ассемблирование программы (создание класс-файла);
3.пре-верификация класс-файлов;
4.упаковка приложения в JAR-архив;
Для начала выясним, что же представляет собой мобильное приложение и что у него внутри.
Создание мидлета
Для удобства разработки можно создать несколько папок, например:
0:/Misc/src - здесь будем хранить исходники *.j;
0:/Misc/tmpclasses - здесь будут сохранены ассемблированные class-файлы;
0:/Misc/classes - папка для конечных (проверенных) class-файлов;
0:/Misc/bin - папка для сборки приложения;
Класс MIDlet
Основной класс приложения должен быть унаследован от абстрактного класса MIDlet (javax/microedition/midlet/MIDlet)
и должен обязательно реализовать методы:
() - конструктор без аргументов, вызывается системой (автоматически) для создания экземпляра основного класса;
startApp() - вызывается системой (автоматически) при запуске приложения;
pauseApp() - вызывается системой (автоматически) при переходе в состояние паузы (например, при входящем звонке);
destroyApp(boolean) - вызывается при переходе в прерванное состояние (выход из приложения);
Написание кода
Напишем программу, отображающую в области системны сообщений текст "Hello, World!",
с помощью Java-команды: System.out.print("Hello, World!"); переведенной в Java-ассемблер.
Основной класс для простейшей программы "Hello World" будет выглядеть так:
; в заголовке указываем имя основного класса (например, Hello)
.class public Hello
.super javax/microedition/midlet/MIDlet
; конструктор основного класса
.method public ()V
.limit stack 1
.limit locals 1
aload_0
invokespecial javax/microedition/midlet/MIDlet/()V
return
.end method
; точка входа в программу - метод startApp
.method public startApp()V
; сюда добавляем код, с которого и начинается программа
.limit stack 2
.limit locals 1
; Java-код: System.out.print("Hello, World!") на JASM'e займет три строчки
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "Hello, World!"
invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V
return
.end method
; метод "паузы"
.method public pauseApp()V
.limit stack 0
.limit locals 1
return
.end method
; метод "выхода"
.method public destroyApp(Z)V
.limit stack 0
.limit locals 2
return
.end method
Теперь наберем этот текст в TextEditor'e и сохраним получившийся файл в папке .../src
c именем Hello.j в кодировке CP 1251.
Внимание! Имя файла должно полностью совпадать с именем основного класса приложения (учитывая регистр)!!!
Ассемблирование программы
Для того чтобы ассемблировать наше приложение, следует запустить программу Jasmin.
В поле "Path to SourceFile(*.j)" укзываем полный путь к исходному файлу .../src/Hello.j
А в поле "Path for generated ClassFile" укызваем папку, куда будет сгенерирован класс-файл .../tmpclasses/
Для запуска процесса ассемблирования жмем "Assemble"!
Пре-верификация
Далее необходимо выполнить пре-верификацию полученного Hello.class.
Для этого запускаем Preverifier и в поле "Path for Input ClassFile:" указывается полный путь к
еще не проверенному class-файлу .../tmpclasses/Hello.class.
А в поле "Path for Verified ClassFile:" указываем папку для конечного class-файла .../classes/
И жмем "Verify"!
В итоге, в папке /classes мы получим готовый Hello.class!
Упаковка приложения
Перед тем как упаковывать приложение необходимо создать файл манифеста MANIFEST.MF содержащий атрибуты приложения.
Сначала в каталоге .../bin создадим папку с именем META-INF.
Затем в TextEditor'e наберем текст манифеста:
Manifest-Version: 1.0
MIDlet-1: HelloWorld,icon.png,Hello
MIDlet-Vendor: Mumey
MIDlet-Version: 1.0
MIDlet-Name: HelloWorld
MicroEdition-Configuration: CLDC-1.1
MicroEdition-Profile: MIDP-2.0
Где:
MIDlet-1 - имя данного мидлета, значек мидлета (можно не указывать), имя основного класса;
MIDlet-Vendor - имя поставщика или разработчика (например: ваше имя);
MIDlet-Version - версия приложения;
MIDlet-Name - имя всего приложения;
MicroEdition-Configuration - требуемая конфигурация J2ME для выполнения мидлета;
MicroEdition-Profile - требуемый профайл J2ME для выполнения мидлета.
Сохраним этот текст в папке .../bin/META-INF c именем MANIFEST.MF
Для упаковки приложения запускаем JZipMan, в котором указываем для упаковки:
1.Папку META-INF с содержащимся в ней MANIFEST.MF;
2.Все классы из папки .../classes (в данном приложении у нас только один класс - Hello.class);
3.Необходимые ресурсы - картинки, текст. файлы и т. п. (в данном приложении у нас их нет).
Сохраним получившийся ZIP-архив в папке .../bin c именем HelloWorld.jar.
HelloWorld.jar - это файл приложения готовый к установке в телефон!
Если Ваш телефон не устанавливает Java-приложения без JAD-файла - можете воспользоваться программой Jar2Jad,
автоматически создающей JAD-файлы.
Особого смысла загружать наше приложение HelloWorld, в телефон нет, поскольку никакой реакции аппарата мы не увидим,
т.к. текст "Hello, World!" выводиться в область системных сообщений доступных только для консоли WTK.
(Это сделано для того, чтобы упростить программу и получить основные представления о структуре мидлета и этапах разработки)
Так что отложите процедуру загрузки до следующей главы.
Глава 3 Пользовательский интерфейс высокого уровня
После того как мы познакомились с основной структурой мидлета, будем посте-
пенно продвигаться в наших познаниях. Java — язык объектно-ориентирован-
ный, поэтому, изучив все его объекты, мы сможем конструировать из них, как из
кубиков, большие многофункциональные приложения. Первым делом разберем-
ся с тем, как выводить информацию на экран телефона, чтобы демонстрировать
результат работы наших приложений. Выводимая информация может быть раз-
нообразной: текст, картинки, средства общения (интеракции) с пользователем.
Все объекты, которые можно отобразить на экране, наследуются из общего клас-
са, название которого говорит само за себя — javax/microedition/lcdui/Displayable.
Дальнейшая эволюция классов, наследуемых из класса Displayable, идет в двух
направлениях:
1. Класс Canvas предоставляет пользователю интерфейс низкого уровня, органи-
зует непосредственный доступ к прорисовке экрана. Подробнее на этом классе
мы остановимся в следующих главах.
2. Класс Screen является родителем для классов пользовательского интерфейса
высокого уровня. Это четыре класса: Alert, Form, List, TextBox, объекты которых
являются стандартными средствами связи с пользователем. Внешний вид та-
ких объектов зависит от конкретной модели телефона.
Класс Form
В этой главе мы рассмотрим работу с формой (объектом класса Form) на примере
программы фотоальбома. Форма является определенного рода контейнером, ко-
торый может содержать различные визуальные элементы, такие как текстовые
строки, поля символьного ввода, картинки, меню выбора, шкалы.
С точки зрения объектов, элементы, которые можно добавить в форму, должны
быть порождены одним из следующих классов: String, Image или Item. Элемент
класса String представляет собой статическую строку, автоматически отформа-
тированную согласно размерам экрана конкретного аппарата. Элемент класса Image
представляет собой картинку. Фактически, оба этих элемента дублируются объек-
тами классов, порожденных из класса Item.
Класс Item является базовым для целой группы элементов, которые могут быть
добавлены в форму: ChoiceGroup (меню выбора), DateField (поле ввода даты и вре-
мени), Gauge (шкала), Imageltem (изображение), Stringltem (статическая строка),
TextField (поле ввода).
Внутренняя реализация формы скрыта от программиста, мы можем контролиро-
вать лишь порядок отображения элементов. Расположение элементов на экране,
навигация между элементами, вертикальная прокрутка экрана при необходимости
контролируются внутренней реализацией и зависят от конкретной модели аппара-
та. Работа с формой осуществляется с помощью следующих методов класса Form:
(вначале в коментариях я буду писать код для Java, а ниже JASM для Jasmin'a )
; Form(String title) — конструктор создающий пустую форму с заголовком title
aload_0
new javax/microedition/lcdui/Form
dup
ldc "title"
invokespecial javax/microedition/lcdui/Form/(Ljava/lang/String;)V
putfield MyClassName/MyFormName Ljavax/microedition/lcdui/Form;
; int append(String str) — добавляет статическую строку str в форму
aload_0
getfield MyClassName/MyFormName Ljavax/microedition/lcdui/Form;
ldc "str"
invokevirtual javax/microedition/lcdui/Form/append(Ljava/lang/String;)I
pop
; int append(Image img) — добавляет объект изображения img в форму
aload_0
getfield MyClassName/MyFormName Ljavax/microedition/lcdui/Form;
aload_0
getfield MyClassName/img Ljavax/microedition/lcdui/Image;
invokevirtual javax/microedition/lcdui/Form/append(Ljavax/microedition/lcdui/Image;)I
pop
; int append(Item item) — добавляет элемент item в форму.
; Следует отметить, что один экземпляр элемента может быть добавлен лишь единожды и только в одну форму
aload_0
getfield MyClassName/MyFormName Ljavax/microedition/lcdui/Form;
aload_0
getfield MyClassName/item Ljavax/microedition/lcdui/Item;
invokevirtual javax/microedition/lcdui/Form/append(Ljavax/microedition/lcdui/Item;)I
pop
; void delete(int itemNum) — удаляет из формы элемент с индексом itemNum.
; Нумерация элементов начинается с нуля
aload_0
getfield MyClassName/MyFormName Ljavax/microedition/lcdui/Form;
iconst_0 ; здесь указывается индекс элемента(в этом примере мы удаляем элемент с индексом 0).
invokevirtual javax/microedition/lcdui/Form/delete(I)V
Класс Display
Остановимся на вопросе, каким образом рассмотренные объекты отображаются на
экране. Этим процессом руководит менеджер дисплея — объект класса Display. Он
создается автоматически реализацией MIDP при запуске мидлета и сохраняется
до вызова функции destroyApp(). Самим такой объект создать нельзя, зато можно
получить ссылку на него с помощью метода:
; getDisplay(MIDIet)
aload_0
aload_0
invokestatic javax/microedition/lcdui/Display/getDisplay(Ljavax/microedition/midlet/MIDlet;)Ljavax/microedition/lcdui/Display;
putfield MyClassName/display Ljavax/microedition/lcdui/Display;
Когда мы получили ссылку на Display, любой объект, наследованный из класса
Displayable, может быть выведен на экран с помощью метода:
; setCurrent(Displayable)
aload_0
getfield MyClassName/display Ljavax/microedition/lcdui/Display;
aload_0
getfield MyClassName/MyDisplayableName Ljavax/microedition/lcdui/MyDisplayable;
invokevirtual javax/microedition/lcdui/Display/setCurrent(Ljavax/microedition/lcdui/Displayable;)V
Ввиду ограниченности ресурсов мобильных аппаратов одновременно на экране
может быть отображен только один объект, перекрытие и наложение окон не под-
держивается. Таким образом, любое приложение может быть рассмотрено как за-
данная последовательность отображаемых объектов.
Во время отображения формы через менеджер дисплея обновление экрана про-
исходит автоматически. Это значит, что при изменении содержания формы или
ее элементов нет необходимости вызывать какие-либо дополнительные функции,
так как изменения сразу будут отображены на экране. Это относится ко всем объек-
там, порожденным классом Screen.
Отображаем текст
Напишем новую программу HelloWorld которая будет отображать текст "Hello, World!" на экране телефона,
используя объект Form.
Структуру мидлета мы рассмотрели в прошлой главе. Помним, что основной класс
мидлета должен быть наследован из абстрактного класса MIDIet. He забываем так-
же о его функциях, обязательных к реализации.
Для того чтобы вывести текст на экран, нам потребуется несколько объектов,
которые будут объявлены как члены(поля) класса.
; Объявляем класс Hello наследуемый от MIDlet
.class public Hello
.super javax/microedition/midlet/MIDlet
; объявляем поля:
; Display display - менеджер дисплея
.field display Ljavax/microedition/lcdui/Display;
; Form form - форма отображаемая на экране
.field form Ljavax/microedition/lcdui/Form;
; конструктор класса
.method public ()V
.limit stack 1
.limit locals 1
aload_0
invokespecial javax/microedition/midlet/MIDlet/()V
return
.end method
; точка входа в программу - метод startApp
.method public startApp()V
.limit stack 4
.limit locals 1
; display = Display.getDisplay(this) - получаем ссылку на менеджер дисплея
aload_0
aload_0
invokestatic javax/microedition/lcdui/Display/getDisplay(Ljavax/microedition/midlet/MIDlet;)Ljavax/microedition/lcdui/Display;
putfield Hello/display Ljavax/microedition/lcdui/Display;
; form = new Form("Hello") - создаем новую форму
aload_0
new javax/microedition/lcdui/Form
dup
ldc "Hello"
invokespecial javax/microedition/lcdui/Form/(Ljava/lang/String;)V
putfield Hello/form Ljavax/microedition/lcdui/Form;
; form.append("Hello, World!") - добавляем в форму текст "Hello, World!"
aload_0
getfield Hello/form Ljavax/microedition/lcdui/Form;
ldc "Hello, World!"
invokevirtual javax/microedition/lcdui/Form/append(Ljava/lang/String;)I
pop
; display.setCurrent(form) - выводим форму на экран
aload_0
getfield Hello/display Ljavax/microedition/lcdui/Display;
aload_0
getfield Hello/form Ljavax/microedition/lcdui/Form;
invokevirtual javax/microedition/lcdui/Display/setCurrent(Ljavax/microedition/lcdui/Displayable;)V
; возвращаемся из метода
return
.end method
; метод pauseApp
.method public pauseApp()V
.limit stack 0
.limit locals 1
return
.end method
; метод destroyApp
.method public destroyApp(Z)V
.limit stack 0
.limit locals 2
return
.end method
Ассемблируем и собираем приложение, как это было рассмотрено ранее.
Теперь можно установить и запустить наше приложение на телефоне!
Отображаем картинку
Приведенной информации вполне достаточно, чтобы написать программу, ото-
бражающую картинку на экране телефона. Заметим, что не все картинки одина-
ково полезны. Во-первых, изображение должно быть в формате PNG (Portable
Network Graphics). Во-вторых, размер картинки не должен превышать размера
дисплея вашего аппарата.
Для удобства работы с ресурсами, можно создать новую папку
0:/Misc/res - где будут храниться все ресурсы разрабатываемого приложения.
Kартинка должна быть помещена в папку ресурсов приложения .../res.
Для того чтобы вывести картинку на экран, нам потребуется несколько объектов,
которые будут объявлены как члены нашего класса ImageShow.
. . .
; Display display - менеджер дисплея
.field display Ljavax/microedition/lcdui/Display;
; Form form - форма отображаемая на экране
.field form Ljavax/microedition/lcdui/Form;
; Image image - картинка для отображения
.field image Ljavax/microedition/lcdui/Image;
. . .
Далее в функции startApp() реализуем следующие действия:
• получим ссылку на менеджер дисплея;
• создадим объект картинки;
• создадим новую форму;
• добавим картинку в форму;
• отобразим форму на экране через менеджер дисплея.
Код функции будет выглядеть следующим образом:
.method public startApp()V
.limit stack 4
.limit locals 2
; display = Display.getDisplay(this) - получаем ссылку на менеджер дисплея
aload_0
aload_0
invokestatic javax/microedition/lcdui/Display/getDisplay(Ljavax/microedition/midlet/MIDlet;)Ljavax/microedition/lcdui/Display;
putfield ImageShow/display Ljavax/microedition/lcdui/Display;
LABEL1:
; image = Image.createlmage("/image.png") - создаем картинку из файла image.png
aload_0
ldc "/image.png"
invokestatic javax/microedition/lcdui/Image/createImage(Ljava/lang/String;)Ljavax/microedition/lcdui/Image;
putfield ImageShow/image Ljavax/microedition/lcdui/Image;
LABEL2:
goto LABEL4
LABEL3:
; обрабатываем ислючение, если файл image.png не может быть открыт (в данном случае мы ничего не делаем)
astore_1
LABEL4:
; form = new Form("Форма с картинкой") - Создаем новую форму с заголовком "Форма с картинкой"
aload_0
new javax/microedition/lcdui/Form
dup
ldc "Форма с картинкой"
invokespecial javax/microedition/lcdui/Form/(Ljava/lang/String;)V
putfield ImageShow/form Ljavax/microedition/lcdui/Form;
; form.append(image) - Добавляем картинку в Форму
aload_0
getfield ImageShow/form Ljavax/microedition/lcdui/Form;
aload_0
getfield ImageShow/image Ljavax/microedition/lcdui/Image;
invokevirtual javax/microedition/lcdui/Form/append(Ljavax/microedition/lcdui/Image;)I
pop
; display.setCurrent(form) - Выводим форму на экран
aload_0
getfield ImageShow/display Ljavax/microedition/lcdui/Display;
aload_0
getfield ImageShow/form Ljavax/microedition/lcdui/Form;
invokevirtual javax/microedition/lcdui/Display/setCurrent(Ljavax/microedition/lcdui/Displayable;)V
; Выходим из метода
return
; catch (IOException ioe) - Реализация обработки исключения при открытии картинки(код между меткам LABEL1 и LABEL2).
; Если при открытии картинки возникает исключение IOException, тогда переходим к коду по метке LABEL3
.catch java/io/IOException from LABEL1 to LABEL2 using LABEL3
.end method
Ассемблируем и собираем приложение, как это было рассмотрено ранее.
Все это просто замечательно, но ничего нового мы пока
что не изобрели. С таким же успехом можно было про-
сто поместить картинку в галерею, если таковая име-
ется. Мы пойдем дальше, задействуем клавиши теле-
фона, чтобы организовать смену картинок в нашем
фотоальбоме.
Класс Command
Итак, сейчас мы работаем с формой, относящейся
к иерархии класса Displayable, который является ба-
зовым для всех отображаемых объектов. Кроме стан-
дартных визуальных средств интеракции, объекты, по-
рожденные этим классом, поддерживают обработку
событий, возникающих в результате действий пользо-
вателя. К отображаемому объекту, в том числе и к фор-
ме, могут быть привязаны различные команды, вызов
которых приведет к некоторым действиям.
Команды представлены объектами класса Command из
пакета javax/microedition/lcdui/
Создается команда с помощью конструктора:
; Command(String label, int commandType, int priority)
aload_0
new javax/microedition/lcdui/Command
dup
ldc "label"
iconst_4 ; здесь задается commandType
iconst_1 ; здесь задается priority
invokespecial javax/microedition/lcdui/Command/(Ljava/lang/String;II)V
putfield MyClassName/MyCommandName Ljavax/microedition/lcdui/Command;
где:
• label — название команды для отображения ее на экране;
• commandType — тип команды;
• priority — приоритет команды.
Поскольку команды относятся к пользовательскому интерфейсу высокого уров-
ня, то при создании команды мы не можем заранее связать ее с какой-то опреде-
ленной клавишей телефона. Порядок вызова команды определит за нас реализа-
ция MIDP, мы лишь можем высказать свои пожелания с помощью параметров
типа команды и ее приоритета.
Тип команды задает внутреннее представление намеченного использования ко-
манды. MIDP предусматривает следующие типы команд, определяемые констан-
тами класса Command:
• 1 (iconst_1) - SCREEN команда имеет отношение к отображаемому в настоящее время экрану;
• 2 (iconst_2) - BACK возврат к логически предыдущему экрану;
• 3 (iconst_3) - CANCEL выбор отрицательного ответа в диалоге, реализуемом текущим экраном;
• 4 (iconst_4) - OK выбор положительного ответа в диалоге, реализуемом текущим экраном;
• 5 (iconst_5) - HELP вызов помощи или подсказки;
• 6 (bipush 6) - STOP остановка выполняемой в настоящее время операции.
• 7 (bipush 7) - EXIT выход из приложения;
• 8 (bipush 8) - ITEM команда имеет отношение к определенному элементу экрана;
Заметим, что константы задают лишь тип команды. Они не предписывают ни-
каких действий. Смена экрана, выход из приложения и прочие действия не вы-
полняются автоматически, они должны быть реализованы в соответствующем
месте. Тип лишь ориентирует реализацию MIDP, как разместить команды на
экране и организовать их вызов в соответствии с общим стилем меню и функци-
ональными клавишами конкретной модели телефона.
Приоритет задает преимущество перед другими командами. Это влияет на поря-
док отображения команд при объединении их в группу.' Приоритет задается це-
лым числом. Наименьшее число означает наивысший приоритет.
Такой вариант значительно ограничивает свободу наших действий, но решает мас-
су проблем, связанных с транспортабельностью приложений. Теперь мы можем
быть уверены, что на любом аппарате с поддержкой J2ME команды будут предло-
жены пользователю корректно, с учетом общей политики отображения и стиля
команд конкретного аппарата.
После создания объекта команды необходимо добавить его в форму, которую мы
собираемся отображать на экране. Методы добавления и удаления команд реали-
зует класс Displayable, то есть команды могут быть добавлены к любому отобра-
жаемому объекту:
; void addCommand(Command cmd) — добавляет команду and к отображаемому объекту
aload_0
getfield MyClassName/MyDisplayableName Ljavax/microedition/lcdui/MyDisplayable;
aload_0
getfield MyClassName/cmd Ljavax/microedition/lcdui/Command;
invokevirtual javax/microedition/lcdui/Displayable/addCommand(Ljavax/microedition/lcdui/Command;)V
Будет ли команда привязана к функциональной клавише или объединена
с другими командами в меню, зависит от реализации MIDP конкретного аппа-
рата. Команда может быть добавлена в объект не более одного раза, но может
быть включена одновременно в несколько объектов.
Интерфейс CommandListener
Как уже было замечено, сам объект команды
не предписывает программе выполнение каких-
либо действий, он является лишь связующим зве-
ном между исполнителем команды и формой.
Приемником и исполнителем команды может яв-
ляться любой объект, реализующий интерфейс
CommandListener, который имеет всего лишь один
метод:
; void commandAction(Command с, Displayable d)
.method public commandAction(Ljavax/microedition/lcdui/Command;Ljavax/microedition/lcdui/Displayable;)V
Этот метод нам нужно написать самим и реа-
лизовать там связанные с командами действия.
Метод commandAction вызовется автоматически при
выборе пользователем одной из команд, которая
и будет передана в метод в качестве аргумента.
В нашем случае удобно сделать так, чтобы ос-
новной класс мидлета содержал блок прослуши-
вания команд в себе самом. В общем случае блок
может содержаться в любом объекте. Таким об-
разом, основной класс мидлета будет расширять
класс MIDIet и одновременно реализовывать ин-
терфейс CommandListener:
; public class SlideShow extends MIDlet implements CommandListener
.class public SlideShow
.super javax/microedition/midlet/MIDlet
.implements javax/microedition/lcdui/CommandListener
Приемник команд, как и сами команды, должен быть привязан к объекту типа
Displayable, в нашем случае это форма. В каждый момент времени отобража-
емый объект может иметь только один приемник. Связь приемника команд
с отображаемым объектом осуществляется с помощью метода класса Displayable:
; void setCommandListener(CommandListener l)
aload_0
getfield MyClassName/MyDisplayableName Ljavax/microedition/lcdui/MyDisplayable;
aload_0
invokevirtual javax/microedition/lcdui/Displayable/setCommandListener(Ljavax/microedition/lcdui/CommandListener;)V
В нашем случае, когда основной класс мидлета реализует блок прослушивания команд,
в качестве аргумента передается ссылка на главный объект.
Теперь у нас есть все необходимые средства для реализации полноценного фото-
альбома. Подготовим картинки для фотоальбома, учитывая размер экрана и мак-
симальный объем приложения вашей модели телефона. Назовем картинки, кото-
рые мы хотим демонстрировать, так: l.png, 2.png и т. д. — и разместим их в папке /
res. Заодно добавим два члена класса SlideShow: int slideNum и int maxSlideNum,
которые будут содержать текущий номер картинки и общее количество картинок
соответственно.
Далее реализуем обработку команд и добавим логику для смены картинок на эк-
ране при нажатии клавиш. В конечном итоге программа полноценного фотоаль-
бома будет выглядеть следующим образом:
; Объявляем класс SlideShow
.class public SlideShow
.super javax/microedition/midlet/MIDlet
.implements javax/microedition/lcdui/CommandListener
; Display display - менеджер дисплея
.field private display Ljavax/microedition/lcdui/Display;
; Form form - отображаемая форма
.field private form Ljavax/microedition/lcdui/Form;
; Command next - команда перехода к следующей картинке
.field private next Ljavax/microedition/lcdui/Command;
; Command back - команда перехода к предыдущей картинке
.field private back Ljavax/microedition/lcdui/Command;
; Image image - объект картинки
.field private image Ljavax/microedition/lcdui/Image;
; int slideNum - номер текущей картинки
.field private slideNum I
; int maxSlideNum - общее количество картинок
.field private maxSlideNum I
; конструктор класса SlideShow
.method public ()V
.limit stack 2
.limit locals 1
aload_0
invokespecial javax/microedition/midlet/MIDlet/()V
; slideNum = 1 - присваиваем переменной slideNum значение 1
aload_0
iconst_1
putfield SlideShow/slideNum I
; maxSlideNum = 5 - присваиваем переменной maxSlideNum значение 5
aload_0
iconst_5
putfield SlideShow/maxSlideNum I
return
.end method
; "Стартовый" метод
.method public startApp()V
.limit stack 6
.limit locals 1
; display = Display.getDisplay(this) - получить ссылку на менеджер дисплея
aload_0
aload_0
invokestatic javax/microedition/lcdui/Display/getDisplay(Ljavax/microedition/midlet/MIDlet;)Ljavax/microedition/lcdui/Display;
putfield SlideShow/display Ljavax/microedition/lcdui/Display;
; form = new Form("Фотоальбом") - создать новую форму с заголовком "Фотоальбом"
aload_0
new javax/microedition/lcdui/Form
dup
ldc "Фотоальбом"
invokespecial javax/microedition/lcdui/Form/(Ljava/lang/String;)V
putfield SlideShow/form Ljavax/microedition/lcdui/Form;
; form.setCommandListener(this) - установить приемник команд для формы
aload_0
getfield SlideShow/form Ljavax/microedition/lcdui/Form;
aload_0
invokevirtual javax/microedition/lcdui/Displayable/setCommandListener(Ljavax/microedition/lcdui/CommandListener;)V
; next = new Command("Вперед", Command.OK, 1) - создать команду перехода к следующей картинке
aload_0
new javax/microedition/lcdui/Command
dup
ldc "Вперед"
iconst_4
iconst_1
invokespecial javax/microedition/lcdui/Command/(Ljava/lang/String;II)V
putfield SlideShow/next Ljavax/microedition/lcdui/Command;
; form.addCommand(next) - добавить команду в форму
aload_0
getfield SlideShow/form Ljavax/microedition/lcdui/Form;
aload_0
getfield SlideShow/next Ljavax/microedition/lcdui/Command;
invokevirtual javax/microedition/lcdui/Displayable/addCommand(Ljavax/microedition/lcdui/Command;)V
; back = new Command("Назад", Command.BACK, 1) - создать команду возврата к предыдущей картинке
aload_0
new javax/microedition/lcdui/Command
dup
ldc "Назад"
iconst_2
iconst_1
invokespecial javax/microedition/lcdui/Command/(Ljava/lang/String;II)V
putfield SlideShow/back Ljavax/microedition/lcdui/Command;
; form.addCommand(back) - добавить команду в форму
aload_0
getfield SlideShow/form Ljavax/microedition/lcdui/Form;
aload_0
getfield SlideShow/back Ljavax/microedition/lcdui/Command;
invokevirtual javax/microedition/lcdui/Displayable/addCommand(Ljavax/microedition/lcdui/Command;)V
; setImage("/1.png") - вызвать (наш) метод для добавления в форму картинки 1.png
aload_0
ldc "/1.png"
invokespecial SlideShow/setImage(Ljava/lang/String;)V
; display.setCurrent(form) - отобразить форму на экране
aload_0
getfield SlideShow/display Ljavax/microedition/lcdui/Display;
aload_0
getfield SlideShow/form Ljavax/microedition/lcdui/Form;
invokevirtual javax/microedition/lcdui/Display/setCurrent(Ljavax/microedition/lcdui/Displayable;)V
return
.end method
.method public pauseApp()V
.limit stack 0
.limit locals 1
return
.end method
.method public destroyApp(Z)V
.limit stack 0
.limit locals 2
return
.end method
.method public commandAction(Ljavax/microedition/lcdui/Command;Ljavax/microedition/lcdui/Displayable;)V
.limit stack 3
.limit locals 3
; if(с==next && slideNum1) - если была выбрана софт-команда back и slideNum>1
aload_1
aload_0
getfield SlideShow/back Ljavax/microedition/lcdui/Command;
if_acmpne LABEL2
aload_0
getfield SlideShow/slideNum I
iconst_1
if_icmple LABEL2
; slideNum-- - то уменьшить slideNum на 1
aload_0
dup
getfield SlideShow/slideNum I
iconst_1
isub
putfield SlideShow/slideNum I
LABEL2:
; form.delete(0) - удалить из формы текущую картинку
aload_0
getfield SlideShow/form Ljavax/microedition/lcdui/Form;
iconst_0
invokevirtual javax/microedition/lcdui/Form/delete(I)V
; setImage("/"+Integer.toString(slideNum)+".png") - получить имя файла картинки из ее номера
; и вызвать (наш) метод для добавления плученной картинки в форму
aload_0
new java/lang/StringBuffer
dup
invokespecial java/lang/StringBuffer/()V
ldc "/"
invokevirtual java/lang/StringBuffer/append(Ljava/lang/String;)Ljava/lang/StringBuffer;
aload_0
getfield SlideShow/slideNum I
invokestatic java/lang/Integer/toString(I)Ljava/lang/String;
invokevirtual java/lang/StringBuffer/append(Ljava/lang/String;)Ljava/lang/StringBuffer;
ldc ".png"
invokevirtual java/lang/StringBuffer/append(Ljava/lang/String;)Ljava/lang/StringBuffer;
invokevirtual java/lang/StringBuffer/toString()Ljava/lang/String;
invokespecial SlideShow/setImage(Ljava/lang/String;)V
return
.end method
; private void setImage(String path) - создадим метод который принимает имя файла картинки в качестве аргумента,
; создает объект картинки и добавляет в форму
.method private setImage(Ljava/lang/String;)V
.limit stack 2
.limit locals 3
LABEL1:
; image = Image.createlmage(path) - создать картинку из файла переданного в аргументе
aload_0
aload_1
invokestatic javax/microedition/lcdui/Image/createImage(Ljava/lang/String;)Ljavax/microedition/lcdui/Image;
putfield SlideShow/image Ljavax/microedition/lcdui/Image;
LABEL2:
goto LABEL3
LABEL4:
; обработать исключение, если файл не может быть открыт
astore_2
LABEL3:
; form.append(image) - добавить картинку в форму
aload_0
getfield SlideShow/form Ljavax/microedition/lcdui/Form;
aload_0
getfield SlideShow/image Ljavax/microedition/lcdui/Image;
invokevirtual javax/microedition/lcdui/Form/append(Ljavax/microedition/lcdui/Image;)I
pop
return
; catch (IOException ioe)- обработка исключения
.catch java/io/IOException from LABEL1 to LABEL2 using LABEL4
.end method
Отметим, что для смены изображения на экране мы лишь помещаем новую кар-
тинку в отображаемую на экране форму, после чего картинка сразу появляется на
экране.
Поздравляю! Если вы не поленились оторваться от
книжки и реализовать вышеописанное, то загрузите по-
лучившийся мидлет в телефон, и вы будете всегда иметь
под рукой маленький фотоальбом. Можно пойти даль-
ше и добавить нехитрую логику перехода к первой кар-
тинке после демонстрации последней и наоборот.
Класс Alert
Класс Alert представляет собой сообщение, состоящее
из изображения и текста, которое отображается
на экране в течение заданного количества време-
ни, после чего автоматически происходит смена
экрана. Класс Alert используется для информи-
рования пользователя об ошибках и возникших
исключительных ситуациях, а также для кратко-
временных информационных сообщений о ре-
зультатах выполнения операций.
Время демонстрации сообщения может быть бесконечно большим. Тогда сооб-
щение будет отображаться на экране до тех пор, пока пользователь вручную не
закроет его. Аналогичная ситуация возникает, если большое количество инфор-
мации требует прокрутки экрана. Остановимся на методах класса Alert:
; Alert(String title) — конструктор создающий пустое сообщение с заголовком title
aload_0
new javax/microedition/lcdui/Alert
dup
ldc "title"
invokespecial javax/microedition/lcdui/Alert/(Ljava/lang/String;)V
putfield MyClassName/MyAlertName Ljavax/microedition/lcdui/Alert;
; void setString(String str) — устанавливает текст str данному сообщению
aload_0
getfield MyClassName/MyAlertName Ljavax/microedition/lcdui/Alert;
ldc "str"
invokevirtual javax/microedition/lcdui/Alert/setString(Ljava/lang/String;)V
; void setTimeout(int time) — устанавливает новое время демонстрации сообщения.
; Время задается аргументом time в миллисекундах или константой FOREVER(-2) для неограниченного времени демонстрации сообщения
aload_0
getfield MyClassName/MyAlertName Ljavax/microedition/lcdui/Alert;
sipush 5000 ; здесь time = 5000 (5 сек.)
invokevirtual javax/microedition/lcdui/Alert/setTimeout(I)V
Пример вывода сообщения с использованием объекта Alert:
. . .
; Объявляем поля класса MyAlert:
; Alert alert - объект сообщения Alert
.field alert Ljavax/microedition/lcdui/Alert;
; Display display - менеджер дисплея
.field display Ljavax/microedition/lcdui/Display;
. . .
; "Стартовый" метод
.method public startApp()V
.limit stack 4
.limit locals 1
; Alert("Сообщение") - создаем объект сообщения Alert с заголовком "Сообщение"
aload_0
new javax/microedition/lcdui/Alert
dup
ldc "Сообщение"
invokespecial javax/microedition/lcdui/Alert/(Ljava/lang/String;)V
putfield MyAlert/alert Ljavax/microedition/lcdui/Alert;
; alert.setString("Это текст сообщения") - устанавливаем текст ("Это текст сообщения") данному сообщению
aload_0
getfield MyAlert/alert Ljavax/microedition/lcdui/Alert;
ldc "Это текст сообщения!"
invokevirtual javax/microedition/lcdui/Alert/setString(Ljava/lang/String;)V
; alert.setTimeout(5000) — устанавливаем время отображения сообщения в 5000 миллисек.(5сек.)
aload_0
getfield MyAlert/alert Ljavax/microedition/lcdui/Alert;
sipush 5000
invokevirtual javax/microedition/lcdui/Alert/setTimeout(I)V
; display = Display.getDisplay(this) - получаем ссылку на менеджер дисплея
aload_0
aload_0
invokestatic javax/microedition/lcdui/Display/getDisplay(Ljavax/microedition/midlet/MIDlet;)Ljavax/microedition/lcdui/Display;
putfield MyAlert/display Ljavax/microedition/lcdui/Display;
; display.setCurrent(alert) - Выводим сообщение на экран
aload_0
getfield MyAlert/display Ljavax/microedition/lcdui/Display;
aload_0
getfield MyAlert/alert Ljavax/microedition/lcdui/Alert;
invokevirtual javax/microedition/lcdui/Display/setCurrent(Ljavax/microedition/lcdui/Displayable;)V
; Выходим из метода
return
.end method
. . .
Класс TextBox
Класс TextBox является потомком класса Screen и представляет собой экран, по-
зволяющий вводить и редактировать текст с клавиатуры аппарата. При создании
объекта в конструкторе нужно указать заголовок поля ввода, начальное содержи-
мое поля, максимально допустимое количество вводимых символов и ограничи-
тель для вводимой информации:
; TextBox(String title, String text, int maxSize, int constraints)
aload_0
new javax/microedition/lcdui/TextBox
dup
ldc "title"
ldc "text"
bipush 100 ; максимально допустимое кол-во вводимых символов maxSize (100)
iconst_0 ; указываем ограничитель constraints (ANY)
invokespecial javax/microedition/lcdui/TextBox/(Ljava/lang/String;Ljava/lang/String;II)V
putfield MyClassName/MyTextboxName Ljavax/microedition/lcdui/TextBox;
Ограничители позволяют заранее задать тип вводимого текста. Так, например,
если информация содержит только цифры, то буквы не будут появляться в этом
поле при нажатии на клавиши. Для полей ввода доступны следующие ограничи-
тели, которые определяются константами класса TextField:
• ANY(iconst_0) — допускается ввод любых символов без ограничений;
• PASSWORD(iconst_1) — используется в комбинации с другими константами для скрытия вво-
димой информации. Вводимые символы отображаются звездочками, как при вводе PIN-кода.
NUMERIC(iconst_2) — применяется для ввода целых чисел. Клавиша * позволяет вводить
отрицательные числа и добавляет знак - перед числом;
• EMAILADDR(iconst_3) — используется для ввода адреса электронной почты;
• PHONENUMBER(iconst_4) — ввод телефонного номера. Поле ввода такого типа привязано
к стандартному вводу телефонного номера аппарата. Это значит, что в меню
автоматически появится команда поиска номера в записной книжке Search;
• URL(iconst_5) — ввод сетевого адреса интернет-страницы в формате URL;
После того как объект поля ввода создан, он может быть отображен на экране теле-
фона так же, как и все отображаемые объекты, наследованные из класса Displayable,
с помощью метода класса Display setCurrent(TextBox). Если вводимый текст превы-
шает размер экрана, система сама обеспечит прокрутку без каких-либо усилий
с нашей стороны. Дальнейшая работа с созданным объектом обеспечивается следу-
ющими методами класса TextBox:
; String getString() — возвращает содержимое поля ввода в виде строки
aload_0
aload_0
getfield MyClassName/MyTextBoxName Ljavax/microedition/lcdui/TextBox;
invokevirtual javax/microedition/lcdui/TextBox/getString()Ljava/lang/String;
putfield MyClassName/MyStringName Ljava/lang/String;
; void setString(String text) — заменяет текущее содержимое поля ввода на строку,заданную параметром text
aload_0
getfield MyClassName/MyTextBoxName Ljavax/microedition/lcdui/TextBox;
aload_0
getfield MyClassName/text Ljava/lang/String;
invokevirtual javax/microedition/lcdui/TextBox/setString(Ljava/lang/String;)V
Попробуем научить телефон здороваться! Напишем простенькую программу, которая запрашивает Ваше имя,
а после ввода и нажатия "ОК" выводит на экран приветствие - "Привет, [Ваше имя]!"
; Объявим класс Hello, наследуемый от MIDlet
; и реализующий интерфеc CommandListener для прослущивания софт-команд
.class public Hello
.super javax/microedition/midlet/MIDlet
.implements javax/microedition/lcdui/CommandListener
; объявим необходимые поля:
; TextBox textbox - объект ввода и редактирования текста
.field private textbox Ljavax/microedition/lcdui/TextBox;
; Command okCommand - команда Ок
.field private okCommand Ljavax/microedition/lcdui/Command;
; String buff - текстовая переменная для хранения введенного имени
.field private buff Ljava/lang/String;
; Display display - менеджер дисплея
.field private display Ljavax/microedition/lcdui/Display;
. . .
.method public startApp()V
.limit stack 7
.limit locals 1
; textbox = new TextBox("Ваше имя:", "", 20, 0) - создадим объект ввода и редактирования текста
aload_0
new javax/microedition/lcdui/TextBox
dup
ldc "Ваше имя:"
ldc ""
bipush 20
iconst_0
invokespecial javax/microedition/lcdui/TextBox/(Ljava/lang/String;Ljava/lang/String;II)V
putfield Hello/textbox Ljavax/microedition/lcdui/TextBox;
; okCommand = new Command("Ok", Command.OK, 1) - создадим команду "Ок"
aload_0
new javax/microedition/lcdui/Command
dup
ldc "Ok"
iconst_4
iconst_1
invokespecial javax/microedition/lcdui/Command/(Ljava/lang/String;II)V
putfield Hello/okCommand Ljavax/microedition/lcdui/Command;
; textbox.addCommand(okCommand) - добавим команду "Ок" в TextBox
aload_0
getfield Hello/textbox Ljavax/microedition/lcdui/TextBox;
aload_0
getfield Hello/okCommand Ljavax/microedition/lcdui/Command;
invokevirtual javax/microedition/lcdui/Displayable/addCommand(Ljavax/microedition/lcdui/Command;)V
; textbox.setCommandListener(this) - установим приемник софт-команд для TextBox
aload_0
getfield Hello/textbox Ljavax/microedition/lcdui/TextBox;
aload_0
invokevirtual javax/microedition/lcdui/Displayable/setCommandListener(Ljavax/microedition/lcdui/CommandListener;)V
; display = Display.getDisplay(this) - получим ссылку на менеджер дисплея
aload_0
aload_0
invokestatic javax/microedition/lcdui/Display/getDisplay(Ljavax/microedition/midlet/MIDlet;)Ljavax/microedition/lcdui/Display;
putfield Hello/display Ljavax/microedition/lcdui/Display;
; display.setCurrent(textbox) - отобразим TextBox на экране
aload_0
getfield Hello/display Ljavax/microedition/lcdui/Display;
aload_0
getfield Hello/textbox Ljavax/microedition/lcdui/TextBox;
invokevirtual javax/microedition/lcdui/Display/setCurrent(Ljavax/microedition/lcdui/Displayable;)V
return
.end method
. . .
.method public commandAction(Ljavax/microedition/lcdui/Command;Ljavax/microedition/lcdui/Displayable;)V
.limit stack 3
.limit locals 3
; if(command == okCommand) - если была выбрана команда "Ок", тогда
aload_1
aload_0
getfield Hello/okCommand Ljavax/microedition/lcdui/Command;
if_acmpne Label
; buff = textbox.getString() - получаем введенный текст и сохраняем его в переменной buff
aload_0
aload_0
getfield Hello/textbox Ljavax/microedition/lcdui/TextBox;
invokevirtual javax/microedition/lcdui/TextBox/getString()Ljava/lang/String;
putfield Hello/buff Ljava/lang/String;
; buff = "Привет, " + buff + "!" - формируем строку приветствия
aload_0
new java/lang/StringBuffer
dup
invokespecial java/lang/StringBuffer/()V
ldc "Привет, "
invokevirtual java/lang/StringBuffer/append(Ljava/lang/String;)Ljava/lang/StringBuffer;
aload_0
getfield Hello/buff Ljava/lang/String;
invokevirtual java/lang/StringBuffer/append(Ljava/lang/String;)Ljava/lang/StringBuffer;
ldc "!"
invokevirtual java/lang/StringBuffer/append(Ljava/lang/String;)Ljava/lang/StringBuffer;
invokevirtual java/lang/StringBuffer/toString()Ljava/lang/String;
putfield Hello/buff Ljava/lang/String;
; textbox.setString(buff) - заменяет текущее содержимое поля ввода на строку приветствия (buff)
aload_0
getfield Hello/textbox Ljavax/microedition/lcdui/TextBox;
aload_0
getfield Hello/buff Ljava/lang/String;
invokevirtual javax/microedition/lcdui/TextBox/setString(Ljava/lang/String;)V
Label:
return
.end method
Класс List
Простейший вариант организации меню — использование готового интерфейса
высокого уровня, предоставляющего все необходимые функции навигации и вы-
бора одного из пунктов. Меню представлено классом List, который наследован от
класса Screen, то есть является объектом, готовым к демонстрации через менед-
жер дисплея. Создается объект меню с помощью конструктора:
; List(String title, int listType)
aload_0
new javax/microedition/lcdui/List
dup
ldc "title"
iconst_3 ; тип меню(listType)
invokespecial javax/microedition/lcdui/List/(Ljava/lang/String;I)V
putfield MyClassName/MyListName Ljavax/microedition/lcdui/List;
Конструктор создает пустой объект, в который можно будет добавить не-
обходимые пункты. Параметр title задает заголовок меню,
а listType определяет один из трех типов меню,
представленных следующими константами класса List:
• IMPLICIT(iconst_3) — каждое перемещение по пунктам меню вызывает немедленное опове-
щение через блок прослушивания команд. Класс List содержит особую команду
Command SELECT_COMMAND, которая формируется при выборе нового пункта и пере-
дается в качестве аргумента в метод блока прослушивания commandAction(Command,Displayable);
• EXCLUSIVE(iconst_1) — позволяет выбор единственного пункта меню; оповещения прило-
жения не генерируются;
• MULTIPLE(iconst_2) — позволяет выбор нескольких пунктов меню в любом сочетании; опо-
вещения не генерируются.
Каждый из пунктов меню может находиться в двух состояниях — выбранный или
невыбранный. В режимах работы IMPLICIT и EXCLUSIVE выбранным может быть толь-
ко один пункт меню, в режиме MULTIPLE выбранными могут быть несколько пунк-
тов меню одновременно.
Класс List расширяет класс Screen и реализует интерфейс Choice. Все его методы
определяются реализуемым интерфейсом.
Интерфейс Choice определяет набор методов для элементов пользовательского
интерфейса верхнего уровня, предоставляющих возможность выбора из несколь-
ких предложенных вариантов (класс List и класс ChoiceGroup). Каждый из вари-
антов представляется текстовой строкой и необязательным графическим изобра-
жением — пиктограммой.
Для организации работы с меню интерфейс Choice определяет следующие мето-
ды, реализованные в классе List:
; int append(String stringPart, Image imagePart) — добавляет новый пункт меню,
; представленный строкой stringPart и картинкой imagePart, в конец списка
; и возвращает присвоенный элементу индекс
aload_0
getfield MyClassName/MyListName Ljavax/microedition/lcdui/List;
ldc "stringPart"
aconst_null ; здесь указываем картинку(в данном примене ее нет - null)
invokevirtual javax/microedition/lcdui/List/append(Ljava/lang/String;Ljavax/microedition/lcdui/Image;)I
pop
; int getSelectedIndex() — возвращает индекс выделенного пункта меню.
; В режиме MULTIPLE всегда возвращает значение -1 ;
aload_0
getfield MyClassName/MyListName Ljavax/microedition/lcdui/List;
invokevirtual javax/microedition/lcdui/List/getSelectedIndex()I
istore_3 ; здесь загружаем индекс в локальную переменную(3)
Следующая программа демонстрирует работу меню.
При выборе какого-либо пункта меню отображается форма,
в которой указывается выбранный пункт.
. . .
; Объявляем следующие поля класса Menu:
; private List list - Объект меню
.field private list Ljavax/microedition/lcdui/List;
; private Command exit - команда выхода из программы
.field private exit Ljavax/microedition/lcdui/Command;
; private Form form - форма для вывода информации
.field private form Ljavax/microedition/lcdui/Form;
; private Command back - команда возврата в меню
.field private back Ljavax/microedition/lcdui/Command;
; private Display display - менеджер дисплея
.field private display Ljavax/microedition/lcdui/Display;
. . .
.method public startApp()V
.limit stack 6
.limit locals 1
; list = new List("Меню",List.IMPLICIT) - создаем меню
aload_0
new javax/microedition/lcdui/List
dup
ldc "Меню"
iconst_3
invokespecial javax/microedition/lcdui/List/(Ljava/lang/String;I)V
putfield Menu/list Ljavax/microedition/lcdui/List;
; list.append("Первый пункт",null) - добавляем первый пункт меню
aload_0
getfield Menu/list Ljavax/microedition/lcdui/List;
ldc Первый пункт"
aconst_null
invokevirtual javax/microedition/lcdui/List/append(Ljava/lang/String;Ljavax/microedition/lcdui/Image;)I
pop
; list.append("Второй пункт",null) - добавляем второй пункт меню
aload_0
getfield Menu/list Ljavax/microedition/lcdui/List;
ldc "Второй пункт"
aconst_null
invokevirtual javax/microedition/lcdui/List/append(Ljava/lang/String;Ljavax/microedition/lcdui/Image;)I
pop
; list.append("Третий пункт",null) - добавляем третий пункт меню
aload_0
getfield Menu/list Ljavax/microedition/lcdui/List;
ldc Третий пункт"
aconst_null
invokevirtual javax/microedition/lcdui/List/append(Ljava/lang/String;Ljavax/microedition/lcdui/Image;)I
pop
; exit = new Command("Выход",Command.EXIT,2) - создаем команду выхода из программы
aload_0
new javax/microedition/lcdui/Command
dup
ldc "Выход"
bipush 7
iconst_2
invokespecial javax/microedition/lcdui/Command/(Ljava/lang/String;II)V
putfield Menu/exit Ljavax/microedition/lcdui/Command;
; list.addCommand(exit) - добавляем команду в меню
aload_0
getfield Menu/list Ljavax/microedition/lcdui/List;
aload_0
getfield Menu/exit Ljavax/microedition/lcdui/Command;
invokevirtual javax/microedition/lcdui/Displayable/addCommand(Ljavax/microedition/lcdui/Command;)V
; list.setCommandListener(this) - устанавливаем прослушивание команд в меню
aload_0
getfield Menu/list Ljavax/microedition/lcdui/List;
aload_0
invokevirtual javax/microedition/lcdui/Displayable/setCommandListener(Ljavax/microedition/lcdui/CommandListener;)V
; display = Display.getDisplay(this) - получаем ссылку на менеджер дисплея
aload_0
aload_0
invokestatic javax/microedition/lcdui/Display/getDisplay(Ljavax/microedition/midlet/MIDlet;)Ljavax/microedition/lcdui/Display;
putfield Menu/display Ljavax/microedition/lcdui/Display;
; display.setCurrent(list) - отображаем меню на экране
aload_0
getfield Menu/display Ljavax/microedition/lcdui/Display;
aload_0
getfield Menu/list Ljavax/microedition/lcdui/List;
invokevirtual javax/microedition/lcdui/Display/setCurrent(Ljavax/microedition/lcdui/Displayable;)V
; form = new Form("Выбор:") - создадим форму для отображения выбранных пунктов меню
aload_0
new javax/microedition/lcdui/Form
dup
ldc "Выбор:"
invokespecial javax/microedition/lcdui/Form/(Ljava/lang/String;)V
putfield Menu/form Ljavax/microedition/lcdui/Form;
; back = new Command("Назад",Command.BACK,1) - создадим команду возврата в меню
aload_0
new javax/microedition/lcdui/Command
dup
ldc "Назад"
iconst_2
iconst_1
invokespecial javax/microedition/lcdui/Command/(Ljava/lang/String;II)V
putfield Menu/back Ljavax/microedition/lcdui/Command;
; form.addCommand(back) - добавим команду в форму
aload_0
getfield Menu/form Ljavax/microedition/lcdui/Form;
aload_0
getfield Menu/back Ljavax/microedition/lcdui/Command;
invokevirtual javax/microedition/lcdui/Displayable/addCommand(Ljavax/microedition/lcdui/Command;)V
; form.setCommandListener(this) - устанавливаем прослушивание команд в форме
aload_0
getfield Menu/form Ljavax/microedition/lcdui/Form;
aload_0
invokevirtual javax/microedition/lcdui/Displayable/setCommandListener(Ljavax/microedition/lcdui/CommandListener;)V
return
.end method
. . .
.method public commandAction(Ljavax/microedition/lcdui/Command;Ljavax/microedition/lcdui/Displayable;)V
.limit stack 2
.limit locals 4
; if(command == List.SELECT_COMMAND){ - если выбран какой-либо пункт меню, тогда,
aload_1
getstatic javax/microedition/lcdui/List/SELECT_COMMAND Ljavax/microedition/lcdui/Command;
if_acmpne Label70
; int choice = list.getSelectedIndex() - получим индекс выбранного пункта и сохраним в локальной переменной choice
aload_0
getfield Menu/list Ljavax/microedition/lcdui/List;
invokevirtual javax/microedition/lcdui/List/getSelectedIndex()I
istore_3
; if(choice == 0) - если выбран первый пункт, тогда,
iload_3
ifne Label29
; form.append("Выбран первый пункт меню") - добавляем соответствующий текст в форму
aload_0
getfield Menu/form Ljavax/microedition/lcdui/Form;
ldc "Выбран первый пункт меню"
invokevirtual javax/microedition/lcdui/Form/append(Ljava/lang/String;)I
pop
Label29:
; if(choice == 1) - если выбран второй пункт, тогда,
iload_3
iconst_1
if_icmpne Label44
; form.append("Выбран второй пункт меню") - добавляем соответствующий текст в форму
aload_0
getfield Menu/form Ljavax/microedition/lcdui/Form;
ldc "Выбран второй пункт меню"
invokevirtual javax/microedition/lcdui/Form/append(Ljava/lang/String;)I
pop
Label44:
; if(choice == 2) - если выбран третий пункт, тогда,
iload_3
iconst_2
if_icmpne Label59
; form.append("Выбран третий пункт меню") - добавляем соответствующий текст в форму
aload_0
getfield Menu/form Ljavax/microedition/lcdui/Form;
ldc "Выбран третий пункт меню"
invokevirtual javax/microedition/lcdui/Form/append(Ljava/lang/String;)I
pop
Label59:
; display.setCurrent(form) }- отображаем форму на экране
aload_0
getfield Menu/display Ljavax/microedition/lcdui/Display;
aload_0
getfield Menu/form Ljavax/microedition/lcdui/Form;
invokevirtual javax/microedition/lcdui/Display/setCurrent(Ljavax/microedition/lcdui/Displayable;)V
Label70:
; if(command == back) - если выбрана команда возврата в меню, тогда,
aload_1
aload_0
getfield Menu/back Ljavax/microedition/lcdui/Command;
if_acmpne Label89
; display.setCurrent(list) - отображаем меню.
aload_0
getfield Menu/display Ljavax/microedition/lcdui/Display;
aload_0
getfield Menu/list Ljavax/microedition/lcdui/List;
invokevirtual javax/microedition/lcdui/Display/setCurrent(Ljavax/microedition/lcdui/Displayable;)V
Label89:
; if(command == exit){ - если выбрана команда выхода, тогда,
aload_1
aload_0
getfield Menu/exit Ljavax/microedition/lcdui/Command;
if_acmpne Label106
; destroyApp(false) - вызываем метод destroyApp,
aload_0
iconst_0
invokevirtual Menu/destroyApp(Z)V
; notifyDestroyed() } - и информируем систему о выходе.
aload_0
invokevirtual javax/microedition/midlet/MIDlet/notifyDestroyed()V
Label106:
return
.end method
Итак, основа проектирования любого приложения — создание структуры демон-
стрирования различных отображаемых объектов и реализация логики перехо-
дов между этими объектами. Логику переходов можно организовать с помощью
пользовательского интерфейса высокого уровня, который мы рассмотрели в этой
главе. Для этого нужно спроектировать отображаемые объекты, добавить к ним
требуемые команды и реализовать интерфейс прослушивания и исполнения ко-
манд. Приложения, организованные таким образом, более транспортабельны
и могут быть перенесены на различные модели телефонов. Пользовательский
интерфейс низкого уровня мы рассмотрим в следующей главе.
Глава 4 Работа с графикой
В прошлой главе мы рассмотрели пример работы с пользовательским интерфейсом
высокого уровня, который представлен иерархией класса Screen. При работе
с объектами такого типа мы не имели прямого доступа к экрану аппарата, а пользова-
лись формой как визуальной абстракцией высокого уровня. Изменения изображе-
ния на экране осуществлялись с помощью методов формы и добавленных в нее
объектов. То есть, работая с высокоуровневым интерфейсом, при всем желании
нарисовать поперек экрана, например, жирную синюю полосу мы не сможем.
Решить проблему непосредственного доступа к экрану нам поможет низкоуров-
невый пользовательский интерфейс, который представлен второй веткой иерар-
хии отображаемых объектов типа Displayable — классом Canvas.
Класс Canvas
Класс Canvas является абстрактным. Это значит, что мы не можем просто создать
объект этого класса.
В объектно-ориентированном программировании абстрактными называются такие классы,
которые содержат в себе хотя бы один абстрактный метод. Использовать абстрактные
классы можно только как основу для новых классов, в которых
обязательна реализация абстрактного метода.
В классе Canvas абстрактным является метод abstract void paint(Graphics g), кото-
рый отвечает за перерисовку экрана и получает в качестве аргумента объект клас-
са Graphics, который создается автоматически при инициализации объекта Canvas.
Класс Graphics
Собственно класс Graphics и предоставляет возможности низкоуровневого гра-
фического рисования. Возможности класса Graphics далеко не безграничны: он
содержит методы для рисования и заливки базовых геометрических фигур, таких
как линии, прямоугольники и дуги. Также поддерживаются возможности отобра-
жения текстовых символов и смены цвета для рисования.
Теперь все в наших руках, включая и все точки экрана, каждая из которых, кста-
ти, имеет свои координаты по осям х и у. Начало координат (точка 0,0) находится
в левом верхнем углу экрана. То есть нужно учесть, что ось у направлена вниз.
А теперь перейдем к рисаванию и рассмотрим методы класса Graphics, которые
могут нам понадобиться. В первую очередь заметим, что размеры экрана у разных
моделей могут серьезно различаться, поэтому сразу будем писать транспортабель-
ные приложения. Таким образом, первым делом нам требуется получить размеры
экрана конкретного аппарата. Делается это с помощью двух методов:
; int getClipHeightO — возвращает высоту текущей области для рисования
aload_1
invokevirtual javax/microedition/lcdui/Graphics/getClipHeight()I
istore_3 ; сохраняем результат в локальную переменную № 3
; int getClipWidth() — возвращает ширину текущей области для рисования
aload_1
invokevirtual javax/microedition/lcdui/Graphics/getClipWidth()I
istore_2 ; сохраняем результат в локальную переменную № 2
Сам класс Canvas также содержит методы для определения размеров отобража-
емой области экрана, которые не зависят от текущей области для рисования
и остаются неизменными во время работы приложения:
; int getHeight() — возвращает высоту отображаемой области экрана
aload_0
aload_0
invokevirtual javax/microedition/lcdui/Canvas/getHeight()I
putfield MyClassName/height I ; сохраняем результат в "глобальную" переменную height
; int getWidth() — возвращает ширину отображаемой области экрана
aload_0
aload_0
invokevirtual javax/microedition/lcdui/Canvas/getWidth()I
putfield MyClassName/width I ; сохраняем результат в "глобальную" переменную width
Рассмотрим несколько методов для отображения на экране базовых геометрических фигур:
; void drawLine(int xl, int yl, int x2, int y2) — рисует линию из точки с координатами (x1, y1)
; в точку с координатами (х2, у2). В классе Graphics нет метода длярисования одной точки,
; поэтому отдельная точка рисуется с помощью линии, начало и конец которой заданы одними и теми же координатами
aload_1
iconst_0 ; x1 (0)
iconst_0 ; y1 (0)
bipush 100 ; x2 (100)
bipush 100 ; y2 (100)
invokevirtual javax/microedition/lcdui/Graphics/drawLine(IIII)V
; void drawRect(int x, int y, int width, int height) — рисует прямоугольник с левым верхним углом в точке (х, у),
; шириной width и высотой height пикселов
aload_1
iconst_5 ; x (5)
iconst_5 ; y (5)
bipush 50 ; width (50)
bipush 30 ; height (30)
invokevirtual javax/microedition/lcdui/Graphics/drawRect(IIII)V
; void drawArc(int x, int y, int width. int height, int startAngle, int arcAngle) - рисует дугу,
; вписанную в прямоугольник с левым верхним углом в точкес координатами (х,у).
; Параметр startAngle задает угол, с которого будет начинаться дуга, а параметр arcAngle задает угловое расстояние,
; на которое будет простираться дуга. Углы задаются в градусах.
; Таким образом, чтобы нарисовать окружность, нужно в квадрат вписать дугу с угловым расстоянием 360.
aload_1
iconst_5 ; x (5)
iconst_5 ; y (5)
bipush 50 ; width (50)
bipush 30 ; height (30)
iconst_0 ; startAngle (0)
bipush 90 ; arcAngle (90)
invokevirtual javax/microedition/lcdui/Graphics/drawArc(IIIIII)V
До сих пор, по умолчанию, мы рисовали черным по белому, но в настоящее время
аппаратов с цветными дисплеями, которые поддерживают технологию J2ME, го-
раздо больше, чем их монохромных собратьев. Поэтому не будем ограничивать
свою фантазию и раскрасим экран нашего телефона веселыми красками.
В отличие от других языков программирования, в J2ME константы для определе-
ния цвета не заданы, поэтому придется нам самим, как настоящим художникам,
наносить на палитру три цветовых составляющих: красный, зеленый и синий (мо-
дель образования цвета RGB). Доля каждого из этих цветов задается числом от 0 до
255. Например, комбинация (0,0,255) задает синий цвет, а (0,0,0) — черный. Наши
краски не кончаются, поэтому экспериментировать мы можем сколько угодно. Цвет
рисования устанавливается с помощью метода класса Graphics:
; void setColor(int red, int green, int blue) — задает цвет рисования, заданный цветовыми составляющими red, green, blue
aload_1
iconst_0 ; red (0)
iconst_0 ; green (0)
iconst_0 ; blue (0)
invokevirtual javax/microedition/lcdui/Graphics/setColor(III)V
Теперь мы можем рисовать не только черным, что, несомненно, скрасит нашу
жизнь, но пока что только по белому. Изменить цвет фона или какой-то его части
можно с помощью методов рисования закрашенных фигур:
; void fillRect(int x, int у, int width, int height) — рисует закрашенный прямоугольник
aload_1
iconst_0 ; x (0)
iconst_0 ; y (0)
aload_0
getfield MyClassName/width I ; загружаем значение из глабальной переменной width
aload_0
getfield MyClassName/height I ; загружаем значение из глабальной переменной height
invokevirtual javax/microedition/lcdui/Graphics/fillRect(IIII)V
; void fillArc(int x, inty, int width, int height, int startAngle, int arcAngle) - рисует закрашенный сектор (или круг)
aload_1
bipush 60 ; x (60)
bipush 60 ; y (60)
bipush 30 ; width (30)
bipush 30 ; height (30)
iconst_0 ; startAngle (0)
sipush 360 ; arcAngle (360)
invokevirtual javax/microedition/lcdui/Graphics/fillArc(IIIIII)V
Все аргументы методов рисования закрашенных фигур идентичны аргументам
аналогичных методов рисования простых фигур. Фигуры закрашиваются теку-
щим цветом, и если мы хотим очистить экран, то нужно установить белый теку-
щим цветом и нарисовать закрашенный прямоугольник размером на весь экран.
Класс Graph
Самое время остановиться и попробовать использовать полученные знания на
практике. В качестве примера нарисуем график параболы. Красная парабола на
желтом фоне — разве это не прекрасно?!
Представим наш график новым классом Graph, который расширит класс низкоуров-
него интерфейса Canvas и реализует его абстрактный метод paint. В методе paint мы
зальем экран желтым фоном, нарисуем рамку и оси координат, сместим начало ко-
ординат в центр экрана, а затем в цикле вычислим координаты для каждой точки
параболы по формуле. Наш класс будет выглядеть следующим образом:
; Объявим "графический" класс Graph наследуемый от Canvas
.class public Graph
.super javax/microedition/lcdui/Canvas
; стандартный конструктор класса Graph
.method public ()V
.limit stack 1
.limit locals 1
aload_0
invokespecial javax/microedition/lcdui/Canvas/()V
return
.end method
; функция прорисовки экрана
.method public paint(Ljavax/microedition/lcdui/Graphics;)V
.limit stack 6
.limit locals 6
; int width = g.getClipWidth() - получить ширину экрана
aload_1
invokevirtual javax/microedition/lcdui/Graphics/getClipWidth()I
istore_2
; int height = g.getClipHeight() - получить высоту экрана
aload_1
invokevirtual javax/microedition/lcdui/Graphics/getClipHeight()I
istore_3
; g.setColor(255,255,0) - установить текущий цвет желтым
aload_1
sipush 255
sipush 255
iconst_0
invokevirtual javax/microedition/lcdui/Graphics/setColor(III)V
; g.fillRect(0,0,width,height) - нарисовать закрашенный прямоугольник размером на весь экран
aload_1
iconst_0
iconst_0
iload_2
iload_3
invokevirtual javax/microedition/lcdui/Graphics/fillRect(IIII)V
; g.setColor(0,0,0) - установить текущий цвет черным
aload_1
iconst_0
iconst_0
iconst_0
invokevirtual javax/microedition/lcdui/Graphics/setColor(III)V
; g.drawRect(0,0,width-1,height-1) - нарисовать рамку
aload_1
iconst_0
iconst_0
iload_2
iconst_1
isub
iload_3
iconst_1
isub
invokevirtual javax/microedition/lcdui/Graphics/drawRect(IIII)V
; g.drawLine(width/2,0,width/2,height) - нарисовать вертикальную ось координат
aload_1
iload_2
iconst_2
idiv
iconst_0
iload_2
iconst_2
idiv
iload_3
invokevirtual javax/microedition/lcdui/Graphics/drawLine(IIII)V
; g.drawLine(0,height/2,width,height/2) - нарисовать горизонтальную ось координат
aload_1
iconst_0
iload_3
iconst_2
idiv
iload_2
iload_3
iconst_2
idiv
invokevirtual javax/microedition/lcdui/Graphics/drawLine(IIII)V
; g.translate(width/2,height/2) - сместить начало координат в центр экрана
aload_1
iload_2
iconst_2
idiv
iload_3
iconst_2
idiv
invokevirtual javax/microedition/lcdui/Graphics/translate(II)V
; g.setColor(255,0,0) - установить текущий цвет красным
aload_1
sipush 255
iconst_0
iconst_0
invokevirtual javax/microedition/lcdui/Graphics/setColor(III)V
; for(int x=-width/2; x()V
astore_1
; display = Display.getDisplay(this) - получить ссылку на менеджер дисплея
aload_0
aload_0
invokestatic javax/microedition/lcdui/Display/getDisplay(Ljavax/microedition/midlet/MIDlet;)Ljavax/microedition/lcdui/Display;
putfield Graphs/display Ljavax/microedition/lcdui/Display;
; display.setCurrent(graph) - вывести график на экран
aload_0
getfield Graphs/display Ljavax/microedition/lcdui/Display;
aload_1
invokevirtual javax/microedition/lcdui/Display/setCurrent(Ljavax/microedition/lcdui/Displayable;)V
return
.end method
Компилируем, запускаем и наслаждаемся полученным
результатом.
Обратим внимание на формулу, по которой мы вы-
считывали координаты точек параболы: -х*х/40. Те-
перь попытаемся вспомнить школьный курс алгебры:
если первый коэффициент квадратного многочлена
отрицательный, то ветви параболы должны быть на-
правлены вниз. Однако на экране мы видим, что
ветви параболы направлены вверх. Дело в том, что
в телефоне ось у направлена не вверх, как в школь-
ных учебниках, а в обратном направлении.
Вторая проблема в том, что, как мы видим, область
для рисования, предоставленная нам аппаратом, мень-
ше, чем реальные размеры экрана, а сверху пристро-
илась незваная полоска с названием основного клас-
са мидлета. Действительно, зачастую разработчики
оставляют себе часть экрана для вспомогательной ин-
формации.
Полный экран можно получить двумя способами: ис-
пользовав функцию профайла MIDP2 или задействовав
специальную API-функцию производителя.
Рисование текста
Рисовать, конечно, здорово, но иногда на картинке требуется что-нибудь напи-
сать, например GAME OVER. В прошлой главе, когда мы отображали текст при помо-
щи интерфейса высокого уровня, форматирование строки происходило автома-
тически. Теперь ответственность за размещение текста на экране, выравнивание
и перенос строк целиком лежит на плечах программиста.
Позиция для текста задается при помощи координат точки размещения текста
и точки привязки. Отображаемая строка заключается в воображаемый прямо-
угольник, точка привязки которого будет помещена в заданные координаты.
; void drawString(String str, int x, int y, int anchor) — рисует строку str в указанной позиции
aload_1
ldc "str"
bipush 10 ; x (10)
bipush 20 ; y (20)
bipush 20 ; anchor (20)
invokevirtual javax/microedition/lcdui/Graphics/drawString(Ljava/lang/String;III)V
Обработка клавишных событий
Интерфейс низкого уровня позволяет нам отслеживать нажатия клавиш. Если
в интерфейсе высокого уровня события формировались только для добавлен-
ных команд, то интерфейс низкого уровня формирует события при нажатии
любой клавиши, причем теперь можно узнать, какая конкретно клавиша была
нажата.
Класс Canvas также определяет блоки прослушивания событий другого уровня.
Теперь в зависимости от действий пользователя будет автоматически вызван один
из следующих методов класса Canvas:
; void keyPressed(int keyCode) — вызывается, если какая-либо клавиша была нажата
.method public keyPressed(I)V
; void keyReleased(int keyCode) — вызывается, если какая-либо клавиша была отпущена
.method public keyReleased(I)V
; void keyRepeated(int keyCode) — вызывается при длительном нажатии клавиши
.method public keyRepeated(I)V
Все методы принимают аргумент, представляющий код нажимаемой клавиши.
Коды клавиш задаются следующими константами класса Canvas:
• KEY_NUM0 (48), KEY_NUM1 (49), KEY_NUM2 (50),... KEY_NUM9(57) - представляют цифры на клавиатуре
телефона;
• LEFT, RIGHT, UP, DOWN — представляют клавиши телефона, обозначенные стрелками;
• KEY_POUND — представляет клавишу с символом # (решетка);
• KEY_STAR — представляет клавишу с символом * (звездочка).
Для того чтобы привязать какие-то действия к желаемому событию, в классе-по-
томке класса Canvas необходимо переписать соответствующую функцию и там
реализовать необходимые действия.
Перейдем от теории к практике. Рассмотрим подробнее обработку событий низ-
коуровневого интерфейса на примере программы управляемой точки, рисующей
фигуры на экране мобильника.
Реализуем класс Point, наследованный из класса Canvas, который будет содер-
жать членами класса текущие координаты точки, размеры отображаемой облас-
ти экрана. Блок прослушивания событий низкого уровня будет отслеживать нажатия
клавиш и менять текущие координаты точки соответственно нажатой цифре.
Код класса Point выглядит следующим образом:
; Объявляем класс управляемой точки Point
.class public Point
; наследуемый от Canvas
.super javax/microedition/lcdui/Canvas
; Объявляем поля ("переменные") класса Point:
; private int x - координата x точки
.field private x I
; private int у - координата y точки
.field private y I
; private int width - ширина экрана
.field private width I
; private int height - высота экрана
.field private height I
; конструктор класса Point
.method public ()V
.limit stack 3
.limit locals 1
; super() - вызов конструктора родительского класса
aload_0
invokespecial javax/microedition/lcdui/Canvas/()V
; width=getWidth() - получить ширину экрана
aload_0
aload_0
invokevirtual javax/microedition/lcdui/Canvas/getWidth()I
putfield Point/width I
; height=getHeight() - получить высоту экрана
aload_0
aload_0
invokevirtual javax/microedition/lcdui/Canvas/getHeight()I
putfield Point/height I
; x=width/2 - установить текущие координаты x точки в центре экрана
aload_0
aload_0
getfield Point/width I
iconst_2
idiv
putfield Point/x I
; y=height/2 - установить текущие координаты y точки в центре экрана
aload_0
aload_0
getfield Point/height I
iconst_2
idiv
putfield Point/y I
return
.end method
; метод перерисовки экрана
.method protected paint(Ljavax/microedition/lcdui/Graphics;)V
.limit stack 5
.limit locals 2
; g.setColor(255,0,0) - установить текущий цвет точки красным
aload_1
sipush 255
iconst_0
iconst_0
invokevirtual javax/microedition/lcdui/Graphics/setColor(III)V
; g.drawLine(x,y,x,y) - отобразить точку в текущих координатах
aload_1
aload_0
getfield Point/x I
aload_0
getfield Point/y I
aload_0
getfield Point/x I
aload_0
getfield Point/y I
invokevirtual javax/microedition/lcdui/Graphics/drawLine(IIII)V
return
.end method
; этот метод вызывается автоматически при нажатии какой-либо клавиши
; и в качестве аргумента принимает код клавиши
.method public keyPressed(I)V
.limit stack 3
.limit locals 2
; switch (keyCode) { - переходим по метке, в зависимости от кода нажатой клавиши
iload_1
tableswitch 49 57
Label52 ; если keyCode=49 то goto Label52, и так далее...
Label75
Label88
Label111
Label193
Label124
Label137
Label160
Label173
default : Label193 ; иначе goto Label193
; сместить текущие координаты точки
; в соответствии с нажатой клавишей
Label52:
; если нажата[1], то x=x-1, y=y-1
aload_0
dup
getfield Point/x I
iconst_1
isub
putfield Point/x I
aload_0
dup
getfield Point/y I
iconst_1
isub
putfield Point/y I
goto Label193
Label75:
; если нажата[2], то y=y-1
aload_0
dup
getfield Point/y I
iconst_1
isub
putfield Point/y I
goto Label193
Label88:
; если нажата[3], то x=x+1, y=y-1
aload_0
dup
getfield Point/x I
iconst_1
iadd
putfield Point/x I
aload_0
dup
getfield Point/y I
iconst_1
isub
putfield Point/y I
goto Label193
Label111:
; если нажата[4], то x=x-1
aload_0
dup
getfield Point/x I
iconst_1
isub
putfield Point/x I
goto Label193
Label124:
; если нажата[6], то x=x+1
aload_0
dup
getfield Point/x I
iconst_1
iadd
putfield Point/x I
goto Label193
Label137:
; если нажата[7], то x=x-1, y=y+1
aload_0
dup
getfield Point/x I
iconst_1
isub
putfield Point/x I
aload_0
dup
getfield Point/y I
iconst_1
iadd
putfield Point/y I
goto Label193
Label160:
; если нажата[8], то y=y+1
aload_0
dup
getfield Point/y I
iconst_1
iadd
putfield Point/y I
goto Label193
Label173:
; если нажата[9], то x=x+1, y=y+1
aload_0
dup
getfield Point/x I
iconst_1
iadd
putfield Point/x I
aload_0
dup
getfield Point/y I
iconst_1
iadd
putfield Point/y I
Label193:
; repaint() - инициировать перерисовку экрана
aload_0
invokevirtual javax/microedition/lcdui/Canvas/repaint()V
return
.end method
Обратим внимание на то, что в отличие от работы с формой, изменения изобра-
жения не сразу отражаются на экране. Перерисовка экрана, реализованная в ме-
тоде paint, должна быть вызвана явно, с помощью метода:
; void repaint() — перерисовка экрана целиком
aload_0
invokevirtual javax/microedition/lcdui/Canvas/repaint()V
Сам же метод paint вызывается внутренней реализацией, мы никогда не будем
вызывать его явно.
Основная работа выполнена. Отображение управляемой точки на экране выпол-
няется в стартовом методе мидлета (FloatPoint) точно так же, как и в предыдущем случае:
.method public startApp()V
.limit stack 3
.limit locals 1
; Point point = new Point() - создать объект управляемой точки
aload_0
new Point
dup
invokespecial Point/()V
putfield FloatPoint/point LPoint;
; display = Display.getDisplay(this) - получить ссылку на менеджер дисплея
aload_0
aload_0
invokestatic javax/microedition/lcdui/Display/getDisplay(Ljavax/microedition/midlet/MIDlet;)Ljavax/microedition/lcdui/Display;
putfield FloatPoint/display Ljavax/microedition/lcdui/Display;
; display.setCurrent(point) вывести управляемую точку на экран
aload_0
getfield FloatPoint/display Ljavax/microedition/lcdui/Display;
aload_0
getfield FloatPoint/point LPoint;
invokevirtual javax/microedition/lcdui/Display/setCurrent(Ljavax/microedition/lcdui/Displayable;)V
return
.end method
В этой главе мы рассмотрели работу с пользова-
тельским интерфейсом низкого уровня, а также
графические возможности языка J2ME и логику
обработки событий. Поскольку класс Canvas не
является потомком класса Screen, он не исполь-
зует ни одной высокоуровневой абстракции, оп-
ределяемой иерархией класса Screen, например
добавление заголовка или бегущей строки невоз-
можно. С другой стороны, класс Canvas принад-
лежит к иерархии отображаемых объектов типа
Displayable, поэтому он может задействовать об-
работку команд высокого уровня и реализовать
блок прослушивания команд.
Для использования интерфейса низкого уров-
ня необходимо создать новый класс, расширя-
ющий класс Canvas, и реализовать его абстракт-
ный метод paint, который и будет осуществлять
прорисовку экрана. В этой главе мы также рас-
смотрели большинство возможностей и мето-
дов классов Graphics и Canvas. Кое-что осталось
на самостоятельное изучение, благо корпора-
ция Sun Microsystems предоставляет подроб-
ную документацию языка.
Глава 5 Сохранение данных и параметров приложения (RMS)
В этой главе мы рассмотрим, как организовать долговременное хранение данных в мобильном телефоне.
Хранение данных организовано в J2ME с помощью системы управления записями (Record Management System),
реализованной в пакете javax/microedition/rms.
Данные представлены записями (массив байтов типа byte[]), которые помеща-
ются в хранилище записей (Record Store) и не стираются при закрытии приложе-
ния, перезагрузке телефона или замене батареи. Размер и физическое местополо-
жение хранилища в памяти телефона зависят от конкретной модели аппарата.
Хранилище жестко привязано к мидлету, и когда вы удаляете приложение из сво-
его телефона, то все данные, относящиеся к нему, удаляются вместе с ним. По
сути, хранилище записей — это база данных.
Применения для RMS могут быть разные, от сохранения параметров и пользова-
тельских настроек приложения до поддержки полноценной адресной книги с ис-
черпывающей личной информацией и гибким поиском.
RecordStore — хранилище записей
Итак, хранилище записей представлено объектом класса RecordStore, работа с ко-
торым начинается с метода открытия хранилища для дальнейшей работы:
; static RecordStore openRecordStore(String recordStoreName, boolean createlfNecessary)
; Первый аргумент метода — уникальное имя хранилища.
; Второй аргумент указывает, должно ли быть создано новое хранилище с таким именем, если оно еще не существует.
aload_0
ldc "recordStoreName"
iconst_1 ; createlfNecessary (true)
invokestatic javax/microedition/rms/RecordStore/openRecordStore(Ljava/lang/String;Z)Ljavax/microedition/rms/RecordStore;
putfield MyClassName/MyRecordStoreName Ljavax/microedition/rms/RecordStore;
Имя хранилища чувствительно к регистру, длина имени не должна превышать 32 символа.
Добавление записи в хранилище осуществляется с помощью метода:
; int addRecord (byte[] data, int offset, int numBytes)
; Сохраняемые данные должны быть представлены последовательностью байтов, содержащихся в массиве data.
; Переменная offset указывает, с какой позиции в массиве начинаются данные, предназначенные для записи,
; a numBytes определяет размер сохраняемых данных.
; Метод возвращает номер новой записи, присвоенный ей при сохранении (recordID)
aload_0
getfield MyClassName/MyRecordStoreName Ljavax/microedition/rms/RecordStore;
aload_2 ; data
iconst_0 ; offset (0)
bipush 100 ; numBytes (100)
invokevirtual javax/microedition/rms/RecordStore/addRecord([BII)I
pop
Рассмотрим вкратце еще некоторые методы хранилища записей RecordStore:
; void deleteRecord(int recordId) — удалить из хранилища запись с номером recordId;
aload_0
getfield MyClassName/MyRecordStoreName Ljavax/microedition/rms/RecordStore;
iconst_1 ; recordId (1)
invokevirtual javax/microedition/rms/RecordStore/deleteRecord(I)V
; static void deleteRecordStore(String recordStoreName) — удалить хранилище записей с именем recordStoreName
ldc "recordStoreName"
invokestatic javax/microedition/rms/RecordStore/deleteRecordStore(Ljava/lang/String;)V
; void setRecord(int recordId, byte[] newData, int offset, int numBytes) — изменить данные, содержащиеся в записи с номером recordld
; остальные параметры идентичны параметрам метода addRecord.
aload_0
getfield MyClassName/MyRecordStoreName Ljavax/microedition/rms/RecordStore;
iconst_1 ; recordId (1)
aload_1 ; newData
iconst_0 ; offset (0)
bipush 100 ; numBytes (100)
invokevirtual javax/microedition/rms/RecordStore/setRecord(I[BII)V
Доступ к данным хранилища предоставляет метод
; byte[] getRecor(int recordID), где recordID — уникальный номер необходимой записи.
; То есть, чтобы получить запись, нужно точно знать ее номер
aload_0
getfield MyClassName/MyRecordStoreName Ljavax/microedition/rms/RecordStore;
iconst_1 ; recordID (1)
invokevirtual javax/microedition/rms/RecordStore/getRecord(I)[B
astore_1 ; сохраняем полученные данные в локальную переменную (массив байтов)
Рассмотрим работу с RecordStore на примере программы простейшего блокнота Notepad.
Данное приложение представляет простую записную книгу в которой можно: создавать заметки,
сохранять, удалять и просматривать по мере необходимости.
; Объявим класс Notepad
.class public Notepad
.super javax/microedition/midlet/MIDlet
.implements javax/microedition/lcdui/CommandListener
; Необходимые поля Notepad:
; private TextBox editor - редактор заметок
.field private editor Ljavax/microedition/lcdui/TextBox;
; private Command view - команда просмотра
.field private view Ljavax/microedition/lcdui/Command;
; private Command save - команда сохранения
.field private save Ljavax/microedition/lcdui/Command;
; private Command delete - команда удаления
.field private delete Ljavax/microedition/lcdui/Command;
; private Command exit - команда выхода
.field private exit Ljavax/microedition/lcdui/Command;
; private Display display - менеджер дисплея
.field private display Ljavax/microedition/lcdui/Display;
; private RecordStore recordstore - хранилище записей
.field private recordstore Ljavax/microedition/rms/RecordStore;
. . .
; стартовый метод
.method public startApp()V
.limit stack 7
.limit locals 1
; editor = new TextBox("Notepad","",255,TextField.ANY) - создать обьект редактора
aload_0
new javax/microedition/lcdui/TextBox
dup
ldc "Notepad"
ldc ""
sipush 255
iconst_0
invokespecial javax/microedition/lcdui/TextBox/(Ljava/lang/String;Ljava/lang/String;II)V
putfield Notepad/editor Ljavax/microedition/lcdui/TextBox;
; view = new Command("View",Command.OK,1) - создать команду просмотра записи
aload_0
new javax/microedition/lcdui/Command
dup
ldc "View"
iconst_4
iconst_1
invokespecial javax/microedition/lcdui/Command/(Ljava/lang/String;II)V
putfield Notepad/view Ljavax/microedition/lcdui/Command;
; save = new Command("Save",Command.OK,2) - создать команду сохранения записи
aload_0
new javax/microedition/lcdui/Command
dup
ldc "Save"
iconst_4
iconst_2
invokespecial javax/microedition/lcdui/Command/(Ljava/lang/String;II)V
putfield Notepad/save Ljavax/microedition/lcdui/Command;
; delete = new Command("Delete",Command.OK,3) - создать команду удаления записи
aload_0
new javax/microedition/lcdui/Command
dup
ldc "Delete"
iconst_4
iconst_3
invokespecial javax/microedition/lcdui/Command/(Ljava/lang/String;II)V
putfield Notepad/delete Ljavax/microedition/lcdui/Command;
; exit = new Command("Exit",Command.EXIT,4) - создать команду выхода из приложения
aload_0
new javax/microedition/lcdui/Command
dup
ldc "Exit"
bipush 7
iconst_4
invokespecial javax/microedition/lcdui/Command/(Ljava/lang/String;II)V
putfield Notepad/exit Ljavax/microedition/lcdui/Command;
; editor.addCommand(view) - добавить в редактор команду просмотра записи
aload_0
getfield Notepad/editor Ljavax/microedition/lcdui/TextBox;
aload_0
getfield Notepad/view Ljavax/microedition/lcdui/Command;
invokevirtual javax/microedition/lcdui/Displayable/addCommand(Ljavax/microedition/lcdui/Command;)V
; editor.addCommand(save) - добавить в редактор команду сохранения записи
aload_0
getfield Notepad/editor Ljavax/microedition/lcdui/TextBox;
aload_0
getfield Notepad/save Ljavax/microedition/lcdui/Command;
invokevirtual javax/microedition/lcdui/Displayable/addCommand(Ljavax/microedition/lcdui/Command;)V
; editor.addCommand(delete) - добавить в редактор команду удаления записи
aload_0
getfield Notepad/editor Ljavax/microedition/lcdui/TextBox;
aload_0
getfield Notepad/delete Ljavax/microedition/lcdui/Command;
invokevirtual javax/microedition/lcdui/Displayable/addCommand(Ljavax/microedition/lcdui/Command;)V
; editor.addCommand(exit) - добавить в редактор команду выхода из приложения
aload_0
getfield Notepad/editor Ljavax/microedition/lcdui/TextBox;
aload_0
getfield Notepad/exit Ljavax/microedition/lcdui/Command;
invokevirtual javax/microedition/lcdui/Displayable/addCommand(Ljavax/microedition/lcdui/Command;)V
; editor.setCommandListener(this) - добавить в редактор блок прослушивания команд
aload_0
getfield Notepad/editor Ljavax/microedition/lcdui/TextBox;
aload_0
invokevirtual javax/microedition/lcdui/Displayable/setCommandListener(Ljavax/microedition/lcdui/CommandListener;)V
; display = Display.getDisplay(this)- получить ссылку на менеджер дисплея
aload_0
aload_0
invokestatic javax/microedition/lcdui/Display/getDisplay(Ljavax/microedition/midlet/MIDlet;)Ljavax/microedition/lcdui/Display;
putfield Notepad/display Ljavax/microedition/lcdui/Display;
; display.setCurrent(editor) - отобразить редактор на экране
aload_0
getfield Notepad/display Ljavax/microedition/lcdui/Display;
aload_0
getfield Notepad/editor Ljavax/microedition/lcdui/TextBox;
invokevirtual javax/microedition/lcdui/Display/setCurrent(Ljavax/microedition/lcdui/Displayable;)V
return
.end method
. . .
; метод обработки софт-команд
.method public commandAction(Ljavax/microedition/lcdui/Command;Ljavax/microedition/lcdui/Displayable;)V
.limit stack 2
.limit locals 3
; if(command == view) - если выбрана софт-команда view, тогда
aload_1
aload_0
getfield Notepad/view Ljavax/microedition/lcdui/Command;
if_acmpne Label12
; viewRecord() - вызвать (наш) метод viewRecord для просмот заметки
aload_0
invokespecial Notepad/viewRecord()V
Label12:
; if(command == save) - если выбрана софт-команда save, тогда
aload_1
aload_0
getfield Notepad/save Ljavax/microedition/lcdui/Command;
if_acmpne Label24
; saveRecord() - вызвать (наш) метод saveRecord для сохранения заметки
aload_0
invokespecial Notepad/saveRecord()V
Label24:
; if(command == delete) - если выбрана софт-команда delete, тогда
aload_1
aload_0
getfield Notepad/delete Ljavax/microedition/lcdui/Command;
if_acmpne Label36
; deleteRecord() - вызвать (наш) метод deleteRecord для удаления заметки
aload_0
invokespecial Notepad/deleteRecord()V
Label36:
; if(command == exit) - если выбрана софт-команда exit, тогда
aload_1
aload_0
getfield Notepad/exit Ljavax/microedition/lcdui/Command;
if_acmpne Label49
; destroyApp(false) - вызываем destroyApp, а затем,
aload_0
iconst_0
invokevirtual Notepad/destroyApp(Z)V
; notifyDestroyed() - для закрытия приложения
aload_0
invokevirtual javax/microedition/midlet/MIDlet/notifyDestroyed()V
Label49:
return
.end method
Такие функции блокнота, как просмотр, сохранение и удаление заметок - реализуем в отдельных методах:
; метод просмотра заметки
.method private viewRecord()V
.limit stack 3
.limit locals 3
; try {
Label0:
; recordstore = RecordStore.openRecordStore("notepad",false) - открыть хранилище записей "notepad"
aload_0
ldc "notepad"
iconst_0
invokestatic javax/microedition/rms/RecordStore/openRecordStore(Ljava/lang/String;Z)Ljavax/microedition/rms/RecordStore;
putfield Notepad/recordstore Ljavax/microedition/rms/RecordStore;
; byte[] buff = recordstore.getRecord(1) - считать в байтовый массив значение записи № 1
aload_0
getfield Notepad/recordstore Ljavax/microedition/rms/RecordStore;
iconst_1
invokevirtual javax/microedition/rms/RecordStore/getRecord(I)[B
astore_1
; String text = new String(buff) - создать текстовую строку из массива байтов
new java/lang/String
dup
aload_1
invokespecial java/lang/String/([B)V
astore_2
; editor.setString(text) - добавить получившуюся строку в редактор
aload_0
getfield Notepad/editor Ljavax/microedition/lcdui/TextBox;
aload_2
invokevirtual javax/microedition/lcdui/TextBox/setString(Ljava/lang/String;)V
; recordstore.closeRecordStore() - закрыть хранилище записей
aload_0
getfield Notepad/recordstore Ljavax/microedition/rms/RecordStore;
invokevirtual javax/microedition/rms/RecordStore/closeRecordStore()V
Label43:
goto Label56
Label46:
; editor.setString("No Record!") - если записей нет, то вывести сообщение
astore_1
aload_0
getfield Notepad/editor Ljavax/microedition/lcdui/TextBox;
ldc "No Record!"
invokevirtual javax/microedition/lcdui/TextBox/setString(Ljava/lang/String;)V
Label56:
return
; catch (RecordStoreException ex) - обработка исключений хранилища записей между метками Label0 и Label43
; в случае исключения перейти на метку Label46
.catch javax/microedition/rms/RecordStoreException from Label0 to Label43 using Label46
.end method
; метод сохранения заметки
.method private saveRecord()V
.limit stack 4
.limit locals 3
; deleteRecord() - удаляем старую заметку
aload_0
invokespecial Notepad/deleteRecord()V
; try {
Label4:
; String text = editor.getString() - получить текст из редактора
aload_0
getfield Notepad/editor Ljavax/microedition/lcdui/TextBox;
invokevirtual javax/microedition/lcdui/TextBox/getString()Ljava/lang/String;
astore_1
; byte[] buff = text.getBytes() - конвертировать полученный текст в массив байт
aload_1
invokevirtual java/lang/String/getBytes()[B
astore_2
; recordstore = RecordStore.openRecordStore("notepad",true) - открыть хранилище записей "notepad"
aload_0
ldc "notepad"
iconst_1
invokestatic javax/microedition/rms/RecordStore/openRecordStore(Ljava/lang/String;Z)Ljavax/microedition/rms/RecordStore;
putfield Notepad/recordstore Ljavax/microedition/rms/RecordStore;
; recordstore.addRecord(buff,0,buff.length) - добавить в хранилище запись из массива buff, начиная с 0, длинною всего массива
aload_0
getfield Notepad/recordstore Ljavax/microedition/rms/RecordStore;
aload_2
iconst_0
aload_2
arraylength
invokevirtual javax/microedition/rms/RecordStore/addRecord([BII)I
pop
; recordstore.closeRecordStore() - закрыть хранилище
aload_0
getfield Notepad/recordstore Ljavax/microedition/rms/RecordStore;
invokevirtual javax/microedition/rms/RecordStore/closeRecordStore()V
Label46:
goto Label50
Label49:
astore_1
Label50:
return
; catch (RecordStoreException ex) - обработать исключения...
.catch javax/microedition/rms/RecordStoreException from Label4 to Label46 using Label49
.end method
; метод удаления заметки
.method private deleteRecord()V
.limit stack 2
.limit locals 2
; if(recordstore != null) - если хранилище записей существует, тогда
aload_0
getfield Notepad/recordstore Ljavax/microedition/rms/RecordStore;
ifnull Label26
; try {
Label7:
; recordstore.deleteRecordStore("notepad") - удалить хранилище записей "notepad"
aload_0
getfield Notepad/recordstore Ljavax/microedition/rms/RecordStore;
pop
ldc "notepad"
invokestatic javax/microedition/rms/RecordStore/deleteRecordStore(Ljava/lang/String;)V
; recordstore = null - "обнулить" поле recordstore
aload_0
aconst_null
putfield Notepad/recordstore Ljavax/microedition/rms/RecordStore;
Label22:
goto Label26
Label25:
astore_1
Label26:
return
; catch(RecordStoreException ex) - обработать исключения
.catch javax/microedition/rms/RecordStoreException from Label7 to Label22 using Label25
.end method
К сожалению, в большинстве телефонов наш Notepad не сможет корректно отображать
русский текст - все из-за проблемы различных кодировок кирилицы. Но это уже тема для
отдельных глав...
В этой главе мы достаточно подробно рассмотрели систему управления записями
RMS, которая представляет собой простую абстракцию базы данных (RecordStore),
связанную с записями. Записи не имеют определенного типа, а хранятся как мас-
сив байтов. Записи можно получать по уникальному идентификатору записи (ID),
если он известен.
Стоит отметить, что производительность RMS даже в современных моделях
достаточно низка, поэтому стоит использовать RMS только в тех случаях, когда
это действительно необходимо. Есть мнение, что производительность может
увеличиваться, если не обновлять некоторые элементы, а переписать полно-
стью все содержимое хранилища.
Глава 6 Организация потоков, работа с файлами
В предыдущих главах мы выводили на экран, главным образом,
картинки, графику и прочую визуализацию, которую приложение считывало из
файлов-ресурсов. Ресурсами в этом случае являлись картинки в определенном,
воспринимаемом телефоном, формате. На самом же деле ресурсами могут яв-
ляться не только картинки. Любая информация в самом разнообразном виде
может храниться в файле-ресурсе. Каким образом информация из файла image.png
превратилась в картинку на экране мобильника, осталось для нас за кадром: мы
просто вызвали метод createImage и получили готовый объект Image. В этой гла-
ве мы рассмотрим подробнее, что такое потоки, как работать с файлами-ресур-
сами и как организовать процедуру чтения из файла.
На этот раз только мы знаем, какого типа информацию содержит ресурс, как ее
следует обрабатывать, использовать или отображать. Черновую работу формиро-
вания объекта из файла, как это было в случае с картинкой, выполнять теперь за
нас некому.
Класс InputStream
Связь между источником данных и приложением осуществляется с помощью
объекта класса InputStream, который можно сравнить с трубой, пропускающей че-
рез себя поток данных. Класс InputStream является базовым для всех классов вво-
да и предоставляет основные методы работы с потоком ввода:
; int read() — прочитать следующий байт из потока данных.
; Эта функция является абстрактной, то есть в любом классе-потомке обязательна ее реализация
aload_0
getfield MyClassName/MyinputstreamName Ljava/io/InputStream;
invokevirtual java/io/InputStream/read()I
pop
; int read(byte[] b) — считать данные из потока в буфер.
; Функция читает все доступные данные, ограничиваясь размером буфера,
; и возвращает количество прочитанных байт
aload_0
getfield MyClassName/MyinputstreamName Ljava/io/InputStream;
aload_0
getfield MyClassName/b [B ; b[]
invokevirtual java/io/InputStream/read([B)I
pop
; int read(byte[] b, int off, int len) — считать данные длины len из потока данных и разместить их в буфере, начиная с позиции off
aload_0
getfield MyClassName/MyinputstreamName Ljava/io/InputStream;
aload_0
getfield MyClassName/b [B ; b[]
aload_0
getfield MyClassName/off I ; off
aload_0
getfield MyClassName/len I ; len
invokevirtual java/io/InputStream/read([BII)I
pop
; long skip(long n) — пропустить п байтов данных потока. Возвращает реальное количество пропущенных байт
aload_0
getfield MyClassName/MyinputstreamName Ljava/io/InputStream;
aload_0
getfield MyClassName/n J ; n
invokevirtual java/io/InputStream/skip(J)J
pop2
; void close() — закрывает поток и освобождает все используемые им ресурсы
aload_0
getfield MyClassName/MyinputstreamName Ljava/io/InputStream;
invokevirtual java/io/InputStream/close()V
Рассмотрим использование класса InputStream на простом примере вывода текста
из файла на экран телефона. Поместим текстовый файл text.txt в папку ресур-
сов приложения .../res (не забудьте при сборке запаковать его в JAR!).
Чтобы связать ресурс с потоком, воспользуемся методом
; getResourceAsStream(String name) класса Class
aload_0
invokevirtual java/lang/Object/getClass()Ljava/lang/Class;
ldc "/name"
invokevirtual java/lang/Class/getResourceAsStream(Ljava/lang/String;)Ljava/io/InputStream;
astore_2 ; результат сохранен в локальную переменную № 2
Затем с помощью метода потока read считаем содержимое файла-ресурса и поместим его на форму, связанную с экраном.
Пример кода будет выглядеть так:
. . .
.method public startApp()V
.limit stack 3
.limit locals 6
; Form form = new Form("BookReader") - создать форму
new javax/microedition/lcdui/Form
dup
ldc "BookReader"
invokespecial javax/microedition/lcdui/Form/(Ljava/lang/String;)V
astore_1
; InputStream is = getClass().getResourceAsStream("/text.txt") - связать файл text.txt с потоком is
aload_0
invokevirtual java/lang/Object/getClass()Ljava/lang/Class;
ldc "/text.txt"
invokevirtual java/lang/Class/getResourceAsStream(Ljava/lang/String;)Ljava/io/InputStream;
astore_2
; byte[] bArr - new byte[150] - создать массив байт размером в 150 элементов
sipush 150
newarray byte
astore_3
; try {
Label26:
; is.read(bArr) - считать содержимое потока в массив байт
aload_2
aload_3
invokevirtual java/io/InputStream/read([B)I
pop
; is.close() - закрыть поток
aload_2
invokevirtual java/io/InputStream/close()V
Label36:
goto Label41
Label39:
astore 4
Label41:
; String str = new String(bArr) - сформировать строку из массива байт
new java/lang/String
dup
aload_3
invokespecial java/lang/String/([B)V
astore 4
; form.append(str) - поместить строку в форму
aload_1
aload 4
invokevirtual javax/microedition/lcdui/Form/append(Ljava/lang/String;)I
pop
; Display display = Display.getDisplay(this) - получить ссылку на менеджер дисплея
aload_0
invokestatic javax/microedition/lcdui/Display/getDisplay(Ljavax/microedition/midlet/MIDlet;)Ljavax/microedition/lcdui/Display;
astore 5
; display.setCurrent(form) - отобразить форму
aload 5
aload_1
invokevirtual javax/microedition/lcdui/Display/setCurrent(Ljavax/microedition/lcdui/Displayable;)V
return
; catch(IOException e) { } - обязательная обработка исключений ввода-вывода
.catch java/io/IOException from Label26 to Label36 using Label39
.end method
. . .
Если наш текст text.txt был английский то все отобразилось более мене нормально,
стрелочки перематывают текст вниз и вверх, на экране не появляется ника-
ких лишних символов, связанных с тем, что размер запрошенного нами массива
(new byte[150]) превышает объем текста. Красота!
Но, как показала практика, радоваться было рано. Запакуем текст на русском,
и видим печальную картину.
Русский текст превратился в шифровку агентов МОСАДа, а снизу прилипли квад-
ратики неиспользованных байтов массива. Отсюда делаем вывод, что кодировка
win 1251 не совпала с той, что запрограммирована в телефоне. Единственное
правильное решение в данной ситуации — пользоваться универсальной кодиров-
кой, которая подходила бы ко всем моделям. Благо, такая кодировка существует.
Ее название говорит само за себя — Unicode.
Встает вопрос: как теперь перевести текст с привычной глазу кириллицы на
Unicode? Предлагаемое решение основывается на поочередном преобразовании каждого символа при
считывании его из потока.
Класс DataInputStream
На этот раз мы воспользуемся классом DataInputStream, который является наслед-
ником класса InputStream и реализует чтение основных типов данных. То есть, кроме
основного метода read(), этот класс содержит методы:
; readBoolean(),
aload_0
getfield MyClassName/Mydatainputstream Ljava/io/DataInputStream;
invokevirtual java/io/DataInputStream/readBoolean()Z
pop
; readByte(),
aload_0
getfield MyClassName/Mydatainputstream Ljava/io/DataInputStream;
invokevirtual java/io/DataInputStream/readByte()B
pop
; readChar(),
aload_0
getfield MyClassName/Mydatainputstream Ljava/io/DataInputStream;
invokevirtual java/io/DataInputStream/readChar()C
pop
; readInt(),
aload_0
getfield MyClassName/Mydatainputstream Ljava/io/DataInputStream;
invokevirtual java/io/DataInputStream/readInt()I
pop
; readUnsignedByte().
aload_0
getfield MyClassName/Mydatainputstream Ljava/io/DataInputStream;
invokevirtual java/io/DataInputStream/readUnsignedByte()I
pop
Преобразование будем выполнять с помощью кодовой строки, которая каждому
символу кириллицы ставит в соответствие его код в универсальной кодировке.
Таким образом, наша программа приобретает следующий вид:
; Объявляем класс BookReader
.class public BookReader
.super javax/microedition/midlet/MIDlet
; private String WIN1251_TO_UNICODE - кодовая строка для преобразования символа из Win1251 в Unicode
.field private WIN1251_TO_UNICODE Ljava/lang/String;
; конструктор класса
.method public ()V
.limit stack 2
.limit locals 1
aload_0
invokespecial javax/microedition/midlet/MIDlet/()V
; WIN1251_TO_UNICODE = "\u0402..." - инициализируем кодовую строку шестнадцатиричными кодами символов Unicode
aload_0
ldc "\u0402\u0403\u201a\u0453\u201e\u2026\u2020\u2021\u20ac\u2030\u0409\u2039\u040a\u040c\u040b\u040f\u0452\u2018\u2019\u201c\u201d\u2022\u2013\u2014\ufffd\u2122\u0459\u203a\u045a\u045c\u045b\u045f\u00a0\u040e\u045e\u0408\u00a4\u0490\u00a6\u00a7\u0401\u00a9\u0404\u00ab\u00ac\u00ad\u00ae\u0407\u00b0\u00b1\u0406\u0456\u0491\u00b5\u00b6\u00b7\u0451\u2116\u0454\u00bb\u0458\u0405\u0455\u0457\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041a\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e\u042f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043A\u043B\u043C\u043D\u043E\u043F\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044A\u044B\u044C\u044D\u044E\u044F"
putfield BookReader/WIN1251_TO_UNICODE Ljava/lang/String;
return
.end method
; стартовый метод
.method public startApp()V
.limit stack 6
.limit locals 8
; Form form = new Form("BookReader") - создать форму
new javax/microedition/lcdui/Form
dup
ldc "BookReader"
invokespecial javax/microedition/lcdui/Form/(Ljava/lang/String;)V
astore_1
; InputStream is = getClass().getResourceAsStream("/text.txt") - связать файл-ресурс text.txt с потоком is
aload_0
invokevirtual java/lang/Object/getClass()Ljava/lang/Class;
ldc "/text.txt"
invokevirtual java/lang/Class/getResourceAsStream(Ljava/lang/String;)Ljava/io/InputStream;
astore_2
; DataInputStream dis = new DataInputStream(is) - получить DataInputStream из is
new java/io/DataInputStream
dup
aload_2
invokespecial java/io/DataInputStream/(Ljava/io/InputStream;)V
astore_3
; int i = 0 - целочисленная переменная для счета символов
iconst_0
istore 4
; char[] bArr = new char[150] - создаем символьный массив из 150 элементов
sipush 150
newarray char
astore 5
; try {
Label39:
; while(true) { - открываем "бесконечный" цикл
; bArr[i++] = (char)convert(dis.readUnsignedByte()) - преобразовать символы в Unicode (при помощи метода convert) и записать в массив
aload 5
iload 4
iinc 4 1
aload_0
aload_3
invokevirtual java/io/DataInputStream/readUnsignedByte()I
invokespecial BookReader/convert(I)C
castore
; } - новая итерация цикла
goto Label39
Label58:
; при исключении EOF (конец файла) выйти из цикла
astore 6
; String str = new String(bArr,0,i-1) - сформировать строку из массива символов
new java/lang/String
dup
aload 5
iconst_0
iload 4
iconst_1
isub
invokespecial java/lang/String/([CII)V
astore 6
; form.append(str) - поместить строку в форму
aload_1
aload 6
invokevirtual javax/microedition/lcdui/Form/append(Ljava/lang/String;)I
pop
; Display display = Display.getDisplay(this) - получить ссылку на менеджер дисплея
aload_0
invokestatic javax/microedition/lcdui/Display/getDisplay(Ljavax/microedition/midlet/MIDlet;)Ljavax/microedition/lcdui/Display;
astore 7
; display.setCurrent(form) - отобразить форму на экране
aload 7
aload_1
invokevirtual javax/microedition/lcdui/Display/setCurrent(Ljavax/microedition/lcdui/Displayable;)V
return
; catch (IOException ex) {} - обязательная обработка исключений ввода-вывода
.catch java/io/IOException from Label39 to Label58 using Label58
.end method
. . .
; создадим метод конвертирующий символ из Win1251 в Unicode
; private char convert(int ch) { - принимаем в качестве аргумента целое число (код символа), а возвращаем символ
.method private convert(I)C
.limit stack 3
.limit locals 3
; if(ch < 128) - если полученный код меньше 128, тогда,
iload_1
sipush 128
if_icmpge Label13
; char result = (char)ch - результат будет символ от полученного кода
iload_1
i2c
istore_2
goto Label26
; else - иначе,
Label13:
; char result = WIN1251_TO_UNICODE.charAt(ch - 128) - результат будет символ из кодовой строки
aload_0
getfield BookReader/WIN1251_TO_UNICODE Ljava/lang/String;
iload_1
sipush 128
isub
invokevirtual java/lang/String/charAt(I)C
istore_2
Label26:
; return result - возвратить результат
iload_2
ireturn
.end method
Символы читаются из потока по одному в виде байта без знака, а после преобра-
зования заносятся в массив символов. Проблема лишних байт решена с помощью
конструктора класса String, принимающего длину формируемой строки. Длину
мы получили при побайтном чтении потока.
Не забудьте при упаковке JAR-файла добавить файл text.txt - с небольшим русским текстом
сохраненным в кодировке CP 1251! Запускаем приложение и видим, что все наши проблемы решены.
Шрифт, конечно, немного крупноват, но для начала сойдет.
Класс ByteArraylnputStream
На примерах мы рассмотрели работу с файлом-ресурсом, который предоставлял
приложению все необходимые данные. На самом деле, поток может быть сфор-
мирован из любых данных и представлен в виде последовательности байтов. Еще
один класс, который порожден из базового класса InputStream и представляет по-
ток в виде открытого массива байтов, — это ByteArraylnputStream.
Данный класс предоставляет все объявленные, но не реализованные функции
класса InputStream, позволяя получить заранее число доступных байтов потока,
пометить позицию возврата для повторного чтения данных.
Внутренняя структура потока открыта для нас. Программисту доступны следу-
ющие поля:
• byte[ ] buf — массив байтов для хранения данных, указанный при создании потока;
• int count — число доступных байтов потока;
• int mark — индекс текущей помеченной позиции для повторного чтения;
• int pos — индекс следующего байта потока для чтения.
Класс OutputStream
До сих пор мы работали с потоком только одного направления — от ресурса к при-
ложению. Все рассмотренные классы, так или иначе, связаны со словом Input (ввод),
а методы чтения данных — со словом read. Вся система ввода зеркально отобража-
ется на работу с потоком обратного направления. Поток вывода данных сохраняет
всю иерархию классов потока ввода, с той лишь разницей, что в названиях классов
вместо слова Input фигурирует слово Output: OutputStream, ByteArrayOutputStream,
DataOutputStream. Каждому методу чтения, содержащему в названии слово read, со-
ответствует аналогичный метод записи, только со словом write.
Функции потоков чтения skip, available, mark и reset для потоков вывода не акту-
альны, вместо них потоки вывода реализуют лишь одну функцию flush(), кото-
рая очищает все данные выходного потока.
* * *
Это были простейшие примеры использования потоков данных. Но кроме ресур-
сов приложения существуют также ресурсы файловой системы и сетевые ресурсы,
ведь мобильный телефон это все-таки средство связи. Организация сетей, поддержка работы с сетевыми
ресурсами, коммуникация между мидлетами и создание межсетевых приложений
реализуются с помощью потоков данных. На некоторых примерах использова-
ния потоков для сетевых данных мы остановимся в заключительных главах, по-
этому оставайтесь с нами: впереди еще много интересного.
Глава 7 Приложения для работы с Интернетом
Большая часть возможностей мобильного телефона и реализации J2ME уже поза-
ди. Мы написали несколько различных приложений, реализующих интеракцию
с пользователем, хранение и обмен данными, а также другие функции языка. Мо-
бильный телефон, однако, является все же средством связи и позволяет связывать-
ся не только с помощью голоса или SMS. Профайл MIDP предоставляет нам воз-
можности связи с Интернетом, получения и передачи различного вида данных.
Тем, кто не знает, что такое сокет (socket), и не сталкивался с разработкой
компьютерных приложений типа «клиент-сервер», придется трудновато. Однако
один из приведенных примеров, демонстрирующий работу по протоколу HTTP,
если уж вы дошли до этого места, реализовать вам вполне по силам. Разработка
приложений типа «клиент-сервер» — это отдельная наука. Мы же ограничимся
самыми простыми примерами, демонстрирующими первоначальное взаимодей-
ствие телефона с Интернетом.
Класс Connector
Не буду забивать вам голову тем, как устроена связь между беспроводной сетью
и Всемирной паутиной, а перейду сразу к делу, то есть к программированию. Се-
тевая работа и беспроводная коммуникация осуществляются в реализации MIDP
средствами пакета javax/microedition/io/, который содержит набор интерфейсов
для различных типов соединений, а также единственный класс Connector. Органи-
зация соединений с любыми ресурсами осуществляется только через этот класс,
который предоставляет следующие методы:
; Connection open(String name) — основной метод класса Connector,
; обеспечивающий открытие соединения с ресурсом, представленным аргументом name
aload_0
ldc "name"
invokestatic javax/microedition/io/Connector/open(Ljava/lang/String;)Ljavax/microedition/io/Connection;
putfield MyClassName/MyConnectionName Ljavax/microedition/io/Connection;
Аргумент имеет формат универсального идентификатора ресурса (URI), кото-
рый можно представить следующей схемой:
<протокол>://[<адрес>][<параметры>]
Например:
http://www.voolkansoft.com
socket://129.144.111.222:9000
datagram://129.144.111.333
file:/foo.dat
comm:0;baudrate=9600
Протокол представляет собой тип передаваемых данных. Реализация MIDP
может поддерживать такие протоколы: http, socket, datagram, file, ftp, comm.
Совсем не обязательно, чтобы ваша реализация MIDP поддерживала все эти
протоколы, поскольку спецификация MIDP требует обязательной поддержки
только одного протокола HTTP 1.1. Не исключена также поддержка протоко-
лов, не указанных в списке. Адресом является обычный URL; в качестве до-
полнительных параметров может выступать номер порта.
Метод возвращает объект Connection, являющийся базовым для интерфейсов,
представляющих различные типы соединений, которые мы рассмотрим позже;
; Connection open(String name, int mode) — метод, аналогичный предыдущему,
; с тем отличием, что параметр mode задает режим создаваемого соединения.
aload_0
ldc "name"
aload_0
getfield MyClassName/mode I
invokestatic javax/microedition/io/Connector/open(Ljava/lang/String;I)Ljavax/microedition/io/Connection;
putfield MyClassName/MyConnectionName Ljavax/microedition/io/Connection;
Режимы соединений представлены следующими константами класса Connector:
• READ (1) — открыть соединение с доступом только на чтение данных;
• WRITE (2) — открыть соединение с доступом только на запись данных;
• READ_WRITE (3) — открыть соединение с доступом и на чтение, и на запись данных;
Следующие методы класса Connector позволяют сразу открывать потоки ввода
и вывода данных, рассмотренные в одной из предыдущих глав, минуя интерфейс
Connection. Когда мы уже работали с потоками ввода и вывода, мы получали дан-
ные из файла, являющегося локальным ресурсом приложения. Вся разница за-
ключается в том, что ресурс будет размещаться в Интернете и предоставлять дан-
ные в своем формате:
; DataInputStream openDataInputStream(String name) — открывает поток ввода данных.
; Аргумент name имеет формат уникального идентификатора ресурса;
aload_0
ldc "name"
invokestatic javax/microedition/io/Connector/openDataInputStream(Ljava/lang/String;)Ljava/io/DataInputStream;
putfield MyClassName/MydataInputStream Ljava/io/DataInputStream;
; DataOutputStream openDataOutputStream(String name) — открывает поток вывода данных;
aload_0
ldc "name"
invokestatic javax/microedition/io/Connector/openDataOutputStream(Ljava/lang/String;)Ljava/io/DataOutputStream;
putfield MyClassName/MydataOutputStream Ljava/io/DataOutputStream;
; InputStream openInputStream (String name) — открывает базовый поток ввода данных;
aload_0
ldc "name"
invokestatic javax/microedition/io/Connector/openInputStream(Ljava/lang/String;)Ljava/io/InputStream;
putfield MyClassName/MyInputStream Ljava/io/InputStream;
; OutputStream openOutputStream(String name) — открывает базовый поток вывода данных
aload_0
ldc "name"
invokestatic javax/microedition/io/Connector/openOutputStream(Ljava/lang/String;)Ljava/io/OutputStream;
putfield MyClassName/MyOutputStream Ljava/io/OutputStream;
Интерфейс Connection
Интерфейс Connection является базовым для всех последующих типов соедине-
ний. Он содержит единственный метод:
; close() закрывающий соединение, которое открывается с помощью метода open() класса Connector.
aload_0
getfield MyClassName/MyConnectionName Ljavax/microedition/io/Connection;
invokeinterface javax/microedition/io/Connection/close()V 1
Этот интерфейс
является самым абстрактным во всей структуре возможных типов соединений.
Дальнейшие интерфейсы будут его расширять и дополнять новыми методами
и возможностями.
Итак, процесс установки соединения и работы с ним выглядит следующим обра-
зом. Приложение использует класс Connector для открытия соединения с сетевым
ресурсом. Метод ореn(...) класса Connector анализирует универсальный идентифи-
катор ресурса и проверяет, поддерживается ли необходимый протокол в данной
платформе. В случае ошибки формируется исключение, иначе возвращается объект
Connection, который содержит ссылки на входной и выходной потоки к сетевому
ресурсу. Приложение получает объекты типа InputStream и OutputStream из объекта
Connection и организует обмен данными с сетевым ресурсом через эти потоки. При
завершении работы приложение закрывает соединение с помощью метода close()
интерфейса Connection.
Интерфейсы InputConnection и OutputConnection
Интерфейсы InputConnection и OutputConnection наследованы от интерфейса Connection
и расширяют его потоками ввода и вывода соответственно. Каждый их них опреде-
ляет по два новых метода для открытия потоков. Названия этих методов такие же,
как и у методов открытия потоков класса Connector, и отличаются они лишь отсут-
ствием аргументов. Использование этих методов открытия потоков мы еще уви-
дим в примерах.
Интерфейс StreamConnection
Интерфейс StreamConnection объединяет в себе два предыдущих интерфейса
InputConnection и OutputConnection, но не определяет никаких новых методов. За-
дача этого интерфейса в том, чтобы представить любой тип соединения, чьи дан-
ные могут быть обработаны как поток байтов. В зависимости от протокола соеди-
нения активизируются разные реализации интерфейса StreamConnection.
Сокет (socket) — это сетевой механизм транспортного уровня, который реализует
работу по протоколу TCP/IP. Грубо говоря, это та дверь, которая связывает про-
грамму с Интернетом. Как любая порядочная дверь в большой мир, сокет имеет
свой адрес, состоящий из сетевого адреса URL и номера порта, через который бу-
дет осуществляться соединение. Номер порта может быть любым в диапазоне
0-65535, однако номера, меньшие чем 1024, обычно зарезервированы системой.
Структура сокета универсальна, поэтому программа, принимающая соединение,
может быть запущена на любой платформе и написана на любом языке, поддер-
живающем сокеты.
Интерфейс StreamConnectionNotifier
Функции сервера может выполнять и мобильный телефон с помощью интер-
фейса StreamConnectionNotifier. Этот интерфейс содержит единственный метод
StreamConnection acceptAndOpen(), который блокирует все операции до тех пор, пока
не появится клиентский запрос на соединение. После чего метод создает новый
объект соединения, через который и связывает серверную часть с клиентской.
При использовании данного интерфейса в качестве сервера будет выступать мо-
бильное устройство. Недостатки такой реализации заключаются в том, что уст-
ройство должно быть постоянно на связи в ожидании подключения клиентов, что
может быть дорого и неудобно.
Интерфейс DatagramConnection
Еще один тип соединений представлен интерфейсом DatagramConnection, который
напрямую наследован от интерфейса Connection. Если соединение через сокеты
основывалось на протоколе TCP, то данное соединение использует протокол пе-
редачи дейтаграмм UDP. Данный протокол используется различными интернет-
службами потоковых трансляций и основан на передаче дейтаграмм. В некото-
рых случаях он является более предпочтительным из-за эффективной и быстрой
пересылки данных, поскольку там нет гарантированной доставки сообщений, ис-
правлений искаженных сообщений и другого контроля, нагружающего связь.
Дейтаграмма представлена интерфейсом Datagram, который объединяет в себе два
интерфейса DataInput и DataOutput. Кроме этого интерфейс содержит информацию
о передаваемых или принимаемых данных и об адресе пункта назначения.
Так же как и StreamConnection, интерфейс DatagramConnection может быть использо-
ван как для клиентской, так и для серверной частей приложения. Если при созда-
нии дейтаграммы в параметре адреса не будет указано имя хоста (datagram: //: 3000),
то соединение будет работать в серверном режиме. Если имя хоста будет указано
(datagram://127.0.0.1:3000), то соединение будет работать в клиентском режиме.
Интерфейс ContentConnection
Интерфейс ContentConnection дополняет интерфейс StreamConnection. Главное от-
личие заключается в том, что если раньше интерфейс не вникал в суть передава-
емых данных, то теперь из него можно извлечь некоторую информацию, опреде-
ленную самим протоколом.
Интерфейс HttpConnection
Самым продвинутым интерфейсом во всей этой иерархии является интерфейс
HttpConnection, представляющий соединения, использующие протокол HTTP. По
этому протоколу организуется передача всех знакомых нам интернет-страниц.
Интерфейс HttpConnection реализует возможность получения информации полей
HTTP-заголовка ресурса, а также поддерживает передачу запросов и получение
откликов по протоколу HTTP. Кроме этого интерфейс определяет полный набор
констант, представляющих коды ошибок и статуса протокола HTTP.
Следующий пример, который мы реализуем, получит по протоколу HTTP дан-
ные интернет-страницы. Это информационная страница FM-радио "ENERGY 104.2 FM".
В данном случае нам потребуется реализация лишь кли-
ентской части приложения, поскольку серверная часть представлена обычным
веб-сервером, с которым мы связываемся с помощью браузера, заходя на стра-
ницу в Интернете.
. . .
.method public startApp()V
.limit stack 3
.limit locals 7
; Form form = new Form("ENERGY 104.2 FM") - создать форму
new javax/microedition/lcdui/Form
dup
ldc "ENERGY 104.2 FM"
invokespecial javax/microedition/lcdui/Form/(Ljava/lang/String;)V
astore_1
; try {
Label10:
; HttpConnection hc = (HttpConnection)Connector.open("http://www.energyfm.ru/?an=nrj_scroll2&nostat=1"),
; Открыть соединение с HTTP-сервером
ldc "http://www.energyfm.ru/?an=nrj_scroll2&nostat=1"
invokestatic javax/microedition/io/Connector/open(Ljava/lang/String;)Ljavax/microedition/io/Connection;
checkcast javax/microedition/io/HttpConnection
astore_2
; DataInputStream dis = hc.openDataInputStream() - получить поток ввода данных
aload_2
invokeinterface javax/microedition/io/InputConnection/openDataInputStream()Ljava/io/DataInputStream; 1
astore_3
; StringBuffer sb = new StringBuffer("") - создать обьект StringBuffer, для создания строки
new java/lang/StringBuffer
dup
ldc ""
invokespecial java/lang/StringBuffer/(Ljava/lang/String;)V
astore 4
; for(int i = 0; i < 1000; i++) { - открыть цикл от 0 до 1000 с шагом 1
iconst_0
istore 6
Label40:
iload 6
sipush 1000
if_icmpge Label103
; char ch = (char)dis.read() - читать следующий байт и преобразовывать в симвов ch
aload_3
invokevirtual java/io/DataInputStream/read()I
i2c
istore 5
; if(i > 587){ - если текущая позиция (в данных html-страницы) больше 587, тогда,
iload 6
sipush 587
if_icmple Label97
; if(ch == '<') - если прочитан следующий тег, тогда,
iload 5
bipush 60
if_icmpne Label73
; break - выйти из цикла
goto Label103
Label73:
; if(ch > 127) { - если символ > 127 (кирилица), тогда
iload 5
bipush 127
if_icmple Label89
; ch += 848 - прибавить 848 (юникод)
iload 5
sipush 848
iadd
i2c
istore 5
Label89:
; sb.append(ch) - добавить полученный символ в StringBuffer
aload 4
iload 5
invokevirtual java/lang/StringBuffer/append(C)Ljava/lang/StringBuffer;
pop
Label97:
; } - новая итерация цикла
iinc 6 1
goto Label40
Label103:
; String text = sb.toString() - получить текст из StringBuffer'a
aload 4
invokevirtual java/lang/StringBuffer/toString()Ljava/lang/String;
astore 6
; form.append(text) - добавить текст в форму
aload_1
aload 6
invokevirtual javax/microedition/lcdui/Form/append(Ljava/lang/String;)I
pop
; dis.close() - закрыть поток
aload_3
invokevirtual java/io/DataInputStream/close()V
; hc.close() - закрыть HTTP-соединение
aload_2
invokeinterface javax/microedition/io/Connection/close()V 1
Label127:
goto Label131
Label130:
astore_2
Label131:
; Display display = Display.getDisplay(this) - получить ссылку на менеджер дисплея
aload_0
invokestatic javax/microedition/lcdui/Display/getDisplay(Ljavax/microedition/midlet/MIDlet;)Ljavax/microedition/lcdui/Display;
astore_2
; display.setCurrent(form) - отобразить форму на экране
aload_2
aload_1
invokevirtual javax/microedition/lcdui/Display/setCurrent(Ljavax/microedition/lcdui/Displayable;)V
return
.catch java/io/IOException from Label10 to Label127 using Label130
.end method
. . .
Мы рассмотрели все типы соединений этой иерархии, выделив четыре основных
типа: потоковые соединения с коммуникационными портами,
соединения уровня приложений по протоколу HTTP, либо через сокеты по про-
токолу TCP/IP, дейтаграммные соединения по протоколу UDP. Из всех этих ти-
пов спецификация MIDP определяет обязательную реализацию лишь для под-
держки протокола HTTP. Попытка использования остальных типов соединений
может сформировать исключение на вашей модели телефона.
Информация, приведенная в этой главе, является лишь базовыми сведениями для
программирования сетевых приложений. Никто не говорит, что, потратив несколь-
ко минут на чтение, вы сможете написать интернет-браузер для вашего мобиль-
ника, однако, приложив некоторые усилия, организовать общение вашего теле-
фона с интернет-ресурсами вам вполне по зубам. И да пребудет с вами сила!
Глава 8 Работа с файловой системой телефона
(по материалам статей MobiLab.ru)
Дополнительный пакет JSR 75 дает разработчику возможность получить доступ к личной информации пользователя
и файловой системе телефона, включая съемные носители информации, такие как Memory Sticks.
Знакомство с ограничениями FileConnection API
Поскольку доступ к файловой системе является потенциально опасной операцией,
при обращении к ней каждый раз будет запрашиваться подтверждение пользователя.
Конечно, необходимость подтверждать каждое обращение мидлета очень раздражает.
Однако, если мидлет подписан, то можно один раз подтвердить правомерность доступа к файловой системе
и забыть об этой проблеме. Для того чтобы сделать это, нужно установить соответствующую blanket(общую) опцию
в настройках прав мидлета.
FileConnection API определяет два вида прав доступа к файлам:
javax/microedition/io/Connector/file/read
javax/microedition/io/Connector/file/write
read позволяет открыть файл в режиме чтения, либо создать InputStream на основании объекта FileConnection.
write позволяет открыть файл в режиме записи, либо создать OutputStream на основании FileConnection объекта.
Также write позволяет выполнять операции удаления и переименования файлов.
Если Вы не имеете прав на проведение операций чтения или записи, будет сгенерирована исключительная ситуация
SecurityException. Важно обработать это исключение внутри мидлета.
Классы и интерфейсы FileConnection API
FileConnection API дает возможность создавать и удалять файлы и папки, получать список файлов в папке,
устанавливать права, получать информацию о файлах и выполнять операции ввода-вывода.
Ниже приведены важнейшие классы и интерфейсы:
ConnectionClosedException возникает если метод вызывает FileConnection в то время как соединение разорвано.
Класс FileSystemRegistry -главный системный реестр. С его помощью можно получить список примонтированных корневых папок (метод listRoots()).
Он также содержит методы для регистрации "слушателей", которые оповещаются в случае добавления или удаления файловой системы в течение выполнения программы.
FileSystemListener - интерфейс, используемый для получения уведомлений о создании и удалении корневых файловых систем.
FileConnection - интерфейс, который используется для доступа к файлам и каталогам устройства.
Он является расширением интерфейса Connection и содержит ряд методов для работы с файлами и папками.
Использование FileConnection API
Поскольку для работы с файловой системой FileConnection API использует Generic Connection Framework,
процедура создания FileConnection похожа на создание простого GCF соединения.
Единственным отличием будет указание URL адреса. Для создания FileConnection, используйте метод open класса Connector:
; Connector.open(String URL)
aload_0
ldc "URL"
invokestatic javax/microedition/io/Connector/open(Ljava/lang/String;)Ljavax/microedition/io/Connection;
putfield MyClassName/MyConnectionName Ljavax/microedition/io/Connection;
URL адрес должен иметь вид: "file://localhost/c:/" или "file:///c:/" для внутренней памяти устройства;
и "file://localhost/e:/" или "file:///e:/" для съемных карт памяти.
Так, для получения доступа к папкам внутренней памяти устройства, нужно создать FileConnection объект, используя следующий URL:
; FileConnection fc = (FileConnection)Connector.open("file:///c:/")
aload_0
ldc "file:///c:/"
invokestatic javax/microedition/io/Connector/open(Ljava/lang/String;)Ljavax/microedition/io/Connection;
checkcast javax/microedition/io/file/FileConnection
putfield MyClassName/fc Ljavax/microedition/io/file/FileConnection;
Поскольку метод Connector.open() не позволяет указать права доступа,
созданный FileConnection объект может выполнять как операции чтения, так и записи.
Указанный при создании объекта FileConnection URL может ссылаться на несуществующий файл или каталог.
Не забывайте закрывать FileConnection после выполнения всех необходимых действий:
; fc.close()
aload_0
getfield MyClassName/fc Ljavax/microedition/io/file/FileConnection;
invokeinterface javax/microedition/io/Connection/close()V 1
FileConnection ссылающийся на несуществующий файл не может использовать многие операции.
Так попытка открыть InputStream или OutPutStream приведут к возникновению java.io.IOException исключительной ситуации.
Единственное, чем Вы можете воспользоваться, это методы create() и mkdir() создающие файл и каталог соответственно.
Для того чтобы убедиться в существовании требуемого файла, воспользуйтесь методом exists():
; fc.exists()
aload_0
getfield MyClassName/fc Ljavax/microedition/io/file/FileConnection;
invokeinterface javax/microedition/io/file/FileConnection/exists()Z 1
pop
Если запрашиваемый файл не существует, Вы легко можете создать его:
; fc.create()
aload_0
getfield MyClassName/fc Ljavax/microedition/io/file/FileConnection;
invokeinterface javax/microedition/io/file/FileConnection/create()V 1
Для создания каталога используется другой метод
; fc.mkdir()
aload_0
getfield MyClassName/fc Ljavax/microedition/io/file/FileConnection;
invokeinterface javax/microedition/io/file/FileConnection/mkdir()V 1
Для удаления файлов и каталогов используется метод
; fc.delete()
aload_0
getfield MyClassName/fc Ljavax/microedition/io/file/FileConnection;
invokeinterface javax/microedition/io/file/FileConnection/delete()V 1
чтобы узнать содержание папки, на которую ссылается объект FileConnection, используйте следующий метод:
; Enumeration fc.list()
aload_0
getfield MyClassName/fc Ljavax/microedition/io/file/FileConnection;
invokeinterface javax/microedition/io/file/FileConnection/list()Ljava/util/Enumeration; 1
pop
FileSystemRegistry имеет метод listRoots(), который позволяет получить список всех примонтированных корневых файловых систем.
Список возвращается как Enumeration
; Enumeration FileSystemRegistry.listRoots()
invokestatic javax/microedition/io/file/FileSystemRegistry/listRoots()Ljava/util/Enumeration;
pop
Операции ввода/вывода
Если Вам уже приходилось работать с GCF, то вы хорошо знакомы с операциями чтения и записи в файл.
Для записи необходимо получить OutputStream с помощью объекта FileConnection, указывающего на необходимый файл:
; OutputStream fc.openOutputStream()
aload_0
getfield MyClassName/fc Ljavax/microedition/io/file/FileConnection;
invokeinterface javax/microedition/io/file/FileConnection/openOutputStream()Ljava/io/OutputStream; 1
pop
Если вы хотите писать в файлы данные в форме какого-нибудь Java типа, можете воспользоваться объектом DataOutputStream:
; DataOutputStream fc.openDataOutputStream()
aload_0
getfield FileBrowser/fc Ljavax/microedition/io/file/FileConnection;
invokeinterface javax/microedition/io/file/FileConnection/openDataOutputStream()Ljava/io/DataOutputStream; 1
pop
Для чтения из файла необходимо получить InputStream или DataInputStream.
; InputStream fc.openInputStream()
aload_0
getfield FileBrowser/fc Ljavax/microedition/io/file/FileConnection;
invokeinterface javax/microedition/io/file/FileConnection/openInputStream()Ljava/io/InputStream; 1
pop
Напишем для примера простейший файловый менеджер для телефонов с JSR 75.
*для Siemens API нужно заменить javax/microedition/io/file/FileConnection на com/siemens/mp/io/file/FileConnection
и javax/microedition/io/file/FileSystemRegistry на com/siemens/mp/io/file/FileSystemRegistry
; Объявляем класс FileBrowser
.class public FileBrowser
.super javax/microedition/midlet/MIDlet
.implements javax/microedition/lcdui/CommandListener
; необходимые поля класса:
; private List browser - контейнер-список
.field private browser Ljavax/microedition/lcdui/List;
; private Command exit - команда для выхода
.field private exit Ljavax/microedition/lcdui/Command;
; private Display display - менеджер дисплея
.field private display Ljavax/microedition/lcdui/Display;
; private String currDirName - текущая папка
.field private currDirName Ljava/lang/String;
; private FileConnection fc - объект FileConnection
.field private fc Ljavax/microedition/io/file/FileConnection;
; объект перечисления Enumeration
.field private enumeration Ljava/util/Enumeration;
; конструктор класса
.method public ()V
.limit stack 2
.limit locals 1
aload_0
invokespecial javax/microedition/midlet/MIDlet/()V
; currDirName = "/" - установить текущую папку, как корневую
aload_0
ldc "/"
putfield FileBrowser/currDirName Ljava/lang/String;
return
.end method
.method public startApp()V
.limit stack 6
.limit locals 1
; browser = new List("FileBrowser",List.IMPLICIT) - создать пустой список
aload_0
new javax/microedition/lcdui/List
dup
ldc "FileBrowser"
iconst_3
invokespecial javax/microedition/lcdui/List/(Ljava/lang/String;I)V
putfield FileBrowser/browser Ljavax/microedition/lcdui/List;
; exit = new Command("Exit", Command.EXIT, 3) - создать команду "Exit"
aload_0
new javax/microedition/lcdui/Command
dup
ldc "Exit"
bipush 7
iconst_3
invokespecial javax/microedition/lcdui/Command/(Ljava/lang/String;II)V
putfield FileBrowser/exit Ljavax/microedition/lcdui/Command;
; browser.addCommand(exit) - добавить команду в список
aload_0
getfield FileBrowser/browser Ljavax/microedition/lcdui/List;
aload_0
getfield FileBrowser/exit Ljavax/microedition/lcdui/Command;
invokevirtual javax/microedition/lcdui/Displayable/addCommand(Ljavax/microedition/lcdui/Command;)V
; browser.setCommandListener(this) - установить в список блок прослущивания команд
aload_0
getfield FileBrowser/browser Ljavax/microedition/lcdui/List;
aload_0
invokevirtual javax/microedition/lcdui/Displayable/setCommandListener(Ljavax/microedition/lcdui/CommandListener;)V
; showCurrDir() - вызвать метод отображения текущей папки
aload_0
invokespecial FileBrowser/showCurrDir()V
; display = Display.getDisplay(this) - получить ссылку на менеджер дисплея
aload_0
aload_0
invokestatic javax/microedition/lcdui/Display/getDisplay(Ljavax/microedition/midlet/MIDlet;)Ljavax/microedition/lcdui/Display;
putfield FileBrowser/display Ljavax/microedition/lcdui/Display;
; display.setCurrent(browser) - отобразить список на экране
aload_0
getfield FileBrowser/display Ljavax/microedition/lcdui/Display;
aload_0
getfield FileBrowser/browser Ljavax/microedition/lcdui/List;
invokevirtual javax/microedition/lcdui/Display/setCurrent(Ljavax/microedition/lcdui/Displayable;)V
return
.end method
.method public pauseApp()V
.limit stack 0
.limit locals 1
return
.end method
.method public destroyApp(Z)V
.limit stack 0
.limit locals 2
return
.end method
.method public commandAction(Ljavax/microedition/lcdui/Command;Ljavax/microedition/lcdui/Displayable;)V
.limit stack 2
.limit locals 5
; if (command == List.SELECT_COMMAND){ - если в списке выбран какой-либо элеметн, тогда,
aload_1
getstatic javax/microedition/lcdui/List/SELECT_COMMAND Ljavax/microedition/lcdui/Command;
if_acmpne Label35
; int i = browser.getSelectedIndex() - получить индекс выбранного элемента списка
aload_0
getfield FileBrowser/browser Ljavax/microedition/lcdui/List;
invokevirtual javax/microedition/lcdui/List/getSelectedIndex()I
istore_3
; String fileName = browser.getString(i) - получить имя выбранного элемента (по иднексу)
aload_0
getfield FileBrowser/browser Ljavax/microedition/lcdui/List;
iload_3
invokevirtual javax/microedition/lcdui/List/getString(I)Ljava/lang/String;
astore 4
; traverseDirectory(fileName) - вызвать метод определения текущей папки
aload_0
aload 4
invokespecial FileBrowser/traverseDirectory(Ljava/lang/String;)V
; showCurrDir() - вызвать метод отображения текущей папки
aload_0
invokespecial FileBrowser/showCurrDir()V
Label35:
; if (command == exit) - если выбранна команда "Exit", тогда,
aload_1
aload_0
getfield FileBrowser/exit Ljavax/microedition/lcdui/Command;
if_acmpne Label47
; notifyDestroyed() - завершить программу
aload_0
invokevirtual javax/microedition/midlet/MIDlet/notifyDestroyed()V
Label47:
return
.end method
; создадим метод отображения текущей папки
.method private showCurrDir()V
.limit stack 3
.limit locals 2
Label0:
; browser.deleteAll() - очистить список
aload_0
getfield FileBrowser/browser Ljavax/microedition/lcdui/List;
invokevirtual javax/microedition/lcdui/List/deleteAll()V
; if ("/".equals(currDirName)) { - если текущая папка является корневой, тогда,
ldc "/"
aload_0
getfield FileBrowser/currDirName Ljava/lang/String;
invokevirtual java/lang/String/equals(Ljava/lang/Object;)Z
ifeq Label29
; enumeration = FileSystemRegistry.listRoots() - получить перечисление всех корневых файлов
aload_0
invokestatic javax/microedition/io/file/FileSystemRegistry/listRoots()Ljava/util/Enumeration;
putfield FileBrowser/enumeration Ljava/util/Enumeration;
; } else { - иначе,
goto Label87
Label29:
; String path = "file:///" + currDirName - получить путь к текущей папке
new java/lang/StringBuffer
dup
invokespecial java/lang/StringBuffer/()V
ldc "file:///"
invokevirtual java/lang/StringBuffer/append(Ljava/lang/String;)Ljava/lang/StringBuffer;
aload_0
getfield FileBrowser/currDirName Ljava/lang/String;
invokevirtual java/lang/StringBuffer/append(Ljava/lang/String;)Ljava/lang/StringBuffer;
invokevirtual java/lang/StringBuffer/toString()Ljava/lang/String;
astore_1
; fc = (FileConnection)Connector.open(path) - создать соединение с полученным путем папки
aload_0
aload_1
invokestatic javax/microedition/io/Connector/open(Ljava/lang/String;)Ljavax/microedition/io/Connection;
checkcast javax/microedition/io/file/FileConnection
putfield FileBrowser/fc Ljavax/microedition/io/file/FileConnection;
; enumeration = fc.list() - получить перечисление всех файлов текущей папки
aload_0
aload_0
getfield FileBrowser/fc Ljavax/microedition/io/file/FileConnection;
invokeinterface javax/microedition/io/file/FileConnection/list()Ljava/util/Enumeration; 1
putfield FileBrowser/enumeration Ljava/util/Enumeration;
; browser.append("..", null) - добавить в начало списка ".." для возврата
aload_0
getfield FileBrowser/browser Ljavax/microedition/lcdui/List;
ldc ".."
aconst_null
invokevirtual javax/microedition/lcdui/List/append(Ljava/lang/String;Ljavax/microedition/lcdui/Image;)I
pop
; }
Label87:
; browser.setTitle(currDirName) - установить путь текущей папки как заголовок списка
aload_0
getfield FileBrowser/browser Ljavax/microedition/lcdui/List;
aload_0
getfield FileBrowser/currDirName Ljava/lang/String;
invokevirtual javax/microedition/lcdui/List/setTitle(Ljava/lang/String;)V
Label98:
; while (enumeration.hasMoreElements()) { - цикл, пока есть следующий элемент перечисления
aload_0
getfield FileBrowser/enumeration Ljava/util/Enumeration;
invokeinterface java/util/Enumeration/hasMoreElements()Z 1
ifeq Label136
; String fileName = (String)enumeration.nextElement() - получить имя следующего файла из перечисления
aload_0
getfield FileBrowser/enumeration Ljava/util/Enumeration;
invokeinterface java/util/Enumeration/nextElement()Ljava/lang/Object; 1
checkcast java/lang/String
astore_1
; browser.append(fileName, null) - добавить имя файла в список
aload_0
getfield FileBrowser/browser Ljavax/microedition/lcdui/List;
aload_1
aconst_null
invokevirtual javax/microedition/lcdui/List/append(Ljava/lang/String;Ljavax/microedition/lcdui/Image;)I
pop
; } - новая итерация цикла
goto Label98
Label136:
; if (fc != null) { - если объект соединения не равен null (был создан), тогда,
aload_0
getfield FileBrowser/fc Ljavax/microedition/io/file/FileConnection;
ifnull Label152
; fc.close() - закрыть соединение
aload_0
getfield FileBrowser/fc Ljavax/microedition/io/file/FileConnection;
invokeinterface javax/microedition/io/Connection/close()V 1
Label152:
goto Label156
Label155:
astore_1
Label156:
return
; } catch (IOException ioe) { } - обработать проверяемое исключение
.catch java/io/IOException from Label0 to Label152 using Label155
.end method
; создадим метод определения текущей папки
.method private traverseDirectory(Ljava/lang/String;)V
.limit stack 5
.limit locals 3
; if (currDirName.equals("/")) - если текущая папка является корневой, тогда,
aload_0
getfield FileBrowser/currDirName Ljava/lang/String;
ldc "/"
invokevirtual java/lang/String/equals(Ljava/lang/Object;)Z
ifeq Label20
; currDirName = fileName - текущей папке присваиваем выбранный путь
aload_0
aload_1
putfield FileBrowser/currDirName Ljava/lang/String;
; else - иначе,
goto Label105
Label20:
; if (fileName.equals("..")) { - если выбрано "..", тогда,
aload_1
ldc ".."
invokevirtual java/lang/String/equals(Ljava/lang/Object;)Z
ifeq Label80
; int i = currDirName.lastIndexOf('/', currDirName.length()-2) - определить позицию последнего '/', в пути к текущей папке
aload_0
getfield FileBrowser/currDirName Ljava/lang/String;
bipush 47
aload_0
getfield FileBrowser/currDirName Ljava/lang/String;
invokevirtual java/lang/String/length()I
iconst_2
isub
invokevirtual java/lang/String/lastIndexOf(II)I
istore_2
; if (i != -1) - если '/' найден, тогда,
iload_2
iconst_m1
if_icmpeq Label71
; currDirName = currDirName.substring(0, i+1) - текущей папкой станет путь до последнего '/'
aload_0
aload_0
getfield FileBrowser/currDirName Ljava/lang/String;
iconst_0
iload_2
iconst_1
iadd
invokevirtual java/lang/String/substring(II)Ljava/lang/String;
putfield FileBrowser/currDirName Ljava/lang/String;
; else - иначе,
goto Label77
Label71:
; currDirName = "/" - установить текущую папку, как корневую
aload_0
ldc "/"
putfield FileBrowser/currDirName Ljava/lang/String;
Label77:
; } else - иначе,
goto Label105
Label80:
; currDirName = currDirName + fileName - добавить к пути текущей папки выбранный элемент
aload_0
new java/lang/StringBuffer
dup
invokespecial java/lang/StringBuffer/()V
aload_0
getfield FileBrowser/currDirName Ljava/lang/String;
invokevirtual java/lang/StringBuffer/append(Ljava/lang/String;)Ljava/lang/StringBuffer;
aload_1
invokevirtual java/lang/StringBuffer/append(Ljava/lang/String;)Ljava/lang/StringBuffer;
invokevirtual java/lang/StringBuffer/toString()Ljava/lang/String;
putfield FileBrowser/currDirName Ljava/lang/String;
Label105:
return
.end method
Как ни печально, но на этом этапе нам придется распрощаться. Я надеюсь, что
чтение не прошло для вас даром, что вы многому научились и открыли для себя
увлекательный мир мобильного программирования. Реализация приведенных
примеров, их понимание и совершенствование — вот золотой ключик от дверей
в чудо-мир мобильных технологий. Пробуйте, ошибайтесь, но не опускайте руки.
Опыт приходит с практикой!
* * *
Эта книга не является учебником, документацией или серьезным методическим
пособием, это именно самоучитель — практические советы, минимум теории
и максимум практики, личные наблюдения автора, начиная с самого нуля.
Если вы действительно серьезно увлеклись мобильным программированием, то
у вас неизбежно появится масса вопросов, благо на сайте разработчиков языка
java.sun.com/j2me/docs/ (java.sun.com/javame/reference/apis.jsp)
имеется исчерпывающая документация, масса книг и ста-
тей (правда, на английском языке). Также за советом можно сходить на специа-
лизированные форумы, например, forum.juga.ru/, раздел которого так и называет-
ся: J2ME — форум о Java в мобильнике. Пользуйтесь поиском, не стесняйтесь
спрашивать: программисты любят помогать друг другу.
Количество обладателей мобильных телефонов уже приблизилось к двум милли-
ардам (!) и неуклонно растет, и каждый из них — потенциальный пользователь
ваших программ. Простота в использовании и всеобщая доступность в любое вре-
мя — вот залог успеха. Будущее именно за мобильными, портативными техноло-
гиями, так что запрыгивайте в поезд, пока не поздно.
Удачи вам!