----------------------- Page 1-----------------------

                               Е. Б      У Г si 3B>J-J 



       Пишем 

       ПРОГРАММЫ 

                       У с           и  ИГРЫ 

      для   сотовых 

       ТЕЛЕФОНОВ 



ОЮ20094628492651                                               020094628492. 



              А     -И 



                                                            V/. 


----------------------- Page 2-----------------------

                                          Е.  Буткевич 



 Пишем 

 ПРОГРАММЫ 

                       Ни  ИГРЫ 

для   сотовых 

ТЕЛЕФОНОВ 



           Москва •   Санкт-Петербург •   Нижний Новгород   •   Воронеж 

           Ростов-на-Дону   •   Екатеринбург •  Самара •   Новосибирск 

                            Киев • Харьков •   Минск 

                                    2006 


----------------------- Page 3-----------------------

ББК   32.882.9 



УДК     621.395:004.3 



Б93 



           Буткевич  Е. Л . 

 Б93       Пишем программы и игры для  сотовых телефонов.  —   СПб.:  Питер, 2006. — 

          204 с :  ил. 



           ISBN 5-469-01139-9 



               Эта книга —      общедоступный   самоучитель         программирования  для   мобильных   телефонов, с по- 

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

          овладеть     базовыми     знаниями   языка   Java,   навыками   работы       с  необходимыми       инструментами       и 

          утилитами,   написать   свои   первые   программы,  а   главное   —       увидеть  и показать  друзьям   результат 

           на  экране  своего   мобильника.  Книга написана доступным,  живым   языком   и ориентирована   вовсе 

           не   на   профессионалов,   а    на   всех,   кто  хотел   бы  расширить    возможности      своего   мобильника   и 

           испытывать   себя   в роли   программиста. 

               Попробуйте  —   это совсем  не сложно! 



                                                                                                  ББК   32.882.9 

                                                                                                 УДК     621.395:004.3 



 Все  права  защищены. Никакая часть данной  книги  не может быть  воспроизведена в какой бы то ни было  форме  без письменного 

 разрешения владельцев   авторских прав. 



 Информация,  содержащаяся   в   данной   книге,   получена  из  источников, рассматриваемых  издательством   как   надежные.  Тем   не 

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

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



 ISBN  5-469-01139-9                                                     '     © ЗАО Издательский дом «Питер», 2006 


----------------------- Page 4-----------------------

Краткое содержание 



Предисловие                                                                10 



Глава    1 . С чего начинать?                                              14 



Глава    2. Hello, World!                                                  23 



Глава    3. Пользовательский   интерфейс высокого уровня                   32 



Глава    4. Работа с графикой                                              42 



Глава    5. Структуры   и форматы данных   в J2ME                          52 



Глава    6. Организация многопоточных        приложений                    66 



Глава    7. Сохранение данных   и параметров приложения                    74 



Глава    8. Стандартные средства пользовательского   интерфейса ..8 5 



Глава    9. Организация потоков, работа с файлами                          96 



Глава  10. Ввод текста, работа со строками                                107 



Глава   11 .   Работа со временем, датой и календарем                      119 



Глава   12. Работа над ошибками                                           127 



Глава   13. Расширение функциональности   с помощью API                   137 



Глава   14. Приложения для   работы с Интернетом                          147 



Глава   15. Защита приложений                                             16 1 



Bonus. Распространение мобильных приложений. 



   Практические советы  по продажам мобильного   контента                 17 1 



Приложение 1 . Полный листинг программы «Змейка»                          173 



Приложение 2. Полный листинг программы «BookReader»                       190 



Приложение 3. Полный листинг программы «AddressBook»                      195 



Литература                                                                203 


----------------------- Page 5-----------------------

Содержание 



Предисловие                                                                          10 

К  читателям                                                                         10 

От издательства                                                                      12 

История языка Java                                                                   12 



Глава 1 .  С чего начинать?                                                          14 

Оборудование                                                                         14 

Программное обеспечение                                                              18 

Эмуляторы                                                                            20 



Глава 2. Hello, World!                                                               23 

Класс MIDlet                                                                         23 

Создание мидлета                                                                     25 

Импорт классов  и пакетов                                                            28 

Компиляция и запуск мидлета                                                          29 

Упаковка мидлета                                                                     31 



 Г л а ва  3 .  П о л ь з о в а т е л ь с к и й  и н т е р ф е й с   в ы с о к о г о  у р о в н я  ... .  3 2 

Класс Form                                                                           33 

Класс Display                                                                        34 

Отображаем   картинку                        ,                                       34 

Класс Command                                                                        36 

Интерфейс CommandListener                                                            38 



 Глава 4. Работа с графикой                                                          42 

Класс Canvas                                                                         42 

Класс Graphics                                                                       42 

Класс Graph                                                                          45 

Рисование текста                                                                     47 

 Обработка  клавишных   событий                                                      48 



 Глава 5. Структуры и форматы данных в J2ME                                          52 

 Объекты                                                                             52 

 Примитивные типы                                                                    53 

 Массивы                                                                             53 


----------------------- Page 6-----------------------

                                                                    Содержание            7 



Класс Vector                                                                              54 

Класс Snake                                                                              55 

Класс Stack                                                                              64 



Глава 6. Организация многопоточных приложений                                            66 

Многозадачность                                                                          66 

Класс Thread                                                        ,                    67 

Приоритеты тредов                                                                        68 

Синхронизация тредов                                                                    \ 70 

Класс Snake                                                                              72 



Глава 7 . Сохранение данных и параметров приложения                                     74 

RecordStore  — хранилище  записей                                                        75 

RecordEnumeration   — нумератор   списка записей                                         76 

RecordFilter  и RecordComparator   — фильтр и компаратор                                 78 

RecordListener    — лови момент                                                          80 

RecordStoreException      — возможные  проблемы                                          80 

High Score — таблица  рекордов                                                           81 



Глава 8. Стандартные средства 

    пользовательского интерфейса                                                         85 

Класс List                                                                               85 

Interface   Choice                                                                       86 

Класс SnakeGame                                                                          87 

Класс Gauge                                                                              93 



Глава 9. Организация потоков, работа с файлами                                           96 

Класс InputStream                           :                                            96 

Класс DatalnputStream                                                                    99 

Класс BookReader                                                                        101 

Класс ByteArraylnputStream                                                              106 

Класс OutputStream                                        j                             106 



Глава  10.   Ввод текста, работа со строками                                           107 

Класс TextBox                                                                           107 

Класс TextField                                                                         109 

Класс AddressBook                                                                        I l l 



 Глава  11 . Работа со временем, датой и календарем                                    119 

Класс Date                                                                              119 

Класс DateField                                                                         120 

Класс Calendar                                                                          122 

Класс TimeZone                                                                           123 

Класс BirthdayFilter                                                                    124 


----------------------- Page 7-----------------------

8         Содержание 



Глава 12. Работа над ошибками                                                 127 

Класс Exception                                                                127 

Формирование  исключений                                                      130 

Класс Alert                                                                    132 

Класс AlertType                                                               133 



Глава 13. Расширение функциональности с помощью API   .. . 137 

Nokia API                                                                     137 

Samsung API                                                                   141 

Siemens API                                                                    144 



Глава 14. Приложения для работы с Интернетом                                  147 

Класс Connector                                                               147 

Интерфейс  Connection                                                         148 

Интерфейсы  InputConnection и OutputConnection                                149 

Интерфейс StreamConnection                                                    149 

Интерфейс StreamConnectionNotifier                                            153 

Интерфейс DatagramConnection                                                  153 

Интерфейс  ContentConnection                                                  154 

Интерфейс HttpConnection                                                      155 



 Глава  15. Защита приложений                                                16 1 

Java Decompiler                                                                161 

Защита приложений                                                             167 

Обфускация                                                                    167 



 Bonus. Распространение мобильных 

    приложений. Практические советы 

    по продажам мобильного контента                                          17 1 



 Приложение 1 . 

    Полный листинг программы «Змейка»                                        173 



 Приложение 2. 

    Полный листинг программы «BookReader»                                    190 



 Приложение 3. 

     Полный листинг программы «AddressBook»                                   195 



Литература                                                                    203 


----------------------- Page 8-----------------------

Посвящаю эту книгу двум самым близким 

мне людям — отцу Леониду Михайловичу 

и супруге Ксюше. 


----------------------- Page 9-----------------------

Предисловие 



К  читателям 



Этот самоучитель поможет вам сделать первые шаги в программировании для мо- 

бильный телефон. Книга предназначена для всех желающих   научиться   разраба- 

тывать приложения для мобильников, причем адресована в первую очередь тем, 

кто уже   владеет   базовыми  навыками программирования и основами объектно- 

ориентированных языков. Тем не менее, она написана так, что даже самый нео- 

пытный  программист   будет  в состоянии реализовывать   приведенные примеры 

и обучаться   всем хитростям   сразу   на практике. В таком случае,  конечно же, не 

помешает дополнительная литература, поскольку многие базовые вещи програм- 

мирования остались за кадром. 



Для разработки приложений мы будем использовать платформу J2ME компании 

Sun Microsystems, поскольку на сегодняшний день это единственный универсаль- 

ный инструмент программирования для мобильных телефонов. 

Книга построена в виде самоучителя, разделенного на главы-уроки. В нем вы най- 

дете больше практических советов  и примеров, нежели теоретических   сведений 

из области  объектно-ориентированного программирования. Я постарался изло- 

жить  материал   максимально доступно, на пальцах, зачастую   не вдаваясь  в под- 

робности и тонкости. Многочисленные примеры, приведенные в книге, хоть и не 

являются образцом аккуратности кодирования или истиной в последней инстан- 

ции, но реализованы и протестированы с точки зрения доступности непрофесси- 

оналам. Опытные программисты могут найти книгу скучной, с избытком инфор- 

мации об азах программирования, местами несерьезной в отношении к сложным 

задачам, а кто-то будет с удовольствием  читать этот букварь. Основной упор кни- 

ги делается   на практическом применении материала, на том, какие средства мо- 

гут потребоваться во время разработок и как ими пользоваться. 



Красивая, элегантная, аккуратно реализованная идея имеет хорошие шансы при- 

нести создателю   некоторую прибыль. Поэтому после обучения собственно про- 

граммированию мы поговорим о процедуре   получения денег за свой программ- 

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

вас хорошим стимулом  к скорейшему обучению! 

Книга содержит   15 глав, каждая из которых обозревает отдельную  тему. Первые 

две главы  практически не касаются собственно программирования, но содержат 

подробную вводную информацию о том, как начать разработки, какое оборудова- 

ние и программное обеспечение вам понадобится для этого. Это базовая инфор- 

мация, основа, без которой дальнейшее программирование невозможно. 

Главы, касающиеся программирования, содержат теоретическую и практическую 

части. В теоретической части обсуждаются       классы и методы, реализующие  воз- 


----------------------- Page 10-----------------------

                                                                 К  читателям         1 1 



можности  платформы, а в практической подробно рассмотрена  реализация при- 

меров, использующих   описанные выше классы. Большинство примеров  перехо- 

дят из главы в главу, расширяя свою функциональность, поэтому   рекомендуется 

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

В  тексте   глав   примеры   приведены не полностью, а только та их часть,  которая 

реализует   новую функциональность. Если вы все же запутаетесь, то полный лис- 

тинг   некоторых   примеров с подробными   комментариями вы найдете   в  составе 

приложений в конце книги. 



Далее  приведен список всех глав с их кратким описанием. 

Глава 1. С чего начинать?  Обзор необходимых   средств для мобильного програм- 

мирования: модели телефонов, поддерживающие J2ME; коммуникационные ка- 

бели; необходимое программное обеспечение; студии разработки; эмуляторы; заг- 

рузка приложений в телефон. 

Глава 2. Первое приложение «Hello, World!» Описание создания первой програм- 

мы в студии  разработки J2ME Wireless Toolkit.  Структура   программы, компиля- 

ция, тестирование  программы на эмуляторе. 

Глава 3. Пользовательский интерфейс высокого уровня. Иерархия класса Screen, 

отображение формы и взаимодействие с ней. Классы Display, Screen, Form, Command. 

Пример программы фотоальбома  («Slide Show»). 

Глава 4. Работа с графикой. Классы Canvas, Graphics. Общая организация графи- 

ки, рисование простых геометрических  фигур. Разбор примера программы управ- 

ления точкой, рисующей  фигуры на экране («Float   Point»). 

Глава 5. Структуры и форматы данных в J2ME. Типы данных. Классы Vector, Stack. 

Работа с данными. Расширение программы «Float Point» до игры  «Змейка». 

Глава 6. Организация многопоточных  приложений, параллельное  программиро- 

вание. Класс Thread. Вопросы синхронизации. Расширение программы   «Змейка»: 

пример приобретает вид законченной, качественной игры. 

Глава 7. Сохранение данных приложения, сохранение параметров программ (RMI, 

класс   RecordStore).  Расширение  «Змейки»,  добавление  запоминания настроек и 

таблицы  рекордов. 

Глава 8. Стандартные средства пользовательского интерфейса, организация меню. 

Классы   List, ChoiceGroup. Управляющие   сигналы   высокого  уровня.  Реализация 

меню для «Змейки». 

Глава 9. Организация потоков, работа с файлами. Класс InputStream. Вопросы ло- 

кализации, проблема  корректного отображения  русского текста. Обзор програм- 

мы «Book Reader».  Класс Font. 

Глава 10. Ввод  текста,  работа   со строками. Классы TextBox, TextField.   Средства 

ввода и редактирования текста. Разбор программы «AddressBook», реализующей 

записную книжку. 

Глава 11. Работа со временем, датой и календарем. Классы Date, Calendar. Извле- 

чение  внутренней   даты   и времени  телефона.  Расширение записной  книжки до 

«Birthday   Reminder». 

Глава 12. Работа над ошибками. Классы Al ert, Error, Exception. Обработка исклю- 

чительных   ситуаций, процесс отладки  программ. 


----------------------- Page 11-----------------------

12         Предисловие 



Глава 13. Расширение функциональности MIDP 1.0 с помощью API фирмы-про- 

изводителя мобильного телефона. Nokia API, Samsung  API, Siemens API. Работа 

со звуком, дополнительные возможности. 

Глава 14. Методы   передачи   и приема данных.  Классы   Connector, Connection, 

OatagramConnecti on, StreamConnecti on, ContentConnecti on, HttpConnecti on. Реализация 

и разбор примеров интернет-приложений. 

Глава 15. Защита приложений (обфускация). Обзор декомпиляторов: просмотр, 

редактирование и усовершенствование чужих приложений. 

Bonus. Распространение мобильных приложений. Практические советы по про- 

дажам мобильного контента. 

Приложение 1. Полный листинг программы  «Змейка». 

Приложение 2. Полный листинг программы «Book Reader». 

Приложение 3. Полный листинг программы «Birthday Reminder». 

Весь этот путь он начала до конца я пройду вместе с вами и реализую все описан- 

ные примеры. В процессе реализации и тестирования примеров я буду приводить 

скриншоты, чтобы никто не усомнился в успешном тестировании. То есть все ре- 

ализовано, все проверено, все работает, чего и вам желаю. А теперь закончим всту- 

пительную часть словами классика. За мной, читатель! 



От издательства 



Ваши замечания, предложения и вопросы  отправляйте  по адресу электронной 

почты comp@piter.com   (издательство  «Питер», компьютерная редакция). 

Мы будем рады узнать ваше мнение! 

Все исходные  тексты, приведенные в книге, вы можете  найти по адресу   http:// 

www.piter.com/download. 

Подробную информацию о наших книгах вы найдете на веб-сайте   издательства: 

http://www.piter.com. 



 История языка Java 



По сравнению со своими старшими братьями в большой семье языков програм- 

мирования, Java   — совсем еще ребенок, причем едва  достигший   десятилетнего 

возраста. Но посмотрите, какой это ребенок! Давайте вместе оглянемся на первые 

дни этого малыша. 

Родители вундеркинда работали в компании Sun Microsystems,  которая в начале 

90-х  годов стояла перед задачей создания передового программного обеспечения 

для различных бытовых  приборов, грубо говоря — для чайников. Основная про- 

блема состояла в том, что для каждой новой модели бытовой техники существо- 

вали свои технические стандарты. 

Ведущему инженеру проекта Патрику Нотону приходилось поддерживать в раз- 

работке сотни интерфейсов, пока это ему окончательно не надоело. В один пре- 


----------------------- Page 12-----------------------

                                                          Иаория языка Java           13 



красный день он решил уволиться   ко всем чертям и понес заявление директору. 

Нужно  отдать   должное  дальновидному   руководству        в лице  Скотта МакНили, 

который попросил изложить причины ухода в подробном письме. Тут уж Патрик 

Нотон оторвался от души и написал все, что он думает о компании и о ее техноло- 

гиях, беспощадно раскритиковав недостатки Sun   Microsystems. 

Письмо возымело  на удивление   большой успех   как у  ведущих   инженеров, так 

и у   высшего  руководства   компании, а именно у   Билла Джоя, основателя   Sun 

Microsystems,     и  Джеймса   Гослинга,  непосредственного   начальника   Нотона. 

В день планируемого увольнения Патрик Нотон получает   зеленый свет  на реа- 

лизацию всех своих идей и собственную группу ведущих   разработчиков под ко- 

довым   названием  Green, чтобы   они делали   что  угодно,  но создали   нечто нео- 

быкновенное. 

Команда приступила к разработке нового объектно-ориентированного языка про- 

граммирования, который должен был стать ведущим на рынке бытовой электро- 

ники.  Вскоре   Нотон предложил   использовать  новые наработанные  технологии 

для интернет-приложений, в связи с чем были написаны компилятор Java и брау- 

зер   Hotjava.  23   мая  1995   года   компания   Sun   официально   представила   Java 

и Hotjava   на выставке SunWorld'95.   Именно тогда  наш «малыш»   сделал первый 

шаг к вашему мобильнику. 

Платформа Java 2 Micro Edition (J2ME)   была разработана уже  ближе к нашим 

дням для устройств  с ограниченными ресурсами памяти и процессора, таких как 

сотовые  телефоны,  пейджеры,  смарт-карты,  органайзеры   и миникомпьютеры. 

J2ME позволяет запускать Java-приложения на ресурсо-ограниченных вычисли- 

тельных  устройствах.  Для данных целей J2ME адаптирует   существующую   Java- 

технологию. 


----------------------- Page 13-----------------------

 Глава 1 



С   чего начинать? 



Эта глава  содержит   обзор необходимых   технических   средств для программиро- 

вания: моделей телефонов, поддерживающих J2ME, коммуникационных кабелей, 

программного обеспечения, студий  разработки, эмуляторов. 

Перед тем как перейти непосредственно к программированию, рассмотрим все, 

что может пригодиться нам в процессе разработки приложений для мобильных 

телефонов.  Подавляющее   большинство  людей   обычно   пропускают        введение 

в любой книжке, которая попадает к ним в руки. Если вы относитесь к этой ка- 

тегории, то  ничего  страшного  не произошло: информация там  была  чисто по- 

знавательного  характера.  Но начиная с этой главы, приготовьтесь   к серьезной 

работе,  поскольку   программирование  — это такая   наука,  которую   нельзя  вы- 

учить в теории. 



Нужно отметить, что сейчас начинается самый главный этап в нашем обучении. 

Не   сделав   всего, что написано в первой главе, дальше  можете  и не продолжать 

читать этот самоучитель,  поскольку его чтение без реальной практики — пустая 

трата времени. 

В любом самоучителе  на любую тему есть упражнения, которые необходимо вы- 

полнять. Ваши упражнения — это реализация примеров, описанных в книге, а без 

первого этапа — поиска и установки всего необходимого оборудования и программ- 

ного обеспечения   — эти упражнения   выполнять  просто  невозможно. Помните: 

чем строже учитель, тем большего результата   он добьется. Запаситесь терпением 

и настойчивостью и не делайте себе поблажек. 



Оборудование 



Итак, начинаем! Первое и самое необходимое  в нашем деле — это, конечно же, 

сотовый телефон с поддержкой языка Java. Тот, кто уже  давно подключил  свой 

телефон к компьютеру и успешно качает на него мелодии, картинки и Java-при- 

ложения, может смело пропустить вводную  часть вплоть до программного обес- 

печения. Ну а те, у кого уже установлено и все описанное далее программное обес- 

печение, могут  задуматься    о том, не следует ли  им отдать   предпочтение   более 

серьезной литературе. 

Далеко не все телефоны поддерживают Java-приложения, и если вы не уверены 

относительно нужной вам модели, то стоит заглянуть  в документацию телефона 

или   на сайт разработчиков developers.sun.com/techtopics/mobility/device  с полным 

списком моделей телефонов, поддерживающих   Java-технологию. 


----------------------- Page 14-----------------------

                                                                                Оборудование                15 



Мобильные устройства  не содержат собственных средств разработки — редакто- 

ров, компиляторов и прочих инструментов, да и не нужно это аппарату с двадца- 

тью кнопками и однодюймовым экранчиком. Поэтому придется прибегнуть к меж- 

платформенной  разработке, то  есть   программировать   на компьютере, а только 

потом переносить готовую программу  на телефон. Таким образом, наличие ком- 

пьютера — такое же необходимое условие в разработке мобильных приложений, 

как и наличие телефона. Надеюсь, что на компьютере у вас установлена операци- 

онная система Windows (которую ругают все кому не лень, но тем не менее про- 

должают ею пользоваться), поскольку все рассмотренное далее программное обес- 

печение предназначено именно для этой системы. 

Связать компьютер с телефоном можно несколькими способами. Мы рассмотрим 

наиболее  простой  и наименее дорогой   способ связи через дата-кабель.  Бывает, 

что дата-кабель идет в комплектации с телефоном, считайте тогда, что вам повез- 

ло. Если нет, то придется купить его отдельно, выложив за него от 15 до ,20 долла- 

ров. Покупайте кабель только для своей модели телефона (обычно совместимые 

модели указаны на упаковке). Даже если разъем кабеля подходит  к вашему теле- 

фону, то это еще ни о чем не говорит. В комплектации кабеля должен быть диск, 

содержащий драйвера для этого кабеля. 

Для начала следует установить драйвер для вашего кабеля. Большинство совре- 

менных кабелей подключаются к USB-порту компьютера и при включении будут 

обнаружены системой как новое USB-устройство. Вслед за обнаружением нового 

устройства автоматически запустится мастер обнаружения нового оборудования, 

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



             Найден новый мастер оборудования 



                 Установка драйверов  оборудования 

                     Драйвер • это программа, позволяющая устройству взаимодействовать с 

                     операционной системой. 



                                      ШШШжшш 



                    Этот мастер заверши установку для устройстьа 



                     Драйвером называется программа, ооеспечиваюшая работу устройства в 

                     операционной системе   Для любого нового устройства требуется установить 

                    драйвер пая Window:. Чтобы найти Файлы драйверов и завершить установку. 



                     r-iwwfji!!*, дей.-iet*. к-.-тог.-ч* . 'rfiiy   i ылючнип, i.iac-iepi) 



                        ••••   Йровести поип- под"эдд«иего"араймра для'устранстьа (рекомендуется].] 



                        С  Довести слиси  всех известных драйверов для зтсго устройства, из 



                                                           "< Назад;:-: j -;У; Даяее>;.||:,- ^'-.Отмена  j ; 

                                                                                      : 



                 Рис.  1.1 . Мастер оборудования поможет установить нужный драйвер 



 Вставьте диск с драйверами для кабеля, выберите в окне мастера  переключатель 

 Провести поиск подходящего драйвера для устройства, на следующем   этапе мастера 


----------------------- Page 15-----------------------

 16          Глава   1 . С чего начинать? 



выберите пункт размещение будет указано, а на следующем  шаге укажите на диске 

драйвер для вашей операционной системы. Если поиск нужного драйвера на дис- 

ке вызовет у вас затруднения, то можно вернуться к предыдущему   этапу мастера 

и выбрать   пункт  дисководы   компакт-дисков, тогда  система  выберет   подходящий 

драйвер с диска автоматически. 

Далее система установит драйвер и уведомит  вас о том, что установка программ- 

ного обеспечения для нового устройства завершена, указав его название, к приме- 

ру, Prolific   USB-to-Serial  Comm Port. Жмите кнопку Готово. 

Теперь  можно  посмотреть   результат   установки   устройства.   Щелкните правой 

кнопкой мыши на значке Мой компьютер на рабочем столе и выберите в контекст- 

ном меню самый нижний пункт — Свойства. Перейдите на вкладку   Оборудование, 

запустите   диспетчер   устройств,  нажав   соответствующую                          кнопку, и   проверьте 

пункт Порты СОМ и LPT. Здесь должно появиться новое устройство, а в скобках — 

номер СОМ-порта, с которым оно связано (рис. 1.2).  Запомните этот номер: он 

вам пригодится для настройки программы связи компьютера с телефоном. 



                      В-Щ     KMILLER 

                          ф   Ш  DVD и компакт-дисководы 

                          $••§ Видеоадаптеры 

                          Ш - в   Дисководы гибких дисков 

                          ф   0  Дисковые устройства 

                             ^   Другие устройства 

                                Звуковые, видео и игровые устройства 

                          Ш   iSg Клавиатуры 

                          ф   HJ   Компьютер 

                          ф- в   Контроллеры IDE ATA/ATAPI 

                          ffi ©  Контроллеры гибких дисков 

                                Контроллеры универсальной последовательной шины USB 

                             __ Мониторы 

                          Й   ^  Мыши и иные указывающие устройства 

                          &J?    Порты СОМ и LPT 



                                Jjf  Порт принтера ЕСР (LPT1) 



                                • Jjf  Последовательный порт (СОМ1) 



                                ••Jjf  Последовательный порт (COM2) 



                                Сетевые платы 



                                Системные устройства 

                          ш-, 

                          Ш - Ш  Тома хранения данных 



                            Рис.  1.2.  В списке устройств появился дата-кабель 



 Если вы видите  новое устройство, значит установка  прошла успешно. На самом 

 деле, дата-кабель может дать неограниченные возможности по управлению вашим 

 телефоном, от закачки новых игр, программ, мелодий и картинок до обеспечения 

 компьютеру   выхода   в  Интернет (технология   GPRS) и прошивки исполняющей 

 системы телефона. Кстати, с прошивками будьте аккуратны и всегда отдавайте себе 

 отчет   в том, что вы делаете — хотя бы для того, чтобы специалист, в случае чего, 

 смог вам помочь. 


----------------------- Page 16-----------------------

                                                                                Оборудование               17 



Программы  связи  компьютера   с телефоном  не входят  в комплект дата-кабеля, 

обычно они содержатся на диске, который идет в комплектации самого телефона. 

Если по каким-то причинам программа для закачки приложений не обнаружива- 

ется на диске или отсутствует сам диск, то придется искать программу, совмести- 

мую   с вашей  моделью   телефона, в дебрях   Интернета. Вооружитесь   терпением: 

если у   вашей модели  телефона есть  возможность  закачки, значит должна   быть 

и программа. Кто ищет, тот... сами знаете — что. 

Для кого-то более легким покажется другой путь: зайти в киоск с программным 

обеспечением и купить себе один из многочисленных дисков типа «Все програм- 

мы для телефона...»  (нужное вписать). К сожалению, в рамках данной главы мы 

не сможем   рассмотреть   программы  для   всех  моделей   телефонов, поэтому   для 

примера ограничимся одной из подобных программ для аппаратов Nokia  7210, 

3510i, 6100,  6610.  Программа связи компьютера с телефоном, которую  мы рас- 

смотрим, называется Mobile Media Browser (MobiMB), ее можно скачать по ад- 

ресу www.logomanager.co.uk, однако там она стоит денег, поэтому будем рассмат- 

ривать ее только в качестве наглядного пособия для всех программ такого рода, 

поскольку все они обладают   схожими настройками и управлением. 

Для настройки программы связи в меню Connection settings (Параметры соедине- 

ния) следует добавить новое соединение с помощью команды Add connection (До- 

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

которых   вы должны   выбрать  тип своего  кабеля, указанный на упаковке  (типы 

кабелей могут быть, например, такими: FBUS, DLR-3, DKU-5). В свойствах ново- 

го соединения следует также  указать  номер СОМ-порта, с которым операцион- 

ная система связала наш кабель на этапе его установки. 

Теперь подключаем   телефон к компьютеру   с помощью дата-кабеля, в программе 

MobiMB, может быть, потребуется нажать кнопку Refresh   (Обновить), чтобы уско- 

рить   процесс определения телефона  программой. После того  как телефон будет 

обнаружен и определен, в левом окне программы появится изображение телефона 

со списком, содержащим   имена нескольких папок (рис. 1.3).   Если на этом этапе 

соединение не устанавливается,  попробуйте  подключить  кабель к другому USB- 

порту, проверьте соответствие  кабеля вашей модели телефона, убедитесь,  что ка- 

бель установлен в операционной системе, а соединение настроено правильно. 



           [ I f   MobiMB Mobile Media Browser 



              File   E *  tfew   Help 



             'P]        % ©      &    0 

            В-^    Nokia6100                          :                    1          Stee l 

                                          Name    й й ; ? ^ ; > \ : ; > : : ' . :   ''••.:'.•.' 

                B - Q  Gallery            SFIoatPoint                               41KB 

                1    j  Q  Images         SelackCat                                 58 KB 

                    '  P3 Tones 

                                                                                    63 KB 

                j  i||j  Applications 



                   _ |   Games 



                                                      .               ,„„„„ 



                       Рис.   1.З.  Медиа-браузер дает доступ к памяти телефона 


----------------------- Page 17-----------------------

18          Глава   1 . С чего начинать? 



Мы видим, что программа успешно обнаружила аппарат и дала нам доступ к четы- 

рем папкам телефона: Gatlery\Images, Gallery\Tones, Applications,  Games, где хранятся 

картинки, мелодии, Java-приложения и Java-игры соответственно. В нижней стро- 

ке выводится объем свободной памяти телефона. Нас, конечно же, интересуют две 

последние папки: именно там мы будем размещать созданные нами программы. 

Немного о программах,  которые мы будем разрабатывать.  Приложение для мо- 

бильного телефона состоит, как правило, из двух файлов: архива с расширением 

 .jar, содержащего байт-код исполняемой части приложения и его ресурсы, и фай- 

ла-описателя   с расширением  .jad   для   первоначальной проверки  совместимости 

с устройством  и настройки различных атрибутов. После того как связь с телефо- 

ном установлена,  с закачкой Java-приложения   в телефон  справится даже ребе- 

нок: нужно выделить jar- Hjad-файлы и перетащить их мышью в папку Applications 

или Games, после чего должен начаться процесс копирования. 

Если копирование не началось, значит аппарат отвергает   приложение, ориенти- 

руясь на параметры, указанные в jad-файле описателя программы. Причин может 

быть несколько: либо размер приложения превосходит максимально допустимый 

(например, для аппаратов Nokia серии 40 максимально допустимый размер при- 

ложения составляет   64  Кбайт), либо недостаточно свободной памяти, либо дан- 

ные в файле описателя некорректны. 

Если приложение было успешно скопировано, то можно отсоединять телефон от 

провода, заходить   в раздел, куда вы скопировали приложение, и запускать  вашу 

программу,  которая   теперь   не зависит   от  средств   разработки, устройств   связи 

и дополнительного   оборудования.  Теперь   приложение  всегда   у   вас  в кармане 

и вы можете воспользоваться им в любой момент. 

 На  этом  обзор   необходимого   оборудования   и его  настройки закончен. Следу- 

ющий этап  — поиск и установка  средств   разработки  мобильных  Java-приложе- 

ний. На этом этапе вам желательно иметь доступ  в Интернет, поскольку именно 

там мы будем черпать необходимые программные ресурсы. Рассчитывайте на то, 

что в общей сложности вам придется скачать около 100  Мбайт. Если это непри- 

 емлемо, то вам прямая дорога в интернет-кафе или крупный магазин с програм- 

 мным обеспечением. 



 Программное обеспечение 



 Теперь   рассмотрим   необходимый   минимум   программного  обеспечения, кото- 

рый должен   быть  установлен   на вашем  компьютере, чтобы   перейти непосред- 

 ственно к программированию. Сразу  оговорюсь, что существует масса   средств 

 разработки, программ, студий, интегрируемых   сред, и мы разберем лишь самый 

 простой вариант программного обеспечения, своего рода необходимый  «канди- 

 датский минимум», без которого говорить дальше просто не о чем. Настоятель- 

 но рекомендую  устанавливать   именно то, что я скажу, а не то, что под руку по- 

 падется:  так   всем   будет  проще, поскольку   на первом  этапе объяснения я буду 

 производить на пальцах — в стиле «нажми то, нажми это». Скажу также, что все 

 программное обеспечение распространяется компанией Sun Microsystems  абсо- 

лютно безвозмездно, то есть даром, поэтому, в худшем случае, вам придется пла- 

тить лишь за трафик. 


----------------------- Page 18-----------------------

                                                                 Программное обеспечение                      19 



Технология   языка Java  связана  с тем, что для   запуска   приложений   требуется 

виртуальная  машина Java. Наша операционная система, не буду лишний раз го- 

ворить какая, содержит   свою версию виртуальной  машины, которая, мягко го- 

воря, нам не подходит,  поэтому следует раздобыть   и установить исполняющую 

среду разработчика JavaTM   2 SDK, Standard Edition (J2SE  SDK), version 1.4.2. 

Скачивается   эта   штуковина   с   сайта   компании  Sun   Microsystems                           по  адресу 

Java.sun.com/j2se/1.4.2/download.html,                 «весит»  52   Мбайт и устанавливается                 без 

особых проблем: когда попросят, смело жмите  Next и соглашайтесь на все пред- 

ложенные условия. На всякий случай запомните, куда вы установили исполня- 

ющую среду, поскольку в дальнейшем какие-нибудь ленивые программы могут 

вас об этом спросить. 

Второе, что нам понадобится, — это набор средств для разработки Java-приложе- 

ний Sun Java Wireless  Toolkit   (J2ME WTK).   На данный момент на том же сайте 

разработчиков по адресу java.sun.com/products/sjwtoolkit/                     доступна версия WTK   2.3 

Beta размером двадцать с небольшим мегабайт. Я же пользовался чуть ли не первой 

версией этого набора, которой оказалось вполне достаточно. Сам набор содержит 

все необходимые нам средства разработки: компилятор, упаковщик, несколько стан- 

дартных эмуляторов телефонов и еще некоторые полезные утилиты. 



При установке WTK   выполняет поиск виртуальной   машины Java, уже  нами ус- 

тановленной, и уточняет   ее  местоположение.  Важно: устанавливать                               программу 

следует в папку, полный путь к которой не содержит  пробелов и русских симво- 

лов, поскольку   в дальнейшем это может  привести к проблемам. На все вопросы 

в процессе установки следует отвечать   утвердительно. 

 Основная утилита   набора, которой мы будем пользоваться   на протяжении всей 

книги, называется KToolbar (рис. 1.4). 



          I H J2ME Wireless Toolkit 



            File  Edit         Help 



           I  ^   New Project... |  g :j * i Project                    Й   • л  Rs.;;- jj  ^  gea r Console  I 

                                                                                   ; 



           :  Device  1   . ••..:.                                  d 



           Create    a  new  p r o j e c t or   open  an existing one 



                                Рис.  1.4.  Основное окно утилиты Ktoolbar 



 Это и есть, собственно, наше окно — если не в Европу, то в большой и увлекатель- 

 ный мир мобильного программирования: необходимый минимум функциональ- 

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

 На этом «обязательные  программы», без которых просто нельзя разработать мо- 

 бильное приложение, заканчиваются, и начинаются «произвольные программы», 

 которые не обязательны, но желательны. 


----------------------- Page 19-----------------------

20         Глава 1 . С чего начинать? 



Эмуляторы 



При разработке приложений нам неоднократно потребуется  их тестировать, что- 

бы вовремя выявить возможные ошибки и неточности. Можно, конечно, каждый 

раз копировать программу на телефон, но, боюсь, это сделает процесс разработки 

слишком трудоемким  и утомительным. Поэтому в процессе отладки удобно вос- 

пользоваться эмулятором телефона — программой, которая имитирует  реальное 

устройство на вашем компьютере. Эмуляторы предоставляют возможность запус- 

ка, отладки и управления мобильным приложением, что позволит избежать мно- 

гократного копирования и поможет предотвратить ошибки, способные повредить 

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

разработки приложения переносится на компьютер, а в телефон загружается толь- 

ко окончательная версия приложения. 

Несколько эмуляторов содержатся в уже установленном нами наборе J2ME Wireless 

Toolkit. В дальнейшем мы будем их использовать, однако они имеют ряд серьезных 

недостатков. Во-первых, это эмуляторы  «телефонов вообще», а не каких-то конк- 

ретных моделей, поэтому они не смогут отразить всей специфики конкретного ап- 

парата, начиная с размеров экрана и расположения функциональных клавиш и за- 

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

Во-вторых, эмуляторы встроены в J2ME WTK  и не позволяют запускать приложе- 

ния, созданные не в этой среде разработки, то есть просто скопированные на компь- 

ютер. При желании это ограничение можно обойти, но особого смысла я в этом не 

вижу, поскольку существуют более продвинутые эмуляторы, которыми мы и вос- 

пользуемся. 



К более продвинутым  относится эмулятор телефона Nokia 7210, которым можно 

воспользоваться при тестировании приложений для любых моделей самой распро- 

страненной серии 40 телефонов Nokia. Эмулятор входит в состав среды разработки 

приложений Nokia 7210 MIDP SDK 1.0, который можно скачать бесплатно на сай- 

те разработчиков www.forum.nokia.com/tools   (размер дистрибутива — 18 Мбайт). При 

установке пакета программа потребует  серийный номер, который нужно отдельно 

 получить на том же сайте. Соглашайтесь на все условия и вводите серийный номер. 

А теперь снова внимание: эмулятор нужно установить в папку wtklib\devices, нахо- 

дящуюся внутри директории, где установлен J2ME Wireless Toolkit. Делать это сле- 

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

руемых устройств интерфейса J2ME WTK, а также чтобы компилятор самостоятельно 

 обнаружил специальные функции производителя. 



 После того как установка завершена, можно запускать эмулятор, который не толь- 

 ко выглядит  презентабельнее стандартных  эмуляторов от Sun Microsystems   и по- 

зволяет запускать Java-приложения, но и эмулирует  все остальные функции теле- 

 фона Nokia: записная книжка, будильник и даже настройки — все по-настоящему. 

 Чтобы запустить   приложение, нужно  в меню  File (Файл) выбрать   команду Open 

 (Открыть), после чего указать путь к приложению, которое следует запустить. Те- 

 перь, прежде чем загружать   себе в телефон все подряд, можно убедиться, что игра 

 или программа действительно того стоит. Этим эмулятором я и буду пользоваться 

 на протяжении всей книги (рис. 1.5). 

 Кроме собственно запуска приложений, с его помощью можно следить за трафи- 

 ком  сетевых   приложений и распределением   выделяемой  памяти в окне статуса 


----------------------- Page 20-----------------------

                                                                   Эмуляторы         2 1 



выполнения приложения, которое можно открыть 

через пункт меню Tools (Инструменты). Отметим 

также, что без исполняющей среды J2SE SDK ус- 

тановить эмулятор  нельзя, поэтому не спешите, 

а делайте все по порядку, не забегая вперед. 

Мобильный вариант языка Java  разрабатывал- 

ся как универсальный, то есть совместимый со 

всеми  моделями  телефонов,  поддерживающи- 

ми стандарт J2ME. Однако в каждом  аппарате 

могут быть свои нюансы: различия в размерах 

экрана,    ограничение     на  объем   приложений 

и другие подводные камни. Так что если вы со- 

брались продавать  ваш программный продукт, 

будь то игра или просто полезное приложение, 

то  обязательно   нужно   протестировать   его  на 

каждом   аппарате   из  заявленного   модельного 

ряда.  Мало ли  что   — а подрыв   репутации мо- 

жет быстро свести ваши заработки к нулю. Про- 

верить   нужно   хотя   бы  на  эмуляторах,   однако 

найти, скачать и установить эмуляторы всех мо- 

делей   с поддержкой Java  — занятие, мягко го- 

воря, кропотливое  и требующее   как минимум 

железной нервной системы. 

 Здесь на помощь может прийти сетевая система 

под названием Tira Developer Network от компа- 

нии Tira Wireless, специализирующейся на раз- 

работке мобильных приложений. Данная систе- 

ма, доступная   по адресу   www.tiradeveloper.com, 

создана для тестирования приложений на совме-           Рис. 1.5.  Так  выглядит эмулятор 

                                                            телефона с запущенной 

 стимость  со всеми распространенными моделя-                     на  нем игрой 

ми телефонов. Для   тестирования   предлагается 

 около 150 мобильных устройств с поддержкой J2ME. В процессе проверки прило- 

жение может пройти около 60 специальных тестов, способных проверить совмес- 

тимость приложения со всеми распространенными стандартами. 

 Для пользования системой вам достаточно зарегистрироваться на сайте, выбрать 

 интересующее вас устройство, с помощью специальной формы закачать свое при- 

ложение и получить   вердикт   о соответствии   вашего  продукта   выбранному   уст- 

 ройству.  Удобно,  не правда  ли, тем  более  что  платить за это никому не нужно. 

 Хотя   немного смущает   тот факт, что для проведения тестов  приложения нужно 

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

 приложение безвозмездно   оказалось  в чужих  руках, то лишний раз   подумайте, 

 надеяться на их порядочность или нет. 



 Итак, чтобы начать разработку   приложений для мобильных телефонов, нам по- 

 требуется: 

 •    телефон с поддержкой  Java-технологии, 

 •    компьютер с операционной системой Windows, 

 •    кабель для связи компьютера с телефоном. 


----------------------- Page 21-----------------------

22             Глава   1 . С чего начинать? 



При этом на компьютере должно быть установлено следующее программное обес- 

печение: 

•     драйвер кабеля, 

•      программа для закачки приложений в телефон, 

•      исполняющая среда Java-разработки J2SE SDK (version   1.4.2), 

 •     набор средств для разработки J2ME   WTK, 

 •     эмулятор вашей модели телефона — желателен, но не обязателен. 

Объем   всего   перечисленного   программного   обеспечения   составляет                                         не  более 

 100 Мбайт. 



                    ' . ' , . •                             *  *   * 



На этом  самый важный  подготовительный   этап  закончен. В следующей                                                  главе 

мы перейдем  непосредственно к программированию. Базовый набор программ 

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

без которой никакие рычаги нам не помогут. Я понимаю, что вам уже не терпит- 

ся перейти к самому   интересному, как в детективе  — заглянуть   в самый конец, 

но я  не рекомендую   этого делать,  не усвоив   всего, что  написано в этой  главе. 

Дальше  вас ждет  масса интересных  примеров приложений. Реализуйте  их, со- 

вершенствуйте, оптимизируйте — и помните, что не ошибается только тот, кто 

ничего не делает. Добро пожаловать в современный мир программирования для 

мобильных   устройств! 


----------------------- Page 22-----------------------

 Глава 2 



Hello, World! 



В   этой  главе   мы  рассмотрим   структуру   программы для   мобильного  телефона, 

а также все этапы ее создания и загрузки в телефон. Будем  считать, что все необ- 

ходимые  провода уже  приобретены и подключены к телефону, который поддер- 

живает платформу J2ME, а соответствующее   программное обеспечение успешно 

установлено на вашем компьютере. 

Для   создания  полноценного мобильного  приложения нам придется выполнить 

следующие  этапы разработки: 

•    написание кода (текста) программы на языке J2ME; 

•    компилирование программы; 

•    тестирование приложения на эмуляторах; 

•    упаковка приложения в jar-архив; 

•    загрузка приложения в мобильный телефон. 

Не будем спешить и для начала выясним, что же представляет собой приложение 

для мобильного телефона и что у него внутри. 



 Класс  MIDIet 



Поскольку Java  2 Micro Edition  поддерживает   большое количество разных   уст- 

ройств, то существуют различные конфигурации этой платформы. Не вдаваясь 

в подробности, мы будем использовать конфигурацию CDLC (Connected Limited 

Device Configuration), расширенную профайлом MIDP (Mobile Information Device 

Profile), который обеспечивает необходимую  функциональность для программи- 

рования под мобильные телефоны, пользовательский интерфейс, постоянное хра- 

нение данных, таймеры, средства   сетевой  связи. Мидлет (MIDIet) — это и есть 

приложение Java, которое использует  профайл MIDP и конфигурацию CLDC. 

Название «мидлет» происходит  от имени основного класса мобильного прило- 

жения javax. mi croedi ti on. mi dl et. MIDI et. Основной класс приложения должен быть 

унаследован   от   абстрактного   класса   MIDIet   и должен   реализовывать   методы 

startAppC), pauseApp()  и destroyApp(boolean).  К сожалению, основы объектно-ори- 

ентированных языков остаются за рамками этой книги, поэтому те, кто считает, 

что   ООП — это   Организация   Освобождения   Палестины, могут  на этом  этапе 

почитать дополнительную литературу. Особо нетерпеливые могут сразу перей- 

ти к водным процедурам   и подхватывать   идеологию  и терминологию языка на 

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


----------------------- Page 23-----------------------

24          Глава 2. Hello, World! 



Реализация   MIDP дает программисту   много возможностей и большую   свободу 

действий, однако и эти возможности  не безграничны, поэтому   начнем с ложки 

дегтя — той головы, выше которой не прыгнуть  в классической реализации язы- 

ка.  MIDP не поддерживает  системного программирования, не дает доступа к сис- 

темным  функциям устройства   и самой  исполняющей системе. В компетенцию 

MIDP   не входит управление  пользовательскими   приложениями и обеспечение 

средств для загрузки, хранения или запуска приложений. 

При    запуске загруженного   в телефон приложения события развиваются следу- 

ющим образом. Управляющая   система создает  экземпляр основного класса, на- 

следованного от класса MIDI et, используя конструктор без аргументов, доставшийся 

в наследство от базового класса. Приложение переходит в состояние PAUSED, после 

чего система вызывает метод startApp(), который и будет являться для нас точкой 

входа в программу.  «А   как же mainO?», — спросят опытные Java-программисты. 

Метода main() мы не видим, но он есть!  Это часть реализации МШР-профайла, 

и   программное обеспечение телефона вызывает его без нашего ведома. 



После вызова метода startAppC)   приложение переходит в состояние ACTIVE, захва- 

тывает   необходимые  ресурсы   и начинает заниматься своими делами. Приложе- 

ние   выполняется до тех пор, пока система не переведет его в одно из состояний — 

PAUSED или DESTROYED. Три упомянутых   состояния и составляют  жизненный цикл 

приложения. Остановимся на каждом из них. 

 •   ACTIVE — активное  состояние.  Приложение  готово   к запуску             или  уже   вы- 

    полняется.  Система   может   приостановить   выполнение  приложения, выз- 

    вав  метод   pauseAppO,  или совсем  прервать   выполнение с помощью   метода 

    destroyApp(boolean) . 

 •    PAUSE — приостановленное состояние. Приложение прекращает выполнение 

    задач и освобождает  часть ресурсов. Выполнение приложения может быть во- 

    зобновлено системой с помощью метода startAppC)   или полностью остановле- 

    но  методом   destroyApp(boolean). 

 •    DESTROYED — прерванное состояние. Приложение остановлено и уже  не может 

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

 «Жизненный цикл» приложения может быть представлен схемой, показанной на 

 рис.  2.1. 

 Система управляет   сменой состояний приложения с помощью абстрактных ме- 

 тодов класса MIDlet, обязательных  к реализации. Кроме того, класс MIDI et предо- 

 ставляет  нам еще несколько методов для контроля над состоянием приложения 

 и   получения информации о нем: 

 •    abstract void startAppC)  — вызывается системой при переходе мидлета в активное 

    состояние. Метод может быть вызван только из приостановленного состояния; 

 •    abstract   void pauseApp()   — вызывается системой при переходе  мидлета  в при- 

    остановленное состояние. Приложение должно освободить совместно исполь- 

    зуемые  ресурсы; 

 •    abstract void destroyApp(boolean   unconditional) — вызывается при переходе мид- 

    лета   в прерванное состояние. Приложение должно   освободить   все   ресурсы 

    и сохранить все необходимые данные в постоянном хранилище. Если аргумент 

    uncondi t i ona 1 задает значение true, то приложение будет прервано в любом слу- 


----------------------- Page 24-----------------------

                                                                 Создание  мидлета           25 



   чае. Если передан аргумент   f a I se, то система может отказаться переводить при- 

   ложение в прерванное состояние и сформировать исключение MIDI etStateChange 

   Exception; 



                         new   О 



                                                              destroyAppO 



                           Рис.   2.1 . «Жизненный цикл» приложения 



    void resumeRequest()   — используется для оповещения системы о желании прило- 

   жения перейти в активное состояние, когда система будет готова перевести мид- 

   лет в активное состояние, она сама вызовет метод sta rtApp(). Метод resumeRequest () 

    вызывается   приложением в приостановленном состоянии, в котором   поддер- 

    живается обработка таймера; 



    void noti fyPaused()   — приложение информирует систему о переходе в приоста- 

    новленное состояние. Метод не будет иметь эффекта, если приложение еще не 

    запущено или уже прервано; 



    void noti fyDestroyed()   — мидлет  информирует систему о переходе в прерванное 

    состояние. С этого момента система будет считать все ресурсы   свободными; 



    Stri ng getAppProperty (Stri ng key) — возвращает параметр приложения, хранящийся 

    в файле описателя мидлета, в виде строки. Аргумент key задает имя необходимо- 

    го параметра.  Если запрашиваемый параметр не задан, то возвращается значе- 

    ние   nul 1. Имена параметров мы рассмотрим позже вместе со структурой   файла 

    описателя. 



Создание мидлета 



Итак, для создания мидлета  остановимся на одном из перечисленных   в первой 

главе средств   разработки J2ME   Wireless  Toolkit          (WTK),   с помощью   которого 

реализованы все приведенные в этой книге примеры. После установки Wireless 

Toolkit запускаем его основную утилиту KToolbar. Доступные после запуска дей- 

ствия — это NewProject  (Новый проект) и OpenProject (Открыть проект). Можно 


----------------------- Page 25-----------------------

26             Глава   2 . Hello, World! 



открыть   демонстрационные   проекты   и на готовых   примерах   рассмотреть   воз- 

можности   и особенности   языка, но  мы  создадим   новый  проект   и назовем   его 

MyFirstMidlet.  Так   же   назовем   и основной  класс   мидлета   (MIDlet   Class Name) 

в окне создания нового проекта (рис. 2.2). 



                                    Help 



               &    New Project       P ?  Open Project  . j   .             •          I   .                 Consul? 

                 } 



             Create a new project or open an existing one 



                         jENew Project 



                         Pioiect  Nan*      JMyFirstMidlet 



                         MIDlet Class Name  JMyFirstMidlet                                                    ! 



                                                                            CteateProiect     |   Cencel 



                                       Рис. 2.2 . Окно создания нового проекта 



 После нажатия кнопки Create Project (Создать проект) появится диалоговое окно 

 ввода информации для файла — описателя приложения (рис. 2.3). Диалоговое окно 

 состоит из нескольких вкладок, первая из которых  — Required  (Обязательные) — 

 содержит  обязательные атрибуты файла-описателя: 

 •      MIDlet-Jar-Size — размер jar-файла   в байтах. Обратите   внимание, что данный 

      атрибут  прописывается   в файле  описателя   автоматически   средствами                                               WTK 

      при упаковке  проекта. Будьте аккуратны   при ручном  редактировании jar-ар- 

      хива: несовпадение заявленного и реального размеров jar-файла послужит при- 

      чиной ошибки при загрузке  приложения в телефон; 



                    SffiSettings for project "MyFirstMidtet"                                                   xj 



                         ч|' ed | Optional j  User   Defined | MIDtets | 



                                          Key                                         Value 



                     MIDlet-Jar-Size                              1100 

                     MIDIet-Jar-URL                               iMyRrstMidletjar 

                     MIDIet-Name                                  JMyFirstMidiet 



                     MIDIet-yersion 

                     rticroEdition-Configuration                  JCLDCTF 



                     MicroEdition-Profile                          IMIDP-1.0 



                                                                                            OK          Cancel 



                          Рис.  2.З . Определение обязательных атрибутов приложения 


----------------------- Page 26-----------------------

                                                             Создание мидлета          2 7 



•    MIDlet-Jar-URL — сетевой адрес и название jar-файла.  В нашем случае это поле 

   содержит лишь имя jar-файла; 

•    MIDlet-Name — имя мидлета  (или комплекта мидлетов), которое будет показа- 

   но пользователю   в меню запуска приложения; 

•    MIDlet-Vendor — разработчик  приложения (организация или частное лицо); 

•    MIDLet-Version   — версия  мидлета  в формате <major>.<minor>.<micro>;  может 

   быть использована при установке  и обновлении приложения; 

•    MicroEdition-Configuration  — требуемая    конфигурация J2ME   для   выполнения 

   мидлета.  В рамках нашей книги используется   конфигурация CDLC-1.0; 

•    MicroEdition-ProfiLe   — требуемый профайл J2ME для выполнения мидлета. Здесь 

   мы рассматриваем работу  с профайлом MIDP-1.0. 

Поля ввода значений необязательных атрибутов  расположены на вкладке Optional 

(Факультативные): 

•    MIDlet-Data-Size — минимальное количество байт данных для постоянного хра- 

   нения, требуемое   мидлетом. Значение поля по умолчанию  — ноль; 

•    MIDlet-Delete-Confirm   — определяет, должна ли система запрашивать  подтверж- 

   дение пользователя   при удалении   приложения из  устройства; 

•    MIDlet-Description — описание мидлета  или набора мидлетов; 

•    MIDlet-Icon — имя графического файла .png в jar-архиве, который будет исполь- 

   зован системой для данного мидлета  в меню запуска приложений; 

•    MIDlet-Info-URL — сетевой   адрес   с описанием  приложения   или информацией 

   о разработчике; 

•    MIDlet-Install-Notify   — указывает, должна ли система уведомлять   пользователя 

    перед установкой  нового  мидлета. 

Разработчик также имеет возможность определить собственные атрибуты прило- 

жения на вкладке  User Defined  (Определенные  пользователем). Имена  атрибутов 

не должны   начинаться  с префикса MIDlet- и могут быть  получены   во время вы- 

полнения приложения с помощью метода getAppProperty   (String key) класса MIDI et. 

После того как все необходимые  атрибуты   указаны, нажимаем кнопку О К и полу- 

чаем сообщение о том, что проект был успешно создан. Разберемся, что в данный 

момент произошло. Для этого перейдем в папку, где установлен Wireless  Toolkit. 

В   каталоге   проектов   apps   появилась   новая   папка   с   именем   нашего   проекта 

MyFirstMidlet. В корневом каталоге   нашего  проекта были автоматически   созданы 

еще несколько папок. Рассмотрим их назначение: 

•    /bin  — папка для хранения файлов приложения: jar-архив   приложения, файл 

    описателя  приложения .jad   и файл манифеста  MANIFEST.MF. Автоматически   соз- 

    данные файлы  MyFirstMidlet.jad   и MANIFEST.MF содержат   указанные   при созда- 

    нии проекта   атрибуты; 

 •   /res  — файлы  ресурсов,   используемые   приложением   во  время   выполнения. 

    Файлы могут содержать текстовые или бинарные данные, картинки в формате 

    PNG (Portable Network   Graphics); 

 •   /src — исходные файлы приложения, содержащие собственно код программы. 

    Файлы должны иметь расширение .Java. 


----------------------- Page 27-----------------------

28          Глава  2 . Hello, World! 



Пришло время   перейти непосредственно к программированию, уверен, что вам 

уже   надоело  читать   это скучное  введение  и уже   не терпится   перейти со своим 

телефоном «на ты». К сожалению, среда Wireless Toolkit не содержит текстового 

редактора, поэтому нам потребуется любой другой редактор, на худой конец обыч- 

ный   блокнот. Достаточно простые и удобные в обращении, с моей точки зрения, 

такие редакторы, как UltraEdit или EditPlus, хотя опять же это дело вкуса, дан- 

ный   вопрос не принципиален, можете пользоваться и блокнотом. 



 Импорт классов и пакетов 



Начнем с того, что классы, предоставляемые нам реализацией J2ME, объединены 

в  пакеты, представляющие  группу   классов определенной тематики. Для компи- 

лятора имя класса составляют   название класса и полное название пакета, где он 

содержится. Такое имя может быть достаточно длинным, поэтому можно импор- 

тировать класс с помощью оператора  import, а в дальнейшем пользоваться лишь 

названием класса. Синтаксис оператора import достаточно простой: 

import  <имя пакета>.<имя класса>: 



Таких операторов  может быть сколько угодно. Они должны располагаться в са- 

мом начале программы. Существует   возможность импортировать сразу все клас- 

сы,   содержащиеся   в пакете. Для этого в операторе  import  вместо  определенного 

имени класса следует указать символ *: 



 import  <имя  пакета>.* 



Для знатоков языка C++        и основ объектно-ориентированного программирования 

отметим, что import не является аналогом директивы include языка С, поскольку 

никакие файлы во время импорта не подключаются. Реализация J2ME, которую 

мы рассматриваем, включает   в себя следующие   пакеты, классы  которых  мы бу- 

дем рассматривать подробно на протяжении всей книги: 

 •   Java. i о — классы работы с потоками ввода и вывода данных; 

 •   Java. 1 ang — классы типов данных языка J2ME, а также некоторые системные 

    классы; 

 •   Java ,'utii   — классы дополнительных утилит, реализующие структуры  данных, 

    а также работу  со временем; 

 •   javax,microedition.io    — классы, обеспечивающие сетевые соединения; 

 •   javax.microedition.Icdui     — классы  пользовательского   интерфейса профайла 

    MIDP; 

 •   javax.microedition.midlet     — содержит единственный класс MIDIet, являющий- 

    ся  основным классом мобильного приложения; 

 •   javax. mi croedi ti on.  rms —  классы поддержки долговременного хранения данных. 

 Итак, первой строчкой нашей программы  мы импортируем   основной класс мо- 

 бильного приложения: 

 import javax.microedition.mi diet.MIDI et : 

 Далее реализуем свой собственный класс MyFi rstMi dl et, расширяющий класс MIDI et. 

 Как   уже   говорилось, обязательны  к реализации три абстрактных   метода  класса 


----------------------- Page 28-----------------------

                                                        Компиляция и запуск  мидлета              2 9 



MI Diet. Они могут быть  и пустыми, но присутствовать   в нашем классе обязаны. 

В  методе   startAppO       получим   атрибут       имени приложения  с помощью   метода 

getAppProperty(String   key) класса MIDI et и отобразим его области системных сооб- 

щений вместе с сакраментальным «Hello, World!». 

Область системных сообщений доступна нам с помощью поля out системного клас- 

са System. Поле out является потоком вывода системной информации и поддержи- 

вает вывод данных всех примитивных типов, а также строковых данных в область 

системных сообщений с помощью методов  print и println, которые различаются 

лишь форматом вывода. Кроме этого, класс System содержит  поле err, являюще- 

еся   потоком для вывода сообщений об ошибках, а также несколько методов для 

доступа  к системной информации. Как оформляются комментарии, вы без труда 

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

следующим образом: 



 import  javax.microedition.midlet.MIDlet: 



public   class   MyFirstMidlet   extends   MIDlet 



 { 

     public   void   pauseAppO { 



     } 



     public void destroyApp(boolean unconditional ) { 

          // уведомить систему о завершении 



          notifyDestroyedO; 



     } 



     public   void startAppO { 

          //  получить строку атрибута    имени мидлета 

          String name - getAppPropertyC'MIDlet-Name") ; 

          //  вывести  сообщение в системную область 

          System.out.printlnC'MIDlet     "+ name + " says : Hello . World!") : 



 Обратим внимание на то, что мы не импортировали классы String  и System из па- 

 кета Java. I ang, но без особых проблем используем их в нашей программе. Все вер- 

 но.  Дело в том, что пакет java. 1 ang просматривается компилятором всегда и в им- 

 порте не нуждается. 



 Компиляция и запуск мидлета 



 После того как весь код нашего первого приложения написан, сохраним получив- 

 шийся файл в папке исходных файлов приложения /src с именем   MyfirstMidlet.java. 

 Заметим, что имя файла должно полностью совпадать с именем основного класса 

 приложения, иначе компилятор выдаст следующую   ошибку: Class MyFirstMidlet is 

 public, should be declared in a file named   MyFirstMidlet.java. 


----------------------- Page 29-----------------------

30             Глава   2 . Hello, World! 



Половина дела сделана, хотя  пока что написанная программа является не более, 

чем простым текстом, который теперь нужно преобразовать в настоящее мобиль- 

ное   приложение. Этим вопросом занимается компилятор — специальная програм- 

ма, переводящая написанный нами текст в машинные команды, понимаемые уст- 

ройством. 

Для того чтобы откомпилировать наше приложение, следует выбрать команду Build 

(Компилировать) на панели управления главного окна KToolbar. В основном окне 

утилиты KToolbar мы увидим результат компиляции — количество ошибок в про- 

грамме, местонахождение  и описание каждой из них. Если все в порядке, то ото- 

бразится сообщение о том, что компиляция прошла успешно (рис. 2.4). 



              Щ 32МЕ Wireless Toolkit - MyFirstMWIet 



             hi     Mi l   fi..) '  '  ii . , . 



                                                                                                      Clear   Console 



               Device   JDeiaultOrayPhone 



             Project     settings     saved 

             Building "MyFirstHidlet" 

             Build complete 



                         Рис.   2.4 . Сообщение об успешном завершении  компиляции 



 На   этом этапе в папке нашего проекта было создано еще несколько директорий, 

 в   одну   из которых, /classes, и было помещено откомпилированное приложение, 

 представленное файлом MyFirstMidlet.class. 

 Сгенерированный компилятором файл уже  является программой, готовой к ис- 

 полнению на эмуляторе  телефона. Чтобы запустить   написанное нами приложе- 

 ние, в списке Device (Аппарат) панели управления утилиты KToolbar нужно выб- 

 рать один из стандартных или установленных нами эмуляторов. Эмулятор вместе 

 с созданным нами приложением запускается с помощью команды Run (Запустить) 

 панели управления. По умолчанию приложение запустится в эмуляторе с много- 

 обещающим названием   «DefaultGrayPhone». 

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

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

 то  мы увидим   желаемый   результат в области   системных   сообщений   утилиты 

 KToolbar (рис. 2.5). 



            B j Z M L   Wirrlrs»  loulkit - MyFirstMidiet  IBIfca.            :  .  •:  .  • - : - . :  i - i  : : : :  "  ^ :  • • • • • • • • • • • • . • • • . • . • . • . . . : : : : • • :  • •  . . . . . . . J.Ql.xI 



                •v New Project   „ | f e cper, Project. , j ^Settuv.. . 



                           ,  ..!  ,-,M     ,                                                              -  • • • ; • . . • ; - ; ; . : 



             HIDlet   MyrirstMidlet  says:          Hello ,  World !                                                 1 



                                          ^  L—  - . . • . -  ».,..•...•. 



                              Рис.   2.5 . Результат работы программы «Hello World!» 


----------------------- Page 30-----------------------

                                                          Упаковка  мидлета        3 1 



После закрытия эмулятора в область.системных  сообщений выводится разнооб- 

разная  статистика.  Оставим   пока ее  в покое, так  как пользы  сейчас  нам от  нее 

никакой. 



Упаковка мидлета 



Заключительным этапом создания полноценного мобильного приложения, гото- 

вого для  загрузки  в телефон, является упаковка созданных  файлов в jar-архив, 

который уже  пригоден для загрузки в мобильный телефон. Для упаковки прило- 

жения следует выбрать в меню Project (Проект) утилиты KToolbar команду Create 

Package (Создать архив). 

После упаковки в папке/bin нашего приложения появился файл             MyfirstMidlet.jar. 

Это и есть   нужный  нам jar-архив.   Современные архиваторы   типа WinZIP   или 

WinRAI^   поддерживают  этот формат и позволяют нам заглянуть внутрь сформи- 

рованного  архива.   Ничего  нового там   мы  не увидим,  только  все тот   же  файл 

MyFirstMidlet.class   и файл манифеста в папке META-INF. В таком виде приложение 

уже  готово  к загрузке  в телефон с помощью средств, рассмотренных  в   предыду- 

щей главе. Особого смысла загружать   наше приложение в телефон нет, посколь- 

ку никакой реакции аппарата мы не увидим, так что отложите  эту процедуру   до 

следующей   главы. 



На этом процесс создания мобильного приложения завершен. Мы рассмотрели 

все этапы, от написания текста программы до тестирования приложения на эму- 

ляторе, упаковки и загрузки  в телефон. Все эти этапы, кроме написания текста, 

мы проделали с помощью среды разработки J2ME Wireless Toolkit. 

Обратим внимание, что эмулятор, конечно, необходим в разработке приложений, 

но далеко   не достаточен.  Как показывает   практика, реальные   аппараты   могут 

в любой  момент   «подложить   нам свинью»   в том месте, где  ее совсем  не ждешь. 

Поэтому нужно как следует протестировать все функции программы на реальном 

телефоне. 



                                         * * * 



Итак, если у вас все получилось, то самое трудное уже позади. В программирова- 

нии, как и в любом деле, главное — начать, а дальше будет только легче. Не реко- 

мендую  продолжать   чтение книги, не выполнив первого задания, хотя  это ваше 

личное дело. В дальнейшем  вас ждет еще масса  интересных примеров с их под- 

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


----------------------- Page 31-----------------------

Глава 3 



Пользовательский 

интерфейс высокого уровня 



После того как мы познакомились с основной структурой   мидлета, будем посте- 

пенно продвигаться      в наших  познаниях. Java  — язык   объектно-ориентирован- 

ный, поэтому, изучив все его объекты, мы сможем конструировать  из них, как из 

кубиков, большие многофункциональные приложения. Первым делом разберем- 

ся с тем, как выводить  информацию на экран телефона, чтобы   демонстрировать 

результат работы наших приложений. Выводимая информация может быть раз- 

нообразной: текст, картинки, средства  общения (интеракции) с   пользователем. 

Все объекты, которые можно отобразить на экране, наследуются   из общего клас- 

са, название которого говорит само за себя — javax. mi croedi ti on. 1 cdui. Di spl ayabl e. 

Дальнейшая   эволюция  классов, наследуемых         из класса  Displayable,   идет   в двух 

направлениях: 

 1.  Класс Canvas предоставляет   пользователю   интерфейс низкого уровня, органи- 

   зует непосредственный доступ к прорисовке экрана. Подробнее на этом классе 

   мы остановимся в следующей   главе. 

2.   Класс Screen  является  родителем   для  классов  пользовательского   интерфейса 

    высокого уровня. Это четыре класса: Alert, Form, Li st, TextBox, объекты которых 

   являются стандартными  средствами  связи с пользователем.  Внешний вид та- 

    ких объектов зависит от конкретной модели телефона. Свобода действий  над 

    этими объектами, изменение их вида, свойств и поведения ограничены функ- 

    циями реализации J2ME. Иерархию отображаемых   объектов можно предста- 

    вить следующей   схемой  (рис. 3.1). 



                      Alert       Form        List      TextBox 



                      Рис. 3 . 1 . Иерархия отображаемых объектов J2ME 


----------------------- Page 32-----------------------

                                                                  Класс Form        3 3 



 Класс Form 



В этой главе мы рассмотрим работу с формой (объектом класса Form) на примере 

программы фотоальбома. Форма является определенного рода контейнером, ко- 

торый  может  содержать   различные  визуальные  элементы, такие как текстовые 

строки, поля символьного ввода, картинки, меню выбора, шкалы. 

С точки зрения объектов, элементы, которые можно добавить  в форму, должны 

быть  порождены   одним из следующих   классов: String,   Image или   Item. Элемент 

класса Stri ng представляет   собой статическую   строку,  автоматически  отформа- 

тированную согласно размерам экрана конкретного аппарата. Элемент класса Image 

представляет собой картинку. Фактически, оба этих элемента дублируются   объек- 

тами классов, порожденных из класса Item. 

Класс   Item является базовым для  целой группы элементов, которые могут быть 

добавлены в форму: ChoiceGroup (меню выбора), DateField (поле ввода даты и вре- 

мени), Gauge (шкала), Imageltem (изображение), Stringltem   (статическая строка), 

TextField (поле ввода). 

Внутренняя реализация формы скрыта от программиста, мы можем контролиро- 

вать лишь порядок отображения элементов. Расположение элементов на экране, 

навигация между элементами, вертикальная прокрутка экрана при необходимости 

контролируются внутренней реализацией и зависят от конкретной модели аппара- 

та. Работа с формой осуществляется с помощью следующих   методов класса Form: 

 •   Form(String   title)   — конструктор; создает пустую форму с заголовком  title ; 

 •   FormCString   title .  Item[]   items)  — конструктор; создает форму с заголовком 

    title , содержащую  элементы массива items; 

 •   i nt  append (Image  i mg) — добавляет объект изображения i mg в форму, возвраща- 

    ет индекс, присвоенный формой элементу; 

т    int   append(String   str)   — добавляет статическую строку str в форму, возвраща- 

    ет индекс, присвоенный формой элементу; 

 •   int  appenddtem   item)—добавляет   элемент item   в форму, возвращает индекс, 

    присвоенный  формой элементу.  Следует отметить, что  один экземпляр эле- 

    мента может быть добавлен лишь единожды и только в одну форму; 

 •   void  delete( int  itemNum) — удаляет из формы элемент с индексом itemNum. Ну- 

    мерация элементов начинается с нуля; 

 •   Item   get (int  itemNum) — возвращает элемент формы с индексом itemNum, сама 

    форма при этом остается неизменной; 

 •   void   insertdnt  itemNum.   Item   item) — вставляет в форму элемент item в пози- 

    цию, заданную переменной itemNum; 

 •   void set (int   itemNum. Item item) —заменяет в форме элемент с индексом itemNum 

    на элемент item; 

 •   void setltemStateListenerdtemStateListener    i Listener)  — задает блок прослуши- 

    вания элемента 1 Li stener. Поскольку реализация элементов скрыта от нас, блок 

    прослушивания элемента — это единственное средство контроля непосредствен- 

    но во время ввода информации. Как только данные элемента изменяются пользо- 

    вателем, который выбирает новый пункт меню или вводит очередной символ, 


----------------------- Page 33-----------------------

34         Глава   3 . Пользовательский  интерфейс высокого уровня 



   автоматически вызывается функция блока прослушивания itemStateChangecK Item 

    item). Следует отметить, что функция вызовется только при изменении элемен- 

   та пользователем.  Если изменение происходит  программно, то вызова не про- 

   изойдет; 

•    int  size()  — возвращает количество элементов в форме. 

Все объекты иерархии класса Screen, в том числе и форма, обладают  необязатель- 

ными заголовком и бегущей строкой. При создании формы в конструкторе мож- 

но   указать   заголовок   отображаемого   объекта,  а можно оставить  заголовок   пус- 

тым, передав   аргумент   null.  В процессе  работы   с формой  можно  получить   ее 

заголовок с помощью метода String   getTitle()   и установить новый заголовок, ис- 

пользуя метод void   setTitleCString     s). 

Бегущая строка представлена  объектом  класса Ticker. Текст бегущей   строки пе- 

редается в конструкторе объекту  класса. Поместить бегущую строку в отобража- 

емый объект   можно с помощью метода  void         setTickerdicker    ticker),  получить 

объект  бегущей   строки — используя   метод Ticker      getTickerO.  Программное ре- 

дактирование бегущей строки осуществляется  с помощью методов класса Ticker: 



•    void   setStringCString   str ) — изменить текст бегущей строки; 

•    String   getStringO  — получить текст бегущей строки. 



 Класс Display 



Остановимся на вопросе, каким образом рассмотренные объекты отображаются на 

экране. Этим процессом руководит менеджер дисплея — объект класса Di spl ay. Он 

создается   автоматически реализацией  MI DP       при запуске мидлета  и сохраняется 

до вызова функции destroyApp().   Самим такой объект создать  нельзя, зато можно 

получить ссылку на него с помощью метода getDi spl ay (MIDI et), который принимает 

в качестве аргумента ссылку на текущий мидлет. В языке Java, как и в C++, ссылка 

на текущий объект обозначается ключевым словом this. 

Когда мы получили  ссылку   на Display,  любой  объект,  наследованный из класса 

Di spl ayabl e, может быть выведен на экран с помощью метода setCurrent (Di spl ayable). 

Ввиду ограниченности ресурсов   мобильных  аппаратов одновременно на экране 

может быть отображен только один объект, перекрытие и наложение окон не под- 

держивается. Таким образом, любое приложение может быть рассмотрено как за- 

данная последовательность  отображаемых   объектов. 

Во время отображения формы через менеджер дисплея обновление экрана про- 

исходит  автоматически. Это значит, что при изменении содержания формы или 

ее элементов нет необходимости вызывать какие-либо дополнительные функции, 

так   как изменения сразу будут отображены на экране. Это относится ко всем объек- 

там, порожденным классом Screen. 



Отображаем картинку 



Приведенной информации вполне достаточно, чтобы  написать программу, ото- 

бражающую   картинку на экране телефона. Заметим, что не все картинки одина- 


----------------------- Page 34-----------------------

                                                                   Отображаем  картинку               3 5 



ково полезны. Во-первых,  изображение должно  быть  в формате  PNG (Portable 

Network   Graphics). Во-вторых,  размер   картинки не должен   превышать   размера 

дисплея вашего аппарата. Если для работы над мидлетом выбран WTK   (Wireless 

Toolkit),  то   после  создания   нового  проекта   картинка должна   быть   помещена 

в  папку ресурсов  приложения /res. 

Структуру мидлета мы рассмотрели в прошлой главе. Помним, что основной класс 

мидлета должен быть наследован из абстрактного класса MIDI et. He забываем так- 

же о его функциях, обязательных  к реализации. 

Для того чтобы вывести картинку на экран, нам потребуется несколько объектов, 

которые будут объявлены как члены класса. Предварительно импортируем необ- 

ходимые библиотеки: 



 import   javax.microedition.lcdui.Form ; 

 import   javax.microedition.lcdui.Image ; 

 import   javax.microedition.lcdui.Display ; 



 private   Form  form :         //  форма,  отображаемая на   экране 

 private   Image  image;        //  картинка  для  отображения 



 private   Display   display :  //  менеджер дисплея 



Далее в функции startApp()   реализуем следующие действия: 

 •    получим ссылку на менеджер дисплея; 

 •    создадим объект картинки; 

 •    создадим новую форму; 

 •    добавим картинку в форму; 

 •    отобразим форму на экране через менеджер дисплея. 

 Код   функции будет выглядеть следующим  образом: 

 public   void  startAppO   { 

     //   получить ссылку на менеджер дисплея 

     display = Display.getDisplay(this) : 

 try { 

     //   создать картинку    из файла  flower.png 

          image = Image.createlmage("/flower.png") ; 

      } 

     catch   (IOException    ioe ) { 

          //  обработать исключительную      ситуацию . 

          //  если файл не может быть открыт 

          System.out.pri ntln(i oe.getMessageC)) ; 

      } 

      //  создать новую форму 

      form = new FormC'SlideShow") ; 

      //  добавить картинку в форму 

      form.append(image); 

      //  вывести форму на экран 

      display.setCurrent(form) : 


----------------------- Page 35-----------------------

36        Глава  3 . Пользовательский интерфейс высокого уровня 



                     ^ (п| х|1   Компилируем приложение, как это было рассмотрено 

                              ранее, запускаем   на стандартном   эмуляторе   из   WTK 

                              и получаем вот такой результат (рис. 3.2). 

                              Все это просто замечательно, но ничего нового мы пока 

                              что не изобрели. С таким же успехом можно было про- 

                              сто поместить картинку в галерею, если таковая име- 

                              ется. Мы пойдем дальше, задействуем   клавиши теле- 

                              фона, чтобы   организовать   смену   картинок  в нашем 

                              фотоальбоме. 



                              Класс Command 



                              Итак,   сейчас   мы   работаем    с   формой, относящейся 

                              к  иерархии  класса  Displayable,   который является ба- 

                              зовым для всех отображаемых   объектов. Кроме стан- 

                              дартных визуальных средств интеракции, объекты, по- 

                              рожденные   этим   классом,  поддерживают          обработку 

                              событий, возникающих в результате действий пользо- 

                              вателя. К отображаемому объекту, в том числе и к фор- 

                              ме, могут быть привязаны различные команды, вызов 

                              которых приведет к некоторым действиям. 



                              Команды  представлены   объектами   класса  Command из 

                              пакета    javax.microedition.lcdui.      Создается    коман- 

   Рис. 3.2 . Вывод картинки 

      на экран телефона       да  с помощью конструктора: Command (String          label, int 

                              commandType.int    priority),где: 

 •   1 abel   — название команды для отображения ее на экране; 

 •   commandType — тип команды; 

 •   priority   — приоритет команды. 

Поскольку команды относятся к пользовательскому  интерфейсу высокого уров- 

ня, то при создании команды мы не можем заранее связать ее с какой-то опреде- 

ленной клавишей телефона. Порядок вызова команды определит за нас реализа- 

ция   MIDP, мы лишь можем  высказать свои пожелания с помощью  параметров 

типа команды и ее приоритета. 

Тип команды задает   внутреннее  представление  намеченного использования ко- 

манды. MIDP предусматривает  следующие типы команд, определяемые констан- 

тами класса Command: 

•    BACK — возврат к логически предыдущему  экрану; 

 •   CANCEL — выбор отрицательного ответа в диалоге, реализуемом текущим экра- 

    ном; 

 •   EXIT — выход из приложения; 

 •   HELP — вызов помощи или подсказки; 

 •    ITEM — команда имеет отношение к определенному элементу экрана; 


----------------------- Page 36-----------------------

                                                                    Класс Command            3 7 



•    OK — выбор положительного ответа в диалоге, реализуемом текущим экраном; 

•    SCREEN — команда имеет отношение к отображаемому в настоящее время экрану; 

м    STOP — остановка выполняемой в настоящее время операции. 

Заметим, что константы задают   лишь тип команды. Они не предписывают ни- 

каких действий. Смена экрана, выход из приложения и прочие действия не вы- 

полняются автоматически, они должны  быть  реализованы в   соответствующем 

месте. Тип лишь  ориентирует   реализацию   MIDP, как разместить   команды на 

экране и организовать их вызов в соответствии с общим стилем меню и функци- 

ональными клавишами конкретной модели телефона. 

Приоритет задает преимущество перед другими командами. Это влияет на поря- 

док   отображения команд при объединении их в группу.' Приоритет задается це- 

лым числом. Наименьшее число означает наивысший приоритет. 

Такой вариант значительно ограничивает свободу наших действий, но решает мас- 

су проблем, связанных с транспортабельностью  приложений. Теперь мы можем 

быть уверены, что на любом аппарате с поддержкой J2ME команды будут предло- 

жены пользователю   корректно, с учетом общей политики отображения  и стиля 

команд конкретного аппарата. 

После создания объекта команды необходимо добавить его в форму, которую мы 

собираемся отображать на экране. Методы добавления и удаления команд реали- 

зует класс Displayable,  то есть команды могут быть добавлены к любому   отобра- 

жаемому   объекту: 

 •    void addCommand(Command and) — добавляет команду and к отображаемому  объек- 

    ту.  Будет  ли команда привязана к функциональной клавише или объединена 

    с другими командами в меню, зависит от реализации MIDP конкретного аппа- 

    рата. Команда может быть добавлена в объект не более одного раза, но может 

    быть включена одновременно в несколько объектов; 



 •    void removeCommand(Command and) — удаляет команду and из отображаемого объекта. 

Для примера проведем эксперимент на эмуляторе Nokia. Добавим в форму четы- 

ре однотипных команды с разным приоритетом: 

Command cl = new CommandC'Commandl" . Command.OK . 4 ) : 

 form.addCommand(cl): 

Command c2 = new Command("Command2" , Command.OK . 3 ) ; 

 form.addCommand(c2) : 

 Command c3 = new CommandC"Command3" . Command.OK . 2 ) ; 

 form.addCommand(c3); 

 Command c4 = new Command("Command4" . Command.OK . 1) ; 

 form.addCommand(c4): 

 display.setCurrent(form); 

 В результате все команды объединились в меню Options, привязанное к левой функ- 

 циональной клавише телефона, при нажатии на которую появился список наших 

 команд.  Это  аппаратно реализованный список, позволяющий  выбрать   одну   из 

 команд  клавишей  Select   или  вернуться   к  предыдущему           экрану  клавишей Back 

 (рис.  3.3). 


----------------------- Page 37-----------------------

38         Глава  3 . Пользовательский интерфейс высокого уровня 



 * ( 1 )  Nokia 721USDX                 Заметьте,  что  в соответствии   с заданными  при- 

     Tools  Help                        оритетами   последняя  добавленная   команда  на- 

                                        ходится   первой в списке. Использование интер- 

                                        фейса  высокого  уровня   приводит   к тому, что  та 

                                        же  самая   программа,  запущенная   на другом ап- 

                                        парате, будет выглядеть   совершенно иначе. 



                                         Интерфейс 

          СопишппЗ                      CommandListener 

          Command2 

          Command]                      Как    уже   было   замечено,   сам    объект   команды 



                                        не   предписывает   программе   выполнение  каких- 

                                        либо действий, он является лишь связующим зве- 

                                        ном    между    исполнителем   команды   и   формой. 

                                        Приемником и исполнителем команды может яв- 

                                        ляться   любой   объект,   реализующий        интерфейс 

                                        CommandListener, который  имеет   всего лишь  один 

                                        метод:  void   commandAction(Command      с.  Displayable 

                                        d).  Этот метод  нам нужно  написать самим и реа- 

                                        лизовать   там   связанные  с   командами   действия. 

                                        Метод corrmandAction вызовется автоматически при 

                                        выборе пользователем   одной из команд, которая 

                                        и   будет передана  в метод  в качестве     аргумента. 



                                        В   нашем   случае   удобно    сделать   так,  чтобы   ос- 

                                        новной класс мидлета содержал   блок  прослуши- 

                                        вания команд в себе самом. В общем случае блок 

         Рис.  3.3 . Аппаратно          может  содержаться   в любом  объекте. Таким об- 

            реализованный               разом, основной класс мидлета  будет расширять 

            список команд               класс  MIDIet и одновременно реализовывать  ин- 

                                        терфейс  CommandListener: 



public class SlideShow extends MIDlet implements CommandListener 



Приемник   команд,  как  и сами   команды, должен   быть   привязан  к объекту               типа 

Displayable,   в   нашем   случае   это   форма.   В   каждый    момент    времени    отобража- 

емый   объект     может    иметь    только   один   приемник .  Связь   приемника   команд 

с отображаемым   объектом  осуществляется   с помощью метода  класса Di spl ayabl e: 

void   setCommandListener(CommandListener        I) . В нашем случае, когда  основной класс 

мидлета   реализует      блок   прослушивания   команд,  в   качестве   аргумента         переда- 

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

опять  воспользуемся   ключевым   словом   this . 



Теперь у   нас есть  все необходимые   средства  для   реализации  полноценного фото- 

альбома.  Подготовим  картинки для  фотоальбома, учитывая   размер экрана и мак- 

симальный объем  приложения вашей модели телефона. Назовем картинки, кото- 

рые мы хотим демонстрировать,  так: l.png, 2.png и т. д. — и разместим  их в папке / 

res.   Заодно  добавим   два   члена   класса    SlideShow:  int   slideNum   и   int  maxSlideNum, 


----------------------- Page 38-----------------------

                                                            Интерфейс CommandListener                   3 9 



которые будут содержать текущий номер картинки и общее количество картинок 

соответственно. 

Далее  реализуем   обработку   команд и добавим логику  для смены картинок на эк- 

ране при нажатии клавиш. В конечном итоге программа полноценного фотоаль- 

бома будет выглядеть   следующим   образом: 



import    javax.microedition.midlet.MIDlet : 

import    javax.microedition.lcdui.Display ; 

import    javax.microedition.lcdui.Form : 

import    javax.microedition.lcdui.Command ; 

import    javax.microedition.lcdui.CommandListener ; 

import    javax.microedition.lcdui.Displayable : 

import    javax.microedition.lcdui.Image ; 

import    javax.microedition.lcdui.Ticker ; 

import    java.io.IOException : 

public class     SlideShow extends    MIDlet  implements   CommandListener 

 { 

     private Display display ;         // менеджер   дисплея 

     private Form form ;               // отображаемая   форма 

     private Command     next ;        // команда   перехода к следующей    картинке 

     private Command     back ;        // команда   перехода к предыдущей     картинке 

     private       Image image ;       // объект   картинки 

     private int slideNunri :          // номер текущей    картинки 

     private    int maxSlideNum=5 ;    // общее количество    картинок 



     public void    startAppO 

      {    // получить ссылку    на менеджер   дисплея 

          display - Display.getDisplay(this) : 

          //  создать   новую форму   без  заголовка 

          form = new    Form(null) ; 

          // установить    приемник   команд  для формы 

          form.setCommandListener(this) : 

          //  создать   команду  перехода к следующей     картинке 

          next = new Command("Next" . Command.OK , 1 ) ; 

          //  добавить   команду в форму 

          form.addCommand С next) : 

          //  создать   команду  возврата к предыдущей     картинке 

          back • new CommandC'Back" ,      Command.BACK . 1) : 

          //  добавить   команду в форму 

          form.addCommand(back) : 

          //  создать   объект бегущей   строки 

          Ticker   t  •   new TickerC'My   Photoalbum"): 



          //   добавить  бегущую строку    в форму 



          form.setTicker(t) ; 

          //   добавить в форму    первую  картинку 

          setlmageC'/l.png") : 


----------------------- Page 39-----------------------

4 0            Глава   3 . Пользовательский интерфейс высокого уровня 



           //  отобразить форму  на   экране 



           di splay.setCurrent(form) ; 



      } 

      // метод    обработки     команд 

      // реализует      действия , предписанные       командами 

     public     void commandAction(Command c . Displayable d ) { 

           // команда     перехода к следующей        картинке . 

           // если    текущая    картинка не последняя 

           if (с == next && slideNum<maxSlideNum ) 

                // увеличить      номер текущей      картинки 

                slideNum++: 

           // команда      возврата к предыдущей       картинке . 

           // если     текущая   картинка не первая 

           if (с —      back & & slideNum>l ) 

                 // увеличить     номер текущей      картинки 

                 slideNum--; 

           // удалить из формы        текущую    картинку 

           form.delete(O); 

           // получить имя файла         картинки из ее номера 

           // и добавить       полученную    картинку в форму 

           setImage("/"+Integer.toString(s1ideNum)+".png") : 



      //   функция   setlmage принимает имя файла   картинки, 



      //   создает   объект   картинки     и   добавляет   в форму 



      public    void   setImage(String path) 



      { 

           t r y  { 



                 // создать     картинку из файла , переданного в аргументе 

                 image = Image.createlmage(path) ; 

            } 

            // обработать      исключительную      ситуацию , 

            // если файл не может быть открыт 

           catch    (IOException      ioe ) { 

                 System.out.pri ntln(i oe.getMessage()) : 

            } 

            // добавить     картинку в форму 

           form.append(image): 



 Отметим, что для смены изображения на экране мы лишь помещаем новую кар- 

 тинку в отображаемую   на экране форму, после чего картинка сразу появляется на 

 экране (рис. 3.4). Заметим, что в данном эмуляторе  изображение не поместилось 

 на    экране, поэтому   реализация   организовала   вертикальную   прокрутку   экрана, 


----------------------- Page 40-----------------------

                                                Интерфейс CommandListener        4 1 



обозначенную  стрелочкой  вниз. Горизонтальная про- 

крутка экрана в реализациях, как правило, отсутствует. 

Поздравляю!  Если   вы   не поленились  оторваться   от 

книжки и реализовать вышеописанное, то загрузите по- 

лучившийся мидлет в телефон, и вы будете всегда иметь 

под рукой маленький фотоальбом. Можно пойти даль- 

ше и добавить нехитрую логику перехода к первой кар- 

тинке после демонстрации последней и наоборот. 

К сожалению, стандартная   реализация J2ME   не под- 

держивает доступ к внутренним параметрам телефона, 

к   записной книжке или экранной заставке,  поэтому, 

в   общем случае,  установить   отображаемую   картинку 

в качестве фона-заставки телефона программно мы не 

можем. Но не стоит отчаиваться, потому что подобная 

функция может быть реализована производителем ап- 

парата в специализированном пакете функций API. На 

некоторых   таких   пакетах   подробнее  мы остановимся 

в одной из заключительных   глав. 

С картинками нужно быть аккуратными, ведь многие 

модели  телефонов  накладывают   серьезные ограниче- 

ния   на размер  приложения, а картинки в этом  плане 

очень ресурсоемки.  Здесь  придется  искать оптималь- 

ное соотношение параметров цена и качество: платим 

в данном случае мы объемом памяти, занимаемым кар- 

тинкой.  Именно поэтому   оптимизацию мидлетов  на-         Рис. 3.4 . Окончательный 

                                                                вид фотоальбома 

чинают с оптимизации картинок, различными средства- 

ми стараясь уменьшить занимаемый ими объем памяти. 

На сегодняшний день существует масса различных средств для обработки карти- 

нок, от классического PhotoShop до специализированных ImageOptimizer, кото- 

рые порой дают лучшие результаты   в плане оптимизации, чем монстры дизайна. 

Часто производители телефонов предлагают   свои средства  для редактирования 

 и оптимизации изображений. Например, Nokia  PC Suite содержит  свой замеча- 

тельный Nokia Image  Converter. 



 Итак, основа проектирования любого приложения — создание структуры демон- 

 стрирования различных  отображаемых   объектов  и реализация логики перехо- 

 дов между этими объектами. Логику  переходов  можно организовать с помощью 

 пользовательского интерфейса высокого уровня, который мы рассмотрели в этой 

 главе. Для этого нужно спроектировать отображаемые объекты, добавить к ним 

 требуемые команды и реализовать интерфейс прослушивания и исполнения ко- 

 манд.  Приложения, организованные таким   образом,  более   транспортабельны 

 и могут быть   перенесены  на различные  модели  телефонов. Пользовательский 

 интерфейс низкого уровня мы рассмотрим в следующей   главе. 


----------------------- Page 41-----------------------

 Глава 4 



 Работа с графикой 



В прошлой главе мы рассмотрели пример работы с пользовательским интерфейсом 

высокого   уровня,   который   представлен    иерархией   класса   Screen.   При   работе 

с объектами такого типа мы не имели прямого доступа к экрану аппарата, а пользова- 

лись формой как визуальной абстракцией высокого уровня. Изменения изображе- 

ния   на экране осуществлялись   с помощью методов  формы и добавленных   в нее 

объектов. То есть, работая   с высокоуровневым   интерфейсом, при всем желании 

нарисовать поперек экрана, например, жирную синюю полосу мы не сможем. 

Решить проблему   непосредственного доступа  к экрану нам поможет низкоуров- 

невый пользовательский интерфейс, который представлен второй веткой иерар- 

хии отображаемых   объектов типа Displayable — классом Canvas. 



 Класс Canvas 



Класс Canvas является абстрактным. Это значит, что мы не можем просто создать 

объект этого класса. Если все же попытаться создать такой объект, то компилятор 

подтвердит вышесказанное вот такими словами: 



javax.microedition . lcdui .Canvas is abstract;  cannot be instantiated 

Canvas с = newCanvasO: 



 В объектно-ориентированном программировании абстрактными называются та- 

кие классы, которые содержат в себе хотя бы один абстрактный метод. Использо- 

вать абстрактные классы можно только как основу для новых классов, в которых 

обязательна реализация абстрактного метода. 

В классе Canvas абстрактным является метод abstract   void paintCGraphics g), кото- 

рый отвечает за перерисовку экрана и получает в качестве аргумента объект клас- 

са Graphics, который создается автоматически при инициализации объекта Canvas. 



 Класс Graphics 



 Собственно класс Graphics и предоставляет   возможности низкоуровневого  гра- 

фического рисования. Возможности  класса Graphics далеко   не безграничны: он 

содержит методы для рисования и заливки базовых геометрических фигур, таких 

как линии, прямоугольники и дуги. Также поддерживаются возможности отобра- 

жения текстовых символов и смены цвета для рисования. 


----------------------- Page 42-----------------------

                                                                       Класс Graphics          4 3 



Теперь все в наших руках, включая и все точки экрана, каждая из которых, кста- 

ти, имеет свои координаты по осям хиу.            Начало координат (точка 0,0)  находится 

в левом верхнем углу экрана. То есть нужно учесть, что ось у направлена вниз. 

А теперь  перейдем  к рисаванию и рассмотрим   методы   класса Graphics, которые 

могут нам понадобиться. В первую очередь заметим, что размеры экрана у разных 

моделей могут серьезно различаться, поэтому сразу будем писать транспортабель- 

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

экрана конкретного аппарата. Делается это с помощью двух методов: 

•     int getClipHeightO   — возвращает  высоту текущей области для рисования; 

•     int getCl ipWidth()   — возвращает ширину текущей области для рисования. 

Сам  класс Canvas  также  содержит   методы   для   определения  размеров   отобража- 

емой  области   экрана, которые  не зависят   от   текущей   области  для   рисования 

и остаются неизменными во время работы приложения: 

•     i nt getHei ght () — возвращает высоту отображаемой области экрана; 

•     int getWidthC)   — возвращает ширину отображаемой области экрана. 

Теперь, получив  ширину и высоту   области для рисования, мы можем использо- 

вать все точки с соответствующими  координатами. По умолчанию текущей обла- 

стью для рисования является весь экран, но и это можно изменить, использовав 

метод SetC lip: 

•    void setClip( int x.   inty .   int width,   int height)  — устанавливает   новую прямо- 

    угольную   область   рисования с левым  верхним  углом   в точке  (х, у),   высотой 

    height и шириной width пикселов. 

Иногда удобно сместить начало координат из левого верхнего угла в более  удоб- 

ное место. Делается   это с помощью метода  void trans late (int   x.   int у), который 

переносит   начало  координат  в точку   с координатами  (х, у).            Получить   текущее 

положение  начала  координат  относительно левого  верхнего   угла  можно следу- 

ющими методами: int getTranslateXO             и int  getTranslateYO. 

После того как мы определились с областью для рисования и началом координат, 

рассмотрим несколько методов для отображения на экране базовых   геометриче- 

ских фигур: 

 •    void drawLinednt  xl,   intyl ,   intx2 .   inty2)   — рисует линию из точки с коорди- 

    натами (Xj, г/,) в точку с координатами (х. , у ).       В классе Graphics нет метода для 

                                                      2  2 



    рисования одной точки, поэтому отдельная точка рисуется с помощью линии, 

    начало и конец которой заданы одними и теми же координатами; 

 •    void drawRect( int x.  inty ,   int width,   int  height)  — рисует   прямоугольник с ле- 

    вым верхним углом  в точке (х, у), шириной width и высотой height пикселов; 

 •    voiddrawRoundRect(intx.   inty.  intwidth,  intheight,  intarcWidth,  intarcHeight) — 

    рисует   прямоугольник   с закругленными   углами.   Первые четыре   аргумента 

    аналогичны аргументам  предыдущего  метода, acrWidth   задает диаметр закруг- 

    ляющей дуги вокруг  оси х, arcHeight задает  диаметр закругляющей  дуги вок- 

    руг оси у; 

 •    void drawArc( int  x,   inty ,  intwidth.   intheight,   int   startAngle.  int  arcAngle)— 

    рисует   дугу, вписанную   в  прямоугольник   с  левым   верхним   углом   в   точке 

    с координатами (х,у).        Параметр startAngle задает угол, с которого будет начи- 


----------------------- Page 43-----------------------

 4 4        Глава 4 . Работа с графикой 



    наться дуга, а параметр arcAngle задает  угловое   расстояние, на которое будет 

    простираться дуга. Углы  задаются  в градусах. Таким образом, чтобы нарисо- 

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

 Линии, прямоугольники и дуги могут быть нарисованы двумя  способами штри- 

 хования — сплошной и пунктирной линиями. Стиль штрихования задается с по- 

 мощью метода void setStrokeStyleCint   style), в качестве аргумента style передает- 

 ся  одна  из констант класса  Graphics: SOLIO — для   рисования сплошных линий, 

 DOTTED — для рисования пунктирных линий. Получить текущий стиль штрихова- 

 ния   позволяет метод int   getStrokeStyle(). 

 До сих пор, по умолчанию, мы рисовали черным по белому, но в настоящее время 

 аппаратов с цветными дисплеями, которые поддерживают технологию J2ME, го- 

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

 свою фантазию и раскрасим экран нашего телефона веселыми красками. 

 Для начала необходимо узнать, цветной ли дисплей у телефона, который достал- 

' ся в распоряжение нашей программе. Эту информацию можно извлечь из менед- 

 жера дисплея, используя  метод boolean  isColorC), который вернет значение true, 

 если аппарат поддерживает   работу  с цветом. Количество доступных   цветов или, 

 в некоторых моделях, количество градаций одного серого цвета можно получить 

 с помощью метода  int numColorsO. 

 В отличие от других языков программирования, в J2ME константы для определе- 

 ния   цвета не заданы, поэтому  придется  нам самим, как настоящим  художникам, 

 наносить на палитру три цветовых составляющих: красный, зеленый и синий (мо- 

 дель образования цвета RGB). Доля каждого из этих цветов задается числом от 0 до 

 255. Например, комбинация (0,0,255) задает синий цвет, а (0,0,0) — черный. Наши 

 краски не кончаются, поэтому экспериментировать мы можем сколько угодно. Цвет 

 рисования устанавливается с помощью двух методов класса Graphics: 

 •    void setColorCint   red,   int green,   int  blue)  — задает   цвет рисования, заданный 

     цветовыми составляющими red, green, Ы ue; 

 •    void  setColorCint   RGB) — устанавливает   цвет, заданный цветовыми  составля- 

     ющими, объединенными в одно число RGB, где каждые два байта  представляют 

     свою составляющую.  Например, красному цвету  (255,0,0) будет соответство- 

     вать число RGB 0xFF0000. 

 Узнать текущий цвет рисования можно опять же двумя способами: либо полу- 

 чить   каждую     цветовую     компоненту   по  отдельности,   используя        методы    int 

 getRedComponentO, int   getGreenComponentO и  int   getBlueComponentO, либо   полу- 

 чить число RGB с помощью метода  int  getColorO. 

 Теперь   мы можем  рисовать   не только  черным, что, несомненно, скрасит  нашу 

 жизнь, но пока что только по белому. Изменить цвет фона или какой-то его части 

 можно с помощью методов рисования закрашенных фигур: 

 •    void fi  11 Rect(int x.  int у.  int width.  int height)   — рисует закрашенный прямо- 

     угольник; 

 •    voidfillRoundRect(intx,     inty,  1ntwidth,  intheight.  intarcWidth.  intarcHeight) — 

     рисует закрашенный прямоугольник с закругленными углами; 

 •    void fillArcCint   x,   inty .  int width,   intheight.   int   startAngle,  int   arcAngle) — 

     рисует закрашенный сектор. 


----------------------- Page 44-----------------------

                                                                                 Класс Graph            4 5 



Все  аргументы   методов   рисования закрашенных  фигур   идентичны   аргументам 

аналогичных   методов  рисования простых  фигур.  Фигуры  закрашиваются   теку- 

щим     цветом, и если мы хотим очистить экран, то нужно установить  белый  теку- 

щим     цветом и нарисовать закрашенный прямоугольник размером на весь экран. 



 Класс Graph 



Самое время   остановиться   и попробовать   использовать   полученные  знания на 

практике. В качестве  примера нарисуем график параболы. Красная парабола на 

желтом фоне — разве это не прекрасно?! 

Представим наш график новым классом Graph, который расширит класс низкоуров- 

него интерфейса Canvas и реализует его абстрактный метод paint. В методе pai nt мы 

зальем экран желтым фоном, нарисуем рамку и оси координат, сместим начало ко- 

ординат в центр экрана, а затем в цикле вычислим координаты для каждой точки 

параболы по формуле. Наш класс будет выглядеть следующим образом: 

import    javax.microedition.midlet.MIDlet : 

import    javax.microedition.lcdui.Display ; 

import    javax.microedition.lcdui.Displayable ; 

import    javax.microedition.lcdui.Canvas ; 

 import   javax.microedition.lcdui.Graphics ; 



public   class   Graph  extends Canvas ( 

     //  функция   прорисовки   экрана 

     public   void paint(Graphics     g ) { 

          //  получить ширину    экрана 

          int   width   -   g.getClipWidthO: 

          //  получить   высоту  экрана 

          int height = g.getClipHeightO : 

          // установить     текущий  цвет желтым 

          g.setColorC255.255.0) : 

          //   нарисовать   закрашенный   прямоугольник 

          //  размером   на весь экран 

          g.fillRect(0.0.width.height); 

          // установить     текущий  цвет черным 

          g.setColor(O.O.O) : 

          //   нарисовать   рамку 

          g.drawRect(O,O,width-l.height-l); 

          //   нарисовать   оси  координат 

          g.drawLine(width/2.0.width/2.height) : 

          д.drawLine(0,height/2.width.height/2); 

          //   сместить  начало   координат в центр    экрана 

          g.translate(width/2.height/2); 

          // установить     текущий  цвет  красным 

          g.setColor(255.0.0); 


----------------------- Page 45-----------------------

46           Глава  4 . Работа с графикой 



          //  для  каждой   точки   по   оси  х 

          f o r d n t  x=-width/2 ;   x<width/2;  x++)  { 

              //   вычислить   значение  у  по формуле 



              int   у  =  -х*х/40; 



               // нарисовать    точку  параболы 

              g.drawline(x.y.x.y); 



Класс, отвечающий за рисование графика, готов. Осталось в стартовом методе при- 

ложения получить   ссылку  на менеджер дисплея и, как в случае с формой, вывес- 

ти отображаемый   объект на экран: 



public   void  startAppO    { 



     //  создать   объект   графика 



     Graph   graph   =   new   GraphO; 



     //  получить   ссылку  на  менеджер дисплея 

     display   = Display.getOisplay(this) ; 

     // вывести    график на экран 

     display.setCurrent(graph); 

 } 

                                   Компилируем, запускаем и наслаждаемся   полученным 

                                   результатом       (рис. 4.1). 

                                   Обратим   внимание  на  формулу,   по   которой   мы  вы- 

                                   считывали   координаты   точек   параболы:   -х*х/40. Те- 

                                   перь попытаемся вспомнить школьный курс   алгебры: 

                                   если   первый   коэффициент квадратного                 многочлена 

                                   отрицательный,  то  ветви   параболы   должны   быть  на- 

                                   правлены   вниз. Однако  на эмуляторе   мы   видим,  что 

                                   ветви   параболы        направлены   вверх.  Дело   в   том,  что 

                                   в  телефоне   ось у      направлена   не  вверх,  как  в  школь- 

                                   ных учебниках,  а в обратном направлении. 



                                   Вторая      проблема   в том, что, как  мы   видим,   область 

                                   для рисования, предоставленная нам аппаратом, мень- 

                                   ше,   чем реальные  размеры  экрана, а сверху пристро- 

                                   илась незваная полоска с названием основного клас- 

                                   са   мидлета.   Действительно,   зачастую   разработчики 

                                   оставляют  себе часть экрана для вспомогательной ин- 

                                   формации. 

                                   Полный  экран можно  получить   двумя  способами: ис- 

                                   пользовав функцию профайла MIDP2, обзор которого 

                                   останется за переплетом  этой книги, или  задействовав 

                                   специальную API-функцию производителя. На втором 

                                   способе  мы  остановимся   в   одной  из          заключительных 

Рис.   4.1. Парабола, созданная     глав, 

       средствами J2ME 


----------------------- Page 46-----------------------

                                                                        Рисование текста            4 7 



 Рисование текста 



Рисовать,   конечно, здорово,  но  иногда   на  картинке требуется   что-нибудь                  напи- 

сать, например GAME OVER. В прошлой главе, когда мы отображали   текст при помо- 

щи  интерфейса   высокого   уровня,  форматирование   строки   происходило                    автома- 

тически. Теперь  ответственность   за размещение  текста   на экране, выравнивание 

и перенос строк  целиком лежит   на плечах              программиста. 



Позиция      для   текста   задается    при   помощи   координат   точки   размещения           текста 

и   точки   привязки .  Отображаемая          строка   заключается      в   воображаемый       прямо- 

угольник, точка привязки которого будет помещена в заданные координаты. Точ- 

ка привязки определяется  комбинацией вертикальной и горизонтальной констант 

класса Graphics. 

 •    BASELINE — размещение  нижней границы текста  по координате у. 



•     ТОР — размещение верхней  границы текста  по координате у. 



•     LEFT — размещение левой   границы текста  по координате  х. 



•     RIGHT — размещение правой  границы текста   по координате  х. 



 •    HCENTER — размещение  середины  текста  по координате   х. 

На рисунке  показано расположение   точек  привязки на прямоугольнике,  ограни- 

чивающем   границы   текста. 



                          Left |  Top         Hcenter |  Top         Right |   Тор 



                                          Some text to draw     .   ' 



                        Left |   Baseline   Hcenter |   Baseline   Right |   Baseline 



                               Рис. 4.2 . Схема точек  привязки текста 



Класс Graphics  предоставляет   следующие   методы  рисования   текста: 

 •    void   drawChar(char    character ,    int  x.  int   y.  int  anchor)   — отображает     символ 

    character,  размещая   его точку   привязки, заданную   аргументом                anchor, в   точке 

    с координатами   (х,у); 

 •    void drawChars(char[]   data,   intoffset .     intlength .   intx .  inty ,  int   anchor)  — ри- 

    сует 1 ength символов массива data, начиная с символа с индексом offset.  Логи- 

    ка размещения текста   на экране остается   прежней; 



 •    voiddrawString(Stringstr,   intx ,   inty .   intanchor)   — рисует   строку   str   в указан- 

     ной позиции; 

 •    void drawSubstring(String str ,       intoffset ,   intlen ,  intx ,  inty ,  int   anchor)  — ри- 

     сует  len  символов   строки  str ,  начиная с символа  с индексом            offset. 

 Работу  с текстом мы рассмотрим  подробнее в дальнейшем  на примере  программы 

 для   чтения книг Book Reader.  Кроме базовых   геометрических   фигур   и текста, как 

 и в прошлой  главе,  мы можем   вывести   на экран картинку, представленную   фай- 

 лом  в формате  PNG . Теперь мы можем  четко указать   положение  картинки на эк- 

 ране, используя   константы  привязки для   рисования текста,  а также   одну   допол- 


----------------------- Page 47-----------------------

4 8         Глава 4 . Работа с графикой 



нительную константу VCENTER, которая размещает точку привязки в середине изоб- 

ражения по оси у.  Метод для отображения картинки: 

•    void drawlmage(Image  Img, intx .   inty .   int anchor) — рисует картинку, заданную 

    аргументом  img, разместив точку ее привязки anchor в координатах (х, у). 



Обработка клавишных событий 



Интерфейс низкого уровня позволяет нам отслеживать  нажатия клавиш. Если 

в  интерфейсе высокого уровня   события формировались только для   добавлен- 

ных   команд, то   интерфейс низкого уровня   формирует   события   при нажатии 

любой  клавиши, причем теперь  можно узнать, какая конкретно клавиша была 

нажата. 

Класс Canvas  также  определяет   блоки прослушивания событий другого уровня. 

Теперь в зависимости от действий пользователя будет автоматически вызван один 

из  следующих   методов класса Canvas: 

 •   void keyPressedC int keyCode) — вызывается, если какая-либо клавиша была на- 

    жата; 

 •   void keyRel eased( int keyCode) — вызывается, если какая-либо клавиша была от- 

    пущена; 

 •   void keyRepeated( int keyCode) — вызывается при длительном нажатии клавиши. 

 Все  методы   принимают аргумент,  представляющий   код нажимаемой клавиши. 

 Коды клавиш задаются следующими константами класса Canvas: 

 •   KEY_NUM0, KEYJUM1, KEYJUM2,... KEYJUM9 -      представляют   цифры на   клавиатуре 

    телефона; 

 •   LEFT, RIGHT, UP,  DOWN — представляют   клавиши телефона, обозначенные стрел- 

    ками; 

 •    KEY_POUND — представляет  клавишу с символом # (решетка); 

 •    KEY_STAR — представляет  клавишу с символом * (звездочка). 

 Для того чтобы привязать какие-то действия к желаемому событию, в классе-по- 

 томке  класса  Canvas   необходимо   переписать  соответствующую          функцию и там 

 реализовать необходимые действия. 

 Перейдем от теории к практике. Рассмотрим подробнее обработку   событий низ- 

 коуровневого интерфейса на примере программы управляемой точки, рисующей 

 фигуры на экране мобильника. 

 Реализуем   класс  Point, наследованный из класса  Canvas, который будет  содер- 

 жать членами класса текущие координаты точки, размеры отображаемой облас- 

 ти экрана, команду очистки экрана, а также флаг, управляющий очисткой экра- 

 на.  Блок  прослушивания событий низкого уровня будет отслеживать   нажатия 

 клавиш   и менять  текущие   координаты точки  соответственно   нажатой цифре. 

 Блок   прослушивания команд будет  поднимать флаг  в случае вызова команды 

 очистки экрана. 



 Код   класса Point выглядит следующим образом: 



 public  class  Point extends Canvas  implements  Commandl_istener { 

     private  int x:        // координата х точки 


----------------------- Page 48-----------------------

                                                         Обработка клавишных событий                         4 9 



private    i n t  у ;         //  координата    у  точки 



private    i n t  width :     //  ширина   экрана 



private    i n t  height :    //  высота   экрана 



private    boolean    cirFlag   -  true :  //  флаг   очистки   экрана 



private    Command   clear :               //  команда   очистки   экрана 



//   конструктор     класса   Point 



public    Point О    { 



     //   вызов   конструктора     родительского     класса 



     superO: 



      // получить ширину экрана 

     width=getWidth() : 

      // получить     высоту   экрана 

     height=getHeight(); 

      // установить текущие координаты           точки в центр     экрана 

     x=width/2 : 

     y=height/2 : 

      // создать объект команды очистки            экрана 

      clear = new Command(«Clear» , Command.OK . 1) ; 

      // добавить     команду очистки      экрана 

      addCommand(clear) : 

      // установить блок прослушивания            команды 

      setCommandLi stener(this) : 

 } 

 // метод    перерисовки     экрана 

public void paintCGraphics g ) { 

      // проверить флаг очистки         экрана 

      if (cirFlag ) { 

           // установить текущий        цвет белым 

           g.setColor(Oxffffff) : 

           // залить прямоугольник         размером с экран 

           g.fillRect(O . 0 . width , height) : 

           // сбросить флаг очистки         экрана 

           cirFlag - false : 

      } 

      // установить текущий        цвет точки     красным 

      g.setColor(255.0.0); 

      // отобразить точку в текущих           координатах 

      g.drawLine(x.y.x.y) : 

 } 

 // метод , реализующий       блок прослушивания       команды 

 public void commandActiontCommand            с . Displayable s ) {. 

      // если сработала       команда    очистки 

      if (с -- clear ) { 

           // поднять флаг очистки         экрана 

           cirFlag = true : 

           // установить      текущие координаты       точки в центр экрана 


----------------------- Page 49-----------------------

5 0          Глава 4 . Работа с графикой 



               x=width/2 : 

              y=height/2 : 

               // инициировать     перерисовку    экрана 

               repaintO : 



     // блок    прослушивания    событий   низкого уровня 

     public void     keyPressed(int    keyCode ) { 

          switch   (keyCode ) { 

               // сместить текущие координаты         точки 

               // в соответствии с нажатой        клавишей 

               case KEYJIUM1 : х- : у- ; break : 

               case KEYJUM2 : у- ; break ; 

               case KEYJUM3 : х++ ; у- ; break ; 

               case KEYJIUM4 : х- : break ; 

               case KEY_NUM6 : x++ : break : 

               case KEYJUM7 : x- : y++ : break : 

               case KEY_NUM8 : y++ : break : 

               case KEY_NUM9 : x++ : y++ ; break : 

          } 

          // инициировать      перерисовку   экрана 

          repaintO; 



Обратим внимание на то, что в отличие от работы с формой, изменения изобра- 

жения не сразу  отражаются   на экране. Перерисовка экрана, реализованная в ме- 

тоде pai nt, должна быть вызвана явно, с помощью одного из следующих   методов 

класса Canvas: 

•     void repaintdnt  х,   inty ,   int width,   int  height)  — перерисовка прямоугольной 

    области экрана шириной width и высотой height, левый верхний угол которой 

    находится в точке с координатами (х,у); 

•     void repaintO  — перерисовка экрана целиком. Аналогична такому вызову пре- 

    дыдущего метода: repaint(0.  0, getWidthO.   getHeightO). 

Сам     же метод   paint   вызывается   внутренней реализацией, мы никогда  не будем 

вызывать его явно. 

Основная работа выполнена. Отображение управляемой точки на экране выпол- 

няется в стартовом методе мидлета точно так же, как и в предыдущем   случае: 



public    void   startAppO   { 

     //   создать   объект  управляемой точки 

     Point   point   =   new PointO : 

     //   получить   ссылку   на   менеджер дисплея 

     display    =   Display.getDisplay(this) ; 

     //   вывести управляемую точку   на   экран 

     di   splay.setCurrent(poi n t ) ; 


----------------------- Page 50-----------------------

                                               Обработка   клавишных событий         5 1 



Немного поиграв в то, что получилось, мне удалось  нарисовать вот такую картин- 

ку (рис. 4.3). 

С этого примера мы начнем разработку   законченной, качественной игры, ко- 

торой уже  и не стыдно будет похвастаться даже перед профессионалами. Идея 

далеко не оригинальна: это знакомая нам с детства  «Змейка», так любимая раз- 

работчиками корпорации Nokia. Самое время дать наш ответ заграничным Чем- 

берленам   и укротить   непослушную   змею. На 

этом примере мы и будем постигать тонкости 

программирования под мобильные телефоны, 

постепенно  усложняя   и совершенствуя         нашу 

игру.  Но это будет уже   в следующих   главах... 



                       * * * 

В этой главе мы рассмотрели работу с пользова- 

тельским интерфейсом низкого уровня, а также 

графические возможности языка J2ME и логику 

обработки  событий. Поскольку класс  Canvas не 

является потомком класса Screen, он не исполь- 

зует ни одной высокоуровневой абстракции, оп- 

ределяемой   иерархией   класса  Screen, например 

добавление заголовка или бегущей строки невоз- 

можно. С другой стороны, класс Canvas принад- 

лежит к иерархии отображаемых   объектов типа 

Di spl ayabl e, поэтому он может задействовать об- 

работку   команд высокого уровня и реализовать 

блок прослушивания команд. 



Для   использования интерфейса низкого уров- 

ня необходимо  создать  новый класс, расширя- 

ющий класс Canvas, и реализовать его абстракт- 

ный метод paint, который и будет осуществлять 

прорисовку экрана. В этой главе мы также рас- 

смотрели   большинство  возможностей   и мето- 

дов классов Graphics и Canvas. Кое-что осталось 

на  самостоятельное   изучение,  благо  корпора- 

ция   Sun   Microsystems   предоставляет    подроб-       Рис. 4.3 . Текст, написанный 

                                                           с помощью «управляемой 

ную документацию языка.                                             точки» 


----------------------- Page 51-----------------------

 Глава 5 



Структуры 

и     форматы данных в J2ME 



Самое время перейти к теме, с которой, наверное, стоит начинать изучение любо- 

го языка программирования: типы и структуры   данных. Какие-то типы мы  уже 

использовали в примерах, теперь рассмотрим все существующие   типы, а также 

структуры   хранения данных. 

Кому-то вводная информация о типах данных и контейнерах языка Java может по- 

казаться скучной. В таком случае лучше сразу перейти к водным процедурам — ко 

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

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

мы продолжим. 



Объекты 



Язык Java является объектно-ориентированным языком даже в большей степени, 

чем остальные языки, оперирующие понятием «объект». Любые данные представ- 

ляются в виде объектов  — сущностей, имеющих набор свойств-полей и методов 

для работы с этими свойствами. 

Создается новый объект с помощью операции new, где указывается тип создава- 

емого объекта. При создании объекта автоматически вызывается его конструк- 

тор — особый метод, отвечающий за инициализацию объекта. Конструктор мо- 

жет иметь входные параметры инициализации, которые передаются в операции 

new через круглые  скобки, например: 



String   st r  -   new StringC'some   text") : 



Объект может иметь несколько конструкторов. Вызывается тот конструктор, пара- 

метры которого совпадут   по количеству и типу с параметрами, переданными че- 

рез new. 

В двух словах   коснемся наследования классов. На основе любого класса можно 

создать свой собственный класс, который будет содержать все поля и методы ро- 

дительского   класса, а также  те  свойства  и методы, которые мы решим  придать 

новому   классу.   Реализуется   наследование  с использованием  ключевого   слова 

extends, которое мы уже применяли в прошлой главе: 

public class Graph extends Canvas 

Для знатоков  C++     заметим, что в языке Java множественное наследование запре- 

щено  вообще.  Вместо   этого  предлагается     сущность,  именуемая   интерфейсом. 


----------------------- Page 52-----------------------

                                                                         Массивы         5 3 



Интерфейс отличается   от обычного класса тем, что содержит только заголовки 

методов и не содержит их реализации. Любой создаваемый класс может реализо- 

вать один или несколько интерфейсов, как мы это делали в прошлой главе: 

public class Point extends Canvas implements CommandListener 

К   сожалению, мы не имеем возможности глубоко вникать в идеологию объектно- 

ориентированного программирования, поэтому на данном этапе можно почитать 

дополнительную литературу   по этому вопросу. 



 Примитивные типы 



Несмотря на то, что Java является объектно-ориентированным языком, представ- 

ляющим любые данные в виде объектов, все же существуют особые типы данных, 

называемые примитивными, которые очень широко используются   в мобильном 

программировании. 

Поддержка таких типов обусловлена   тем, что зачастую   малоэффективно созда- 

вать полноценный объект, используя оператор new и всю систему  объектного за- 

хвата памяти, для одной маленькой циферки. Далее приведены примитивные типы, 

которые поддерживает J2ME, с объемом памяти, выделяемым каждому   типу: 

byte       1 байт 

short      2 байта 

char       2 байта 

int        4 байта 

long       8 байт 

В  прошлых главах мы рассматривали   набор методов  различных   классов. Перед 

каждым   методом  указан  тип данных,  который возвращает   тот   или иной метод. 

Например, метод класса Canvas  int        getHeightO   возвращает высоту экрана в виде 

числа, представленного   примитивным типом  int. Часто  методы   выполняют ка- 

кие-то действия, но не возвращают  никаких данных или значений. В таком слу- 

чае тип возвращаемых данных в заголовке метода должен быть определен как voi d 

(пустой тип данных). В этом случае оператор return предписывает лишь выход из 

метода и не возвращает никаких значений. 

Справедливости ради заметим, что любой примитивный тип данных может быть 

представлен соответствующим  объектом одного из классов: Byte, Character, Short, 

 Integer, Long. 



 Массивы 



Массивом называется набор однотипных данных, хранимых  в смежных ячейках 

 оперативной памяти. Работа с массивами в языке Java производится в три этапа. 

 1.  Объявление массива. На этом этапе задается переменная и тип массива. Квад- 

    ратные скобки указывают   на то, что объявляется  именно массив. Например: 

 int array[] ; 

 Image images[] : 


----------------------- Page 53-----------------------

54          Глава 5 . Структуры и форматы данных в J2ME 



2.   Определение  массива  осуществляется   с помощью оператора   new, в котором 

   требуется   указать точный размер массива, называемый длиной. На этом этапе 

    происходит   захват памяти для размещения массива. Ссылка на захваченную 

    память присваивается переменной массива: 

array - new int[50] ; 

images = new Image[20] : 

3.   Инициализация массива — присвоение значений элементам массива. Каждый 

    элемент имеет свой индекс — порядковый номер от нуля до длины массива без 

    единицы. Обращение к элементам массива выглядит так: 



аггау[0]   =   5; 

аггауШ    =   10: 



Одна из основных  проблем обычных   массивов состоит в том, что при создании 

массива нужно точно указать количество хранимых в нем данных, что далеко не 

всегда   удобно   или вообще  невозможно. Кроме обычных   массивов существуют 

классы-контейнеры,  позволяющие  хранить   произвольное  количество  данных. 

Такие контейнеры позволяют удобно решать задачи, требующие частого добавле- 

ния и удаления  неизвестного заранее количества данных. Все контейнеры содер- 

жатся в пакете Java. uti 1. Класс Vector содержится в языке Java с самых первых ее 

версий. 



 Класс Vector 



Класс Vector предназначен для хранения произвольного числа аргументов самого 

общего типа Object, то есть хранимые данные должны быть тем или иным образом 

наследованы из класса Object. Под эту категорию попадают любые объекты языка 

J2ME, кроме примитивных типов данных. 

 Размер вектора (size) — это количество хранимых в нем элементов. Каждый эле- 

мент при добавлении получает   свой порядковый индекс, начиная с нуля. Таким 

 образом, индекс  последнего   элемента  вектора на единицу   меньше его размера. 

 Метод int  size()  возвращает текущий размер вектора, а метод boolean             is Empty () 

 возвращает значение true, если вектор не содержит элементов. 

 Метод void setSi ze(int   newSize) устанавливает новый размер вектора, причем если 

значение newSi ze превышает текущий размер вектора, то все новые элементы при- 

 нимают значение null. Если значение newSi ze меньше, чем текущий размер векто- 

ра, то все элементы с индексами newSize и выше удаляются. 

 Добавить новый элемент в вектор можно с помощью трех методов: 

 •    void   addElement(Object  obj)—добавляет      новый элемент в конец вектора. До- 

    бавленный элемент получает   наивысший индекс, размер вектора  увеличива- 

    ется на единицу; 

 •    void   insertElementAt(Object   obj.  int   index)   — добавляет   новый элемент с за- 

    данным индексом  1 ndex. Индекс элемента, находившегося на этом месте, а так- 

    же все следующие   индексы увеличиваются   на единицу. Аргумент  index дол- 

    жен быть больше или равен нулю и меньше, или равен размеру   вектора; 


----------------------- Page 54-----------------------

                                                                      Класс Snake        5 5 



•    void   setElementAKObject    obj,  int  index)   — заменяет элемент с индексом  index 

   на новый элемент obj, при этом старый элемент удаляется   из  вектора. 



Удалить   элементы из вектора можно также  следующими   методами: 



•    void  removeElementAt(int   index)—удаляет     из вектора элемент с индексом index, 

    при этом все индексы элементов, выше удаляемого,  уменьшаются   на единицу; 



•    boolean   removeElement(Object   obj)—удаляет     из вектора  первое вхождение  эле- 

   мента  obj   с логикой сдвига  индексов, как и в предыдущем   случае.   Метод  воз- 

   вращается   значение true, если  удаляемый   элемент  действительно   находился 

    в векторе; 



•    void   removeAll Elements О   — удаляет  все элементы  вектора; аналогично   вызову 

    setSize(O). 



Получить   конкретный элемент вектора можно с помощью следующих                   методов: 



•    Object  firstElementO    — возвращает  первый элемент  вектора; 



•    Object  lastElementO   — возвращает   последний элемент вектора; 



•    Object  elementAtdnt    index)   — возвращает  элемент вектора с индексом  1 ndex. 



Следующие   методы  предоставляют   возможности поиска элемента в векторе: 

•    boolean  contains(Object    el em)  — возвращает   значение true, если  элемент   el em 

    содержится   в векторе; 



•    int  indexOf (Object   el em)  — возвращает    индекс  первого   вхождения     элемента 

    el em в вектор; в случае отсутствия   элемента в векторе  возвращает - 1 ; 



•    int  indexOf (Object  elem,   int  index)   — возвращает   индекс первого   вхождения 

    элемента elem в вектор, начиная поиск с элемента с индексом  index; 



•    int  I a s 11 ndexOf (Ob ject  elem)   — возвращает   индекс последнего  вхождения   эле- 

    мента elem в вектор; 



•    int  lastlndexOf (Object  elem,  int  index)— возвращает индекс последнего   вхож- 

    дения элемента el em в вектор. Поиск осуществляется  в обратном направлении, 

    начиная с элемента с индексом  index. 



Класс Snake 



Самое время вернуться к нашей змее. В прошлой главе мы рассматривали  пример 

программы   управляемой   точки.  Теперь   расширим   это  приложение  до   управля- 

емой змейки. Сейчас мы заложим основу и реализуем  скелет нашей будущей игры. 



Первым  делом   нарисуем   собственно управляемую   змейку,  которая   будет   состо- 

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

тинкой.  По сценарию змея должна   не  просто  ползать   по экрану, а охотиться   на 

воображаемую       добычу,   поэтому   также   нам потребуется   дополнительная   логика 

для   управления   добычей     и   процессом   «поедания».   И  самое   главное:  если   змея 

укусит   свой хвост или упрется   в стену, то она неминуемо погибнет. 



Основной  класс, который будет реализовывать   всю логику   игры, будем  наследо- 

вать  от рассмотренного  в прошлой главе   класса  Canvas, который отвечает   нашим 


----------------------- Page 55-----------------------

56           Глава  5 . Структуры и форматы данных в J2ME 



требованиям рисования произвольной графики, а также организации управления 

на   низком уровне: 



private    class   Snake  extends Canvas 



Для реализации гибкого змеиного тела нам потребуется пять различных элемен- 

тов: голова, хвост, тело и два варианта сгибов тела, различающихся направлением 

поворота. Эти типы частей в нашем классе будут представлены следующими кон- 

стантами: 

 //  константы ,   определяющие тип части       тела  змеи 

private int HEAD - 0 ;                    //   голова  змеи 

private int TAIL - 1;                     //   хвост  змеи 

private int BODY - 2 :                    //   тело змеи 

private int ACLOCKWISEJURN = 3            // сгиб тела     против часовой     стрелки 

private int CLOCKWISEJRJRN = 4 ;// сгиб тела по часовой                  стрелке 

 В каждый момент времени мы точно знаем, в какую сторону ползет змея, и в соот- 

ветствии с этим делаем необходимые проверки и продвигаем змею в нужном на- 

 правлении. Возможные направления представлены следующими константами: 

 //  константы ,   определяющие     направление    движения 

private int UP =0 :               //  вверх 

 private int LEFT = 1;            //  влево 

 private int RIGHT = 2            //  вправо 

 private int DOWN • 3 :           //  вниз 

 private int direction            //  текущее направление      движения   змеи 

 Текущее  направление движения представлено  полем int direction, которое при- 

 нимает значение одной из констант, определяющих направление движения. 

 Поскольку  мы взялись за красивую  качественную  игру, то и змея наша должна 

 выглядеть достаточно реалистично, поэтому в зависимости от направления дви- 

 жения различные части змеи будут выглядеть по-разному. Заготовим для каждой 

 возможной части отдельные картинки и разместим их в папке ресурсов приложе- 

 ния   /res. Здесь можно проявить фантазию и художественное   мастерство. Я нари- 

 совал вот такие части (рис. 5.1). 



                              HeadLeft.       HeadUp .     HeadDown .      HeadRight. 

                                png             png            png            png 



                                                                             BBBB 



                              TailLefl.       TailUp.       TailDown.       TailRight. 

                                png             png            png            png 



                               ваш 

                              BodyLeft.       BodyUp. 

                                png             png 



                                                                             ili 

                            TurnDownLeft.   TurnUpLefl.    TurnDownRight.  TurnUpRight. 

                                png             png            png             png 



               Рис.   5 . 1 . Части змеи выглядят по-разному   в зависимости от направления 


----------------------- Page 56-----------------------

                                                                                        Класс Snake              5 7 



Частей змеи у нас фиксированное количество, не требующее  динамического до- 

бавления или удаления, поэтому в данном случае достаточно обычного массива, 

который также будет являться членом класса Snake: 



private    Image[] images; 



Размер картинки также будем хранить в отдельном поле: 



private    int   imageSize: 



Каждая часть тела змеи будет представлена  объектом класса SnakePart, который 

будет содержать   тип части, координаты и направление движения. Если для уп- 

равления точкой нам было достаточно  хранить только текущие  координаты, то 

теперь нам потребуется хранилище для всех частей. Класс Vector полностью отве- 

чает нашим требованиям, поэтому класс SnakePart должен быть наследован от клас- 

са Object.   Согласно правилам  хорошего   тона программирования работу   с  внут- 

ренними полями нового класса организуем  через дополнительные методы. Код 

класса SnakePart выглядит следующим образом: 

private    class   SnakePart  extends   Object     { 

     private     int  x:           //  координата х 

     private     int   у:          //  координата у 

     private     int  part :       //  код  части   тела   змеи 

     private     int  dir ;        //  код   направления движения 

     //   конструктор, принимающий   все   поля       как аргументы 

     public    SnakePart(int _x,       int   _y.  int   _part,   int   _dir)   { 

           x   = _x: 

          У   -   _У: 

           part   = _part : 

           dir   = _dir : 



      } 



      //  получить код части       тела змеи 

      private    int  getPartO     {   return  part :  } 

      //  получить код направления         движения 

      private int getDirO { return dir : } 

      //  получить координату х 

      private    int getX O { return x; } 

      //   получить координату у 

      private    int getY O { return y ; } 

      //  установить     тип части и направление 

      private    void   setPartDir(int  _part .     int   _dir)   { 

           //   если  второй аргумент 0 . то 

           //   направление остается       неизменным 

           part = _part : 

           ifCJir      != 0 ) dir - _dir ; 



 Все элементы змеи будет хранить поле 



 private   Vector snake; 


----------------------- Page 57-----------------------

58           Глава    5 . Структуры и форматы данных в J2ME 



Также для организации графики нам потребуются   следующие  поля: 

private int width :         // ширина  экрана 

private int height ;       // высота  экрана 

private int xHead :        // координата х головы змейки 

private int yHead :         // координата у головы змейки 



После всех необходимых   приготовлений и организации структуры   класса можно 

переходить   к конструированию  нового объекта  класса Snake: 

// конструктор     класса  Snake 

public SnakeO { 

     // конструктор    родительского   класса 

     superO : 

     // создать    массив для картинок-частей     змейки 

     images • new     Image[20] : 

     // создать    обьекты  картинок для каждой    части 

     try { 

          // голова во всех     направлениях 

          images[0] = Image.createlmageC'/HeadUp.png") : 

          images[l] » Image.createlmageC'/HeadLeft.png") ; 

          images[2] = Image.createlmageC'/HeadRight.png") ; 

          images[3] = Image.createlmageC'/HeadDown.png") : 

          // хвост во всех     направлениях 

          images[4] = Image.createlmageC'/TailUp.png") ; 

          images[5] = Image.createlmageC'/TailLeft.png"): 

          images[6] = Image.createlmageC'/TailRight.png") : 

          images[7] = Image.createlmageC/Tail Down.png") : 

          // тело 

          images[8] - Image.createlmageC"/BodyUp.png") : 

          images[9] » Image.createlmageC"/BodyLeft.png") ; 

          images[10] = images[9] : 

          images[ll] = images[8] : 

          // сгиб тела    против  часовой  стрелки 

          images[12] = Image.createlmageC"/TurnDownLeft.png") ; 

          images[13] = Image.createlmageC'/TurnUpLeft.png") ; 

          images[14] = Image.createlmageC"/TurnDownRight.png") ; 

          images[15] » Image.createlmageC"/TurnUpRight.png") ; 

          // сгиб тела по часовой      стрелке 

          images[16] - images[14] ; 

          images[17] = images[12] ; 

          images[18] = images[15] : 

          images[19] = images[13] ; 

      } 

     catch   (IOException   ioe ) {} 

      // получить   размер одного   элемента   змеи 

       imageSize = images[O].getWidth() ; 

     /7 создать    вектор , хранящий   элементы  змейки 


----------------------- Page 58-----------------------

                                                                                       Класс Snake              5 9 



     snake   =   new VectorO : 



     //   получить  ширину    экрана 

     width = getWidthO ; 

     //   получить   высоту   экрана 

     height = getHeightC) : 

     //   установить    координаты    головы в центре     экрана 

     //   для  транспортабельности       координаты    кратны   imageSize 

     xHead = ((width/2)/imageSize)*imageSize : 

     yHead = ((height/2)/imageSize)*iiriageSize; 

     //   создать   элемент    головы  змейки 

     SnakePart     parts - new SnakePart(xHead,yHead.HEAD.UP) ; 

     //   добавить    элемент в вектор 

     snake.addElement(partS); 

     //   создать   элемент   тела   змейки 

     parts = new      SnakePart(xHead.yHead+imageSize.BODY,UP) ; 

     snake.addElement(partS) : 

     //   создать   элемент   хвоста   змейки 

     parts - new      SnakePart(xHead.yHead+2*imageSize.TAIL.UP) ; 

     snake.addElement(partS); 

     //   установить    текущее   направление     движения 

     direction = UP ; 

     //   сбросить   флаг   окончания игры 



     gameOverFlag   =     false : 



} 



Как    описывалось в прошлой главе, для любых классов, расширяющих класс Canvas, 

обязателен к реализации метод pai nt (),                 отвечающий за перерисовку экрана. Прой- 

дем по всем элементам вектора и выведем на экран соответствующие  части змей- 

ки   согласно их координатам: 

public    void paint(Graphics       g ) { 

      //  очистить    экран 

     g.setColor(OxFFFFFF) : 

     g.fillRect(O.0,width.height); 

     g.setColor(OxOOOOOO); 

      //  нарисовать    рамку 

     g.drawRect(l.l,width-3.height-3); 

      //  получить    длину  змейки 

      int snakeLen = snake.sizeO ; 

      //  если   поднят   флаг  конца   игры 

      if ( gameOverFlag ) { 

           //  вывести    сообщение 

           g.drawStringC'GAME       OVER" .width/2.height/2-10.g.HCENTER|g.TOP): 

           //  вывести    счет , равный   длине   змейки 

           String    score - new StringC'YOUR       SCORE :   "+snakeLen) : 

           g.drawString(score,width/2.height/2+10.g.HCENTER|g.TOP) : 

           return : 


----------------------- Page 59-----------------------

6 0          Глава 5 . Структуры и форматы данных в J2ME 



     }  else { 

         // для каждого     элемента  змейки 

         for ( int 1-0 : i<snakeLen ; i++ ){ 

              // вывести    картинку ,  индекс  которой  вычисляется 

              // с помощью    комбинации   кода части  тела и кода   направления 

              g.drawImage(images[4*((SnakePart)snake.elementAt(i)).getPart()+ 

                        ((SnakePart)snake.elementAt(i)).getDir()]. 

                        ((SnakePart)snake.elementAt(i)).getX(). 

                        ((SnakePart)snake.elementAt(i)).getY(). 

                       g.HCENTER    | g.VCENTER) ; 



Теперь добавим управление нашей змейкой. Для этого реализуем  метод key Pressed (i nt 

keyCode).  Перед  тем   как осуществлять          передвижение,  будем  проверять,  можно  ли 

ползти   в   заданном     направлении.  Если   на  пути   стена   или   собственный   хвост,  то 

поднимаем   флаг   конца  игры. 

public void keyPressed(int      keyCode ) { 

     // если флаг    конца  игры не поднят 

     if ( IgameOverFlag )!{ 

          switch   (keyCode ) {               i 

               // проверить , можно ли передвигаться в заданном        направлении . 

               // и вызвать соответствующую      функцию  передвижения 

              case KEY_NUM2 : 

 checkMoveUHead,     yHead-imageSize) ; moveUpO :    break : 

              case KEY_NUM4 : 

 checkMoveCxHead-    imageSize . yHead) : moveLeftO :    break : 

              case KEY_NUM6 : 

 checkMove(xHead+    imageSize . yHead) : moveRightO ;    break : 

              case KEY_NUM8: 

 checkMove(xHead . yHead+    imageSize) : moveDownO :    break : 

          } 

          // вызвать    перерисовку  экрана 

          repaintO : 



 // проверить    возможность   передвижения 

 // в точку с координатами      (хН.уН ) 

 public void checkMove(int      xH , int yH ) { 

      // для каждого    элемента  змеи 

     for ( int 1-3 ; i<snake.size()-l : i++ ) { 

          // если   проверяемая   точка совпадает с координатами      элемента 

          if ( хН —     ((SnakePart)(snake.elementAt(i))).getX( ) && 

               yH == ((SnakePart)(snake.elementAt(i))).getYO ) 

               // поднять флаг    конца  игры 


----------------------- Page 60-----------------------

                                                                              Класс Snake            6 1 



         gamedverFlag=triie; 



// передвижение     влево 

private void moveLeftO { 

     // если   текущее  направление не направо 

     if ( direction!=RIGHT ) { 

          // сдвинуть координату     головы  влево 

         xHead--imageSize : 

          // проверить   выход за левую    границу 

         if ( xHead < 4 ) { gameOverFlag=true ;      return ; } 

          // получить первый    элемент  змеи 

         SnakePart    head - (SnakePart)(snake.firstElementC)) : 

          // если   текущее  направление   вверх 

          if ( direction—U P ) 

              // заменить    голову на сгиб против    часовой  стрелки 

              head.setPartDi r(ACLOCKWISE_TURN.LEFT) : 

         else { 

              // если   текущее направление    вниз 

              if ( direct!on==DOWN ) 

                   // заменить   голову на сгиб по часовой     стрелке 

                   head.setPartDi r(CLOCKWISE_TURN.LEFT) ; 

              else // если    текущее направление    влево 

                   // заменить   голову на тело 

                   head.setPartDir(BODY.LEFT); 



          } 

          // создать   новый  элемент для головы 

          SnakePart   sPart = new SnakeParttxHead.yHead.HEAD.LEFT) ; 

          // добавить   голову первым   элементом   вектора 

          snake.insertElementAt(sPart.O) : 

          // продвинуть    хвост 

          // удалить   последний   элемент 

          snake.removeElement(snake.1astElement()): 

          // назначить хвостом     предпоследний   элемент 

          ((SnakePart)snake.lastElementО).setPartDi r(TAIL,-1) ; 

          // установить    текущее  направление 

          direction - LEFT : 



 Передвижение  в остальных   направлениях реализуйте   самостоятельно  в функци- 

ях moveUp(), moveRight(), moveDown() по образу   и подобию приведенной выше функ- 

ции     moveLeftO. 

 Итак, основа игры написана. Приложение уже  можно компилировать и запускать. 

 На   экране мы увидим   вот такого  милого червячка  (рис.  5.2). 


----------------------- Page 61-----------------------

62           Глава   5 . Структуры и форматы данных в J2ME 



Управление змейкой уже реализовано, поэтому можно смело делать первые шаги, 

точнее, первые ползки. Покрутимся в разные стороны и убедимся,  что все пово- 

роты реализованы без ошибок. При попытке уползти за пределы экрана мы долж- 

ны получить  сообщение о завершении игры (рис. 5.3). 



       Щ$Щ№&-                                              # ( l )   Nokia 7210 SDK 



                                                            File  Tools  Help 



            Рис.  5.2 . Змейка   в начале  игры                    Рис. 5.3 . «Game   over!» 



 Итак, все  работает   отменно, кроме  главного   момента   — змея   не растет.  Как мы 

 помним из аналогов, для роста змее требуется   вкусная и здоровая пища, которая 

 появляется на экране случайным  образом. Пища в нашей игре будет  представле- 

 на сердечком,  картинку  для   которого  сохраним   в файле  heart.png,  находящемся 

 в папке /res вместе с остальными картинками элементов змейки. Для реализации 

 правильного питания в класс Snake добавим  несколько полей: 



 private   Image   heart :        //  картинка сердца 



 private   in t  xHeart:          //  координата   х  сердца                            . 



 private   in t  yHeart :         //  координата у   сердца 



 private   boolean  eatFlag :     //  флаг   удлинения   змеи 



 Также  нам понадобится   метод   вычисления  координат для   случайного   размеще- 

 ния   сердечка       на  экране.  Здесь   нам   потребуется           генератор      случайных       чисел, 


----------------------- Page 62-----------------------

                                                                                       Класс Snake              6 3 



представленный   в языке J2ME   классом   Random. Очередное   случайное   число  гене- 

рируется  с помощью метода   int nextlnt О. Заметим, что число может  быть  отрица- 

тельным, поэтому требуется   получить  его абсолютное значение, используя   метод 

класса  Math int       absdnt      a). 

//   получить   новые  координаты  для   сердца 

public    void   setNewHeartO     { 



     //   получить    количество    возможных   позиций   по   координате  х 

      int  xPartMaxNum   •   (width-5)/imageSize; 

     //   получить    количество    возможных   позиций   по   координате  у 

     int   yPartMaxNum   =   (height-5)/imageSize : 



      iI получить длину змеи 

      int len • snake.sizeO : 

      // создать объект      генератора    случайных чисел 

     Random    rnd = new    RandomO : 

      // флаг    корректной    генерации 

     boolean     setFlag = false ; 

      // повторяем ,    пока не сгенерируем       корректные координаты      сердца 

     while ( IsetFlag ) { 

           // поднять флаг      корректной    генерации 

           setFlag - true : 

           // сгенерировать      случайную    координату    х . кратную   imageSize 

           xHeart = imageSize*(Math.abs(rnd.nextInt())PxPartMaxNum+l): 

           // сгенерировать      случайную    координату у , кратную      imageSize 

          yHeart - imageSize*(Math.abs(rnd.nextInt()UyPartMaxNum+l) ; 

           // проверить совпадение        координат сердца 

           // с координатами       элементов   змеи 

           for ( int i=0 : i<len-l : i++ ) 

                if ( xHeart == ((SnakePart)(snake.elementAt(i))).getX( ) && 

                     yHeart == ((SnakePart)(snake.elementAt(i))).getYO ) 

                     // опустить флаг       корректной    генерации 

                     setFlag • false : 



 В   конструкторе   класса   Snake добавим               создание   объекта   картинки для              сердечка 

 heart    = Image.createlmage("/heart.png")               и в самом  конце вызовем  метод   вычисле- 

 ния    координат для   этой  картинки setNewHeart().  В методе   paint О   вызовем   про- 

рисовку   картинки : g.drawlmage(heart,xHeart                    .yHeart.д.HCENTER         |  g.VCENTER). 



 Осталось   реализовать   логику   удлинения змеи при встрече  с сердечком.  В   методе 

 checkMove() при попытке передвижения проверяем, совпадают ли координаты пред- 

 полагаемого        передвижения   с   координатами   сердечка,  и   в   случае   равенства   под- 

 нимаем флаг удлинения   змеи: 



 i f  (  хН   ==   xHeart  &&  уН   —  yHeart  ) 



       eatFlag    •  true : 

 else                                                                         •   . 

       eatFlag  *    false : 


----------------------- Page 63-----------------------

64          Глава   5 . Структуры и форматы данных   в J2ME 



В методах передвижения moveUp (), moveLeft О, moveRi ght (), moveDown (), перед тем как 

продвинуть хвост, удаляя   последний элемент вектора, нужно проверить, не под- 

нят   ли флаг удлинения змеи. В случае удлинения  вызовем метод генерации но- 

вых координат для сердечка setNewHeart(), а продвижение хвоста производить не 

будем: 

// если флаг удлинения     змеи  поднят 

if   (eatFlag ) 

     // получить   новые  координаты для сердца 

     setNewHeartO : 

else { 

     // продвинуть   хвост 

     // удалить   последний  элемент 

     snake.removeElement(snake.1astElement()) : 

     // назначить   хвостом  предпоследний   элемент 

     (CSnakePart)snake.lastElementС)).setPartDir(TAIL.-l); 



Вот и все, самое страшное уже позади. Теперь подключаем все необходимые пакеты: 

 <&(1) Nokia 7210 SDK                     import   javax.microedition.midlet.MIDlet; 

                                          import   javax.microedition.lcdui.Image; 

                                          import   javax.microedition.lcdui.Canvas: 

                                          import   javax.microedition.lcdui.Graphics: 

                                          import   javax.microedition.Icdui.Display: 

                                          import   Java.util.Vector: 

                                          import   Java.util.Random: 

                                          import   java.io.IOException: 



                                          компилируем  получившееся   приложение и ис- 

                                          правляем ошибки, без которых не обойтись, осо- 

                                          бенно на первых этапах освоения нового языка. 

                                          Запускаем приложение и посвящаем час-другой 

                                          превращению червяка в анаконду (рис. 5.4). 

                                          Заметим, что  мы написали только  основу   для 

                                          игры, ведь змейка пока что не ползает самосто- 

                                          ятельно, так что играть в такую  игру будет ин- 

                                          тересно   только   детям   дошкольного   возраста, 

                                          и то лишь первые десять минут. Также хотелось 

                                          бы расширить игровое поле на весь экран, реа- 

                                          лизовать  систему   хранения таблицы  рекордов 

                                          и   многое другое, чем  мы  и займемся   в следу- 

                                          ющих главах. 



                                           Класс Stack 



                                          В заключении главы рассмотрим еще один класс- 

     Рис. 5.4. Змейка в действии           контейнер, реализующий структуру данных стек. 


----------------------- Page 64-----------------------

                                                                         Класс Stack         6 5 



Данная структура характерна особым подходом к добавлению и удалению элемен- 

тов. Принцип хранения данных LIFO (Last  In, First Out) означает, что извлечь из 

структуры можно только тот элемент, который был добавлен последним. Для ясно- 

сти проведем аналогию с узким стаканом, в который можно складывать различные 

предметы, но достать можно лишь тот предмет, который находится выше всех. 

В языке J2ME класс Stack реализован на основе класса Vector, расширяя его функ- 

циональность следующими  методами: 



•    Object   push (Object  item)—добавляет  элемент на вершину стека; 

 •   Object   pop О — извлекает элемент с вершины стека; 

•    Object   peek () — возвращает элемент с вершины стека без его извлечения; 

 •   boolean   empty() — возвращает значение true, если стек пуст. 

ш    int  search(Object    о) — возвращает   позицию искомого объекта относительно 

    вершины стека. Если объект содержится в стеке несколько раз, то возвращает- 

    ся позиция самого верхнего; если элемент не содержится в стеке, то возвраща- 

    ется значение — 1. 



                                             *  *  *        \ 



Итак, мы рассмотрели в теории и применили на практике основные типы и струк- 

туры данных языка J2ME. Корректное обращение с данными и преобразование 

типов данных предотвратит массу возможных ошибок, поэтому к этому моменту 

стоит отнестись особо внимательно. Перед разработкой любого приложения сле- 

дует хорошо обдумать, каким образом будут храниться данные приложения, ка- 

кова область применения тех или иных данных, и в зависимости от этого уже про- 

ектировать структуру классов и самого мидлета. 


----------------------- Page 65-----------------------

Глава 6 



Организация 

 многопоточных приложений 



В прошлой главе  мы реализовали  основную часть игры «Змейка»,  придержива- 

ясь  классических   принципов игры. Используя   стандартное  управление, можно 

ползать по всему  экрану и собирать сердечки, удлиняя змею. При столкновении 

змеи со стеной или с собственным телом  игра заканчивается. Пока что  главное 

отличие от классической игры заключается в том, что змейка стоит на месте, если 

не нажимать управляющие клавиши. Таким образом, эта увлекательная и разви- 

вающая игра теряет всякий смысл. 



Основная   проблема  в реализации самостоятельного   продвижения змеи такова, 

что если программно зациклить какое-то действие, то приложение не будет иметь 

возможности выйти из цикла и обработать нажатие функциональных клавиш. На 

помощь в решении этой проблемы приходит многозадачность мобильной опера- 

ционной системы. 



 Многозадачность 



В современных операционных системах, в том числе и мобильных, реализовано 

понятие многозадачности, которое заключается в одновременном выполнении не- 

скольких программ. Слово «одновременно»   здесь надо понимать так: каждой из 

программ поочередно отводится определенное количество процессорного време- 

ни, в течение которого программа выполняется, а затем снимается с процессора, 

уступая   свое место другой программе. 

Сама программа, в нашем случае — мидлет, также может создавать несколько па- 

раллельных процессов, которые будут выполняться одновременно. Такие процес- 

сы в оригинале языка Java называются thread. Понятие thread в русской литерату- 

ре переводят  по-разному. В буквальном  переводе термин обозначает  «нить», но 

мы все же не швеи-мотористки, а какие-никакие программисты. Часто thread пе- 

реводят   как «поток»,  но, к сожалению, этот термин уже   занят потоками  ввода- 

вывода  (streams), на которых мы остановимся позже. Так что в данной главе мы 

будем пользоваться транслитерацией термина — «тред». 

В любом мидлете существует, по крайней мере, хотя бы один тред — главный тред 

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

ного выполнения. В языке Java треды представлены объектами класса Thread. 


----------------------- Page 66-----------------------

                                                                            Класс Thread           6 7 



 Класс Thread 



Класс  Thread  реализует   интерфейс Runnable, который содержит   объявление  лишь 

одного метода: voi d run () . В методе  run () должны быть реализованы все действия, 

предусмотренные   для   выполнения в конкретном треде.   Сам  класс Thread  содер- 

жит пустую реализацию метода   run (), поэтому задать  действия для   создаваемого 

треда  можно следующими   двумя   способами: 



•     расширить класс Thread и переопределить  метод   гип(); 



•     реализовать   интерфейс Runnable. 



Мы   будем   пользоваться   вторым   способом,  поскольку   в   наших   примерах   класс, 

создающий   тред,   уже   наследован   от   какого-то другого  класса,  а   множественное 

наследование в языке Java  не поддерживается.  Для  каждого   способа класс Thread 

содержит   отдельный   конструктор: 

 •    Thread О   — конструктор,  не  принимающий аргументов;   вызывается   из  конст- 

    руктора   класса-потомка, расширяющего  класс Thread; 



•     Thread(Runnable   target)     —   конструктор,  принимающий  в   качестве            аргумента 

    объект, реализующий   интерфейс Runnabl e; 



После того как объект треда создан, предписанные в методе  run О действия еще не 

начинают   выполняться. Для   управления   тредом   существует   несколько   методов 

класса Thread: 



 •    void start ()  — начать выполнение треда. Виртуальная   машина вызывает   метод 

    run(), который никогда не вызывается программистом явно. После вызова ме- 

    тода  start О   начинается параллельное  выполнение программы  в двух направ- 

    лениях: тред идет  своей дорогой, а поезд идет   своей; 



 •    void  sieepClong   mill is)  — приостанавливает   выполнение треда   на mill is             мил- 

    лисекунд. 



В этом месте мы остановимся и попробуем применить изученную  теорию на прак- 

тике. Вернемся к программе фотоальбома, который мы реализовали во второй гла- 

ве. Усовершенствуем   эту программу таким образом: добавим  показ слайд-шоу,  то 

есть автоматическую   смену  картинок на экране. 



Организуем управление автоматической сменой картинок в отдельном треде. Для 

этого   в   основном   классе   нашего   фотоальбома           SlideShow   реализуем      интерфейс 

Runnable. Теперь декларация класса будет выглядеть   следующим   образом: 



 public  class  SlideShow   extends  MIDlet  implements  CommandListener.   Runnable 



 Следует также добавить в класс SI i deShow новое поле для объекта треда: 



 private   Thread  thread; 



 Как уже   было  сказано ранее, все действия, выполняемые тредом, следует реали- 

зовать  в методе   гип(): 

 public   void  runО  { 

     //   вечный цикл 

     while   (true)  { 

          try  { 



              //  удалить   из  формы   текущую  картинку 


----------------------- Page 67-----------------------

68          Глава  6 .  Организация   многопоточных   приложений 



             form.delete(O): 

              // добавить в форму новую    картинку 

              // увеличиваем   текущий  номер картинки и берем 

              // остаток от деления на общее число картинок 

              setlmage("/"+((siideNum++)PmaxSlideNum+1)+".png"); 

              // остановить выполнение треда на 3 секунды 

             thread.sleep(3000) : 

         // обработать исключительную     ситуацию 

         } catch   (InterruptedException    e ) { 



В методе  startAppC)   осталось  создать  объект треда  и дать  команду для  начала его 

выполнения: 

// создать объект треда 

thread = new Thread(this) ; 

 // начать  выполнение треда 

thread . startO ; 



Заметим, что   после запуска   слайд-шоу   остановить   смену   картинок будет  очень 

проблематично, поэтому стоит все же воздержаться   от бесконечных  циклов и за- 

вести флаг, сбрасывая который, можно организовать завершение работы   треда. 



 Приоритеты тредов 



Как    уже   было   отмечено, одновременное  выполнение  нескольких   тредов                  доста- 

точно условно, поскольку физически в каждый момент времени выполняется толь- 

ко   один  тред.   Переключение   тредов   и  количество   выделенного   процессорного 

времени зависит от операционной системы, однако программист тоже может по- 

влиять  на этот   процесс. Иногда  некоторым тредам   требуется   выделить   большее 

или    меньшее   количество   процессорного   времени.  Технология                мультитрединга 

позволяет определить  приоритеты выполнения для  каждого   треда. 



Приоритеты тредов   представлены   целыми числами  в диапазоне от   1 до   10. Чем 

выше значение приоритета, тем больше времени выделяется треду операционной 

системой. Также в классе Thread определены константы MIN_PRIORITY, NORM_PRIORITY 

и   MAX_PRIORITY, представляющие   значения минимального, нормального  и макси- 

мального приоритетов   соответственно. 

 Работа с приоритетами осуществляется с помощью следующих методов класса Thread: 

 •    void  setPrioritydnt      newPriority)   — установить       новый   приоритет   треду. При 

    попытке  выставить   приоритет,  выходящий   за  допустимый   диапазон,  будет 

    сформировано исключение              IllegalArgumentException; 

 •    int getPriorityC)   — возвращает текущий приоритет   треда. 

 Продемонстрируем  на небольшом примере многозадачность  системы.  Запустим 

 параллельно  два   треда,  каждый   из которых   в цикле будет  выводить   сообщения 


----------------------- Page 68-----------------------

                                                                            Приоритеты тредов                 69 



в системную область (рис. 6.1). Главный тред будет выполнять основной код мид- 

лета в методе startApp(), а для второго треда создадим отдельный класс SecondThread: 



public    void   startAppO    { 

     // создать второй тред 

     SecondThread thread = new SecondThreadO ; 

     // запустить второй тред 

     thread.startO : 

     // в цикле выводить сообщения в системную область 

     for(int 1-1 ; i<=5 ; i++ ) 

          System.out.printlnC'Main  Thread:  "+i) : 



private class SecondThread extends Thread { 

      // в цикле выводить сообщения в системную область 

     public void run( ) { 

           fordnt    1-1 : i<=5 : 1++ ) 

               System.out.println("Second   Thread:  "+i) : 



                     IS J2ME Wireless Toolkit - SlideShm» 



                     i  l e  f  in  •   |,  •  H e l p 



                     I  O a New Project... | jjP Open Project %   Settings  \H$  Bum   %  Run 



                                ifaultGrayPhone 



                     Main Thread: 1 

                     Second Thread: 1 

                    |Main Thread: 2 

                     Second Thread: 2 

                     Main Thread: 3 

                     Second Thread: 3 

                     Main Thread: 4 

                     Second Thread: 4 

                     Main Thread: 5 

                     Second Thread: 5 



                     «I      •                  illllli                                    Ш 



        Рис.   6 . 1 . Результаты параллельной работы двух тредов с одинаковым  приоритетом 



 На рисунке мы видим, что переключение тредов работает, как швейцарские часы, 

 и сообщения от каждого  треда  выдаются   по очереди, несмотря на то, что  вывод 

 сообщений в программе представлен единым непрерывным циклом. 

 Попробуем теперь перед запуском второго треда выставить ему повышенный при- 

 оритет thread.setPriority(Thread.MAX_PRIORITY)                     и посмотрим, что же из этого по- 

 лучится (рис. 6.2). 

 Видим, что картина существенно поменялась: главный тред не начал выполнять- 

 ся, пока тред с повышенным приоритетом не закончил работу. Не исключена си- 


----------------------- Page 69-----------------------

70             Глава 6 . Организация  многопоточных  приложений 



туация, что при наличии множества тредов с высоким приоритетом какой-то низ- 

коприоритетный   тред   вообще   не получит                         управления.  Такую   ситуацию                   можно 

предотвратить   с помощью следующих   методов   класса Thread: 



 •     void yield()   — принудительное   переключение  системы на выполнение следу- 

     ющего   треда; 



 •     void joinC)   — ожидание  завершения работы  треда;  останавливает   работу   про- 

     граммы, из которой был вызван данный тред. 



                          jZME Wireless Toolkit - SlideShow 



                       File   Edit  Project  Help      . .   . 



                                          .  | ; j g Open Project                     '  Buid      Run 



                         Device:  JDefaultGtayPhone 



                                                                                                      • 



                       Second Thread : 1 

                       Second Thread : 2 

                       Second Thread : 3 

                       Second Thread : 4 

                       Second Thread : 5 

                      Kain Thread : 1 

                      Main Thread : 2 

                      Main Thread : 3 

                      Main Thread : 4 

                      Main Thread : 5 

                        -i                                                                          i И 

                                                                                    ' 

            Рис.    6.2 . Результаты параллельной работы двух тредов с разным приоритетом 



 При     создании тред получает приоритет создавшего его треда. Как правило, все треды 

 работают с нормальным (NORMPRIORITY) приоритетом. Треды, ожидающие какого-то 

 события, например нажатия кнопки, могут получить  повышенный приоритет, а тре- 

 ды, выполняющие длительную, ресурсоемкую   работу   — пониженный приоритет. 



 Отследить  состояние системы с точки зрения выполняющихся в конкретный мо- 

 мент тредов   можно с помощью следующих                             методов: 



 •     boolean isAiiveO  — возвращает  значение true, если тред был запущен ид о сих 

      пор    не прекратил выполнение; 



 •     Thread currentThreadC) — возвращает тред, выполняемый в данный момент вре- 

      мени; 



 •     i nt acti veCount С) — возвращает количество тредов, выполняемых системой в дан- 

      ный     момент времени. 



 Синхронизация тредов 



 Основная сложность при работе с тредами заключается в том, что разные треды, по- 

 рожденные одним мидлетом, имеют общее адресное пространство, то есть одни и те 

 же данные могут быть доступны из разных тредов. В чем здесь криминал? Рассмот- 

 рим,    например, ситуацию, когда два треда работают с одним и тем же вектором. Пер- 

 вый     тред   получает        длину   вектора,  затем   происходит   смена   выполняемого   треда, 

 и   второй тред удаляет элементы из этого же вектора. Первый тред возобновляет ра- 


----------------------- Page 70-----------------------

                                                             Синхронизация тредов            7 1 



боту и с удивлением   обнаруживает,  что получен- 

ная   длина вектора не соответствует       действитель- 

ности, формирует исключение неверного индекса 

и  падает с таким вот грохотом (рис. 6.3). 

Это далеко   не единственный   пример. Контек- 

стное   переключение   тредов   может   произойти 

в любой момент. В самой простейшей операции 

между     вычислением   и присваиванием   резуль- 

тата может вклиниться другой  тред и поменять                      java/lang/ArraylndexOut 

                                                                   OfBoundsException 

данные. Результаты  такой путаницы могут быть 

совершенно   непредсказуемы. 

Описанная проблема  разрешается   в языке Java 

при   помощи технологии синхронизации тредов.                                     Back 

Идея заключается  в блокировке тредом   доступа 

к  объекту   на время работы  с общими данными. 

Перед тем как начать работать  с данными, тред 

ставит   блок  на доступ   к объекту,   и все  осталь- 

ные треды   при необходимости   работы   с этими 

данными ожидают  снятия блока. 

Блокировка осуществляется   с помощью опера- 

тора   synchronized(Object      obj)  {...}. В   фигурных 

скобках   размещается   критическая   секция про- 

граммы — код приложения, требующий синхро- 

низации. В качестве аргумента  obj оператору пе- 

редается   объект,  который будет  заблокирован. 

 Если  в момент   выполнения оператора   synchro- 

nized синхронизируемый объект уже заблокиро- 

ван каким-то другим тредом, то выполнение при-             Рис.  6.3 . Демонстрация  ошибки при 

 остановится до тех пор, пока блок не будет снят.                     синхронизации 

 Если какой-то метод требует синхронизации всех 

 своих операторов, то весь метод может быть помечен как synchronized, например: 

public   synchronized  void  someActionWithDataO {...} 

 В таком случае  блокируется   объект this, вызвавший данный метод. Блок снима- 

 ется только после завершения работы   метода. 

 Очень неприятная ситуация может возникнуть при перекрестной блокировке, когда 

 один тред получил управление синхронизированным объектом А, а второй — синх- 

 ронизированным объектом В. После этого первый тред пытается вызвать синхрони- 

 зированный метод объекта  В, а второй — аналогичный метод объекта  А. Оба  треда 

 застрянут и будут ждать освобождения объектов, блокированных друг другом, а про- 

 грамма с такой структурой  тредов просто зависнет. В теории такая ситуация называ- 

 ется  взаимоблокировкой  (deadlock). Так что  при разработке  приложений следует 

 очень внимательно отнестись к вопросам корректной реализации синхронизации. 

 В языке Java существует   способ предотвращения взаимоблокировки. Внутри син- 

 хронизированной критической секции можно приостановить работу   треда  с по- 

 следующим   ее возобновлением.  Класс Object   содержит   несколько методов   wait, 

 которые откладывают   выполнение треда.  Отличие этих методов  от метода  sleep 


----------------------- Page 71-----------------------

72          Глава   6 . Организация многопоточных  приложений 



класса Thread заключается в том, что во время приостановки работы треда блоки- 

ровка с синхронизируемого   объекта   снимается. Методы  класса  Object,   отвеча- 

ющие за остановку и возобновление тредов: 

•    void wait()   — текущий тред снимает блокировку с объекта и приостанавлива- 

    ется до тех  пор, пока из какого-либо другого треда  не будут вызваны методы 

    notify  О  или   notifyAlK); 

•    void wait (long  timeout) — текущий   тред   снимает  блокировку   и   откладывает 

    выполнение,  пока  из какого-либо другого  треда   не будут   вызваны   методы 

    noti fy()   или noti fyAI 1 () или не истечет тайм-аут длиной timeout миллисекунд; 

•    voidwaitClongtimeout.   intnanos)  — аналогичен предыдущему  методу, с точнос- 

    тью тайм-аута  до наносекунд, которая задается  аргументом   nanos. Вызов этих 

    трех методов допускается только из синхронизированных методов или крити- 

    ческих секций программы; 

 •    void notify()   — возобновляет работу  одного приостановленного треда, произ- 

    вольно выбранного системой; 

 •    void noti fyAI 10   — возобновляет работу всех приостановленных на данном объекте 

    тредов. Фактически начинает работу только один тред, первым восстановивший 

    блокировку объекта, остальные ждут снятия блока в обычном режиме. 

 Следует   заметить,  что  при обращении к общим данным синхронизация нужна 

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

треды просто читают данные, не изменяя их, синхронизация тредов не требуется. 



 Класс Snake 



Теперь  у   нас достаточно   информации, чтобы  добавить   в игру   самостоятельное 

продвижение  змейки. Для   этого необходимо  реализовать   в  классе  Snake интер- 

 фейс Runnabie, а в методе  run О организовать цикл продвижения змеи в текущем 

направлении. Декларация класса Snake будет выглядеть теперь так: 

private class Snake extends Canvas implements Runnable 

 В методе run() будем проверять текущее направление движения змейки и в зави- 

 симости от этого вызывать соответствующий  метод продвижения. Обратим вни- 

 мание, что метод run О      и метод keyPressed(int   keyCode) будут изменять данные од- 

 ного и того же объекта, поэтому здесь потребуется синхронизация. В объявлении 

 метода keyPressed добавим ключевое слово synchronized, а в методе гип() выделим 

 критическую секцию кода: 



 public   void   run() { 

     //  пока не поднят флаг конца игры 

     while (IgameOverFlag ) { 

          //  синхронизировать с управлением 

          synchronized   (this ) { 

              //  продвинуть змейку в текущем направлении 

              if(direction==UP) {checkMove(xHead . yHead-7) ;    moveUpO: } 

              if(direction==LEFT) {checkMove(xHead-7 , yHead) :    moveLeftO: } 

              if(direction==RIGHT) {checkMove(xHead+7 . yHead) ;    moveRightO: } 

              if(direction==DOWN) {checkMove(xHead, yHead+7) :     moveDownO: } 


----------------------- Page 72-----------------------

                                                                                   Класс Snake             7 3 



          //  вызвать перерисовку      экрана 

          repaintO : 

          try { 

               //  приостановить    тред на speed    миллисекунд 

               Thread.sieep(Ievel*speed) : 

          } catch    (InterruptedException     e ) { 



Заметим, что в класс Snake мы ввели два новых поля, private byte level  и private  int 

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

содержит   уровень   сложности  игры, а поле  speed — коэффициент ускорения для 

вычисления времени, на которое «засыпает»  тред перед очередным продвижением 

змейки. Чем меньше этот параметр, тем  быстрее  будет двигаться   наша змейка. 

В конструкторе установим speed  = 100, level  = 5, а в методе checkMove после каждого 

«съеденного»   сердечка будем увеличивать скорость передвижения змейки: 

//   проверить совпадение     координат    передвижения 

// с координатами      сердца 

if ( хН == xHeart & & уН == уHeart ) { 

     eatFlag = true ; 

     // увеличить скорость 

     speed--; 

 }  else 

     eatFlag   -  false : 



Все, что осталось сделать, это создать в методе startApp()  новый тред и запустить его: 



//   создать   объект  треда  автоматического   передвижения 

Thread   moveThread   =   new Thread(curSnake); 

//   начать   выполнение   треда 

moveThread. s t a r t O : 



Компилируем, запускаем и наслаждаемся   процессом! Играть стало интересней, 

играть стало веселее, а корпорация Nokia кусает локти от зависти. На достигну- 

том мы, конечно же, останавливаться не будем и в следующих   главах еще усовер- 

шенствуем   нашу   игру.  Добавим   картинок-заставок, таблицу   рекордов, возмож- 

ность выбора уровня игры и прочие прелести. Оставайтесь с нами, не переключайте 

канал, дальше будет еще интереснее! 



                                                    *  * * 



 В этой главе  мы опробовали многозадачность  виртуальной  машины Java  на не- 

хитрых   примерах. Иногда без создания нового треда просто не обойтись  как это 

                                                                                                   ( 



 было в случае  со змейкой. При создании большой полноценной программы сле- 

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

 Зачастую   оправдано  создание  отдельных   тредов   для   каждой   подзадачи.  После 

 создания тредов следует подумать над распределением приоритетов. Особое вни- 

 мание следует  уделить   синхронизации тредов, если они обращаются   к одним 

 и  тем же данным программы. 


----------------------- Page 73-----------------------

Глава 7 



Сохранение данных 

и параметров приложения 



Как показывает практика, люди делятся на две категории: на тех, кто прошел Doom 

от начала до конца, и на тех, у кого не хватило терпения, реакции или секретных 

кодов, чтобы выпустить кишки всем злобным монстрам, которыми щедро нашпи- 

гован каждый новый уровень. Поколение игры Doom хорошо знает основной прин- 

цип ЗО-стрелялок: «Главное — не забыть сохраниться!»   В этой главе мы рассмот- 

рим,  как организовать долговременное хранение данных в мобильном телефоне. 

Хранение данных организовано в J2ME с помощью системы управления запися- 

ми   (Record   Management   System),  реализованной в  пакете   javax.microedition.rms. 

Данные представлены   записями (массив байтов  типа  byte[]),  которые помеща- 

ются в хранилище записей (Record Store) и не стираются при закрытии приложе- 

ния, перезагрузке телефона или замене батареи. Размер и физическое местополо- 

жение хранилища  в памяти телефона  зависят   от  конкретной модели   аппарата. 

Хранилище жестко привязано к мидлету, и когда вы удаляете приложение из сво- 

его телефона, то  все данные, относящиеся к нему, удаляются                 вместе  с ним. По 

сути, хранилище  записей — это база данных, которая может   быть   представлена 

следующей  схемой (рис. 7.1). 



                      Хранилище   записе й                  "Test  Storage" 



                         "Test  Storage"                      <record  1> 



                                                              <record  2> 



                            "Blondes" 



                                                              <record N> 



                             "SNAKE" 



                                                               "Blondes" 

                                "SNAKE"                       <record  1> 



                              <record                         <record  2> 



                              <record 2> 



                                                              <record  N> 



                              <record N> 



                               Рис.  7 . 1 . Схема хранилища  записей 


----------------------- Page 74-----------------------

                                          RecordStore — хранилище записей        7 5 



Применения для RMS могут быть разные, от сохранения параметров и пользова- 

тельских настроек приложения до поддержки полноценной адресной книги с ис- 

черпывающей личной информацией и гибким поиском. В игре, которую  мы пи- 

шем, мы будем использовать систему управления записями для хранения уровня 

сложности игры, а также таблицы рекордов. 



 RecordStore — хранилище записей 



Итак, хранилище записей представлено объектом класса RecordStore, работа с ко- 

торым начинается с метода открытия хранилища для дальнейшей работы   static 

RecordStore openRecordStore(StringrecordStoreName. booleancreatelfNecessary).  Первый 

аргумент метода — уникальное имя хранилища. Второй аргумент указывает, дол- 

жно ли быть создано новое хранилище с таким именем, если оно еще не существу- 

ет. Имя хранилища чувствительно к регистру, длина имени не должна превышать 

32 символа. 



Добавление записи в хранилище осуществляется с помощью метода i nt addRecord (byte[ ] 

data,   int offset,  int numBytes). Сохраняемые данные должны быть  представлены 

последовательностью   байтов, содержащихся   в массиве data. Переменная   offset 

указывает, с какой позиции в массиве начинаются данные, предназначенные для 

записи, a  numBytes  определяет   размер  сохраняемых   данных.  Метод   возвращает 

номер новой записи, присвоенный ей при сохранении (recordID). 

Рассмотрим вкратце еще некоторые методы хранилища записей RecordStore: 

 •   void  deleteRecorddnt   recordld)   — удалить  из хранилища  запись с номером 

    recordld; 

 •   static  void del eteRecordStore(StringrecordStoreName)  — удалить  хранилище за- 

   писей с именем recordStoreName;                                  \ 

 •   1 ong getLastModi f i ed() — возвращает время последнего изменения данных в хра- 

    нилище записей в виде количества миллисекунд, прошедших с 1 января 1970 года 

   до момента последнего изменения (Именно так!.. А кому сейчас легко?); 

 •   String getName() — получить имя хранилища данных; 

 •   1 nt getNextRecord ID () — возвращает номер, который будет присвоен следующей 

   добавленной записи; 

 •   int  getNumRecordsO — возвращает   количество записей, содержащихся   на дан- 

    ный момент в хранилище; 

 •   intgetRecordSize(int   recordld)  — возвращает размер (в байтах) конкретной за- 

    писи хранилища с номером recordld; 

 •   int getSizeO  — возвращает   объем  памяти (в  байтах),  который занимают  все 

    записи хранилища; 

 •   int getSizeAvailableO  — возвращает объем памяти (в байтах), доступный для 

    расширения данного хранилища; 

 •   int getVersion() — получить  номер версии хранилища. При каждой операции 

    с хранилищем, добавлении, удалении   или изменении любой его записи, под- 

    нимается номер версии хранилища; 


----------------------- Page 75-----------------------

7 6         Глава 7 . Сохранение данных и параметров приложения 



•    static   String[]   listRecordStoresO   — возвращает   массив имен всех хранилищ 

    мидлета; 

•    voidsetRecorddnt   recordld, byte[] newData. intoffset.   intnumBytes)  —изменить 

    данные, содержащиеся в записи с номером recordld; остальные параметры иден- 

    тичны параметрам метода addRecord. 

Доступ к данным хранилища предоставляет метод byte[] getRecorddnt reccrdID), 

где  recordID — уникальный номер необходимой записи. То есть, чтобы  получить 

запись, нужно точно знать ее номер. На этом этапе возникает небольшая пробле- 

ма: как запоминать и где хранить эти номера. Вариант сохранения номеров запи- 

сей в самом хранилище отпадает  — по той же самой причине. На выручку  прихо- 

дит нумератор — интерфейс RecordEnumerati on, осуществляющий перечисление всех 

записей в хранилище. 



 Record Enumeration — 

 нумератор списка записей 



Используя метод enumerateRecords (RecordFi I ter f i 1 ter, RecordComparator comparator, 

boolean   keepUpdated), можно получить   список записей конкретного хранилища. 

Навигация  по списку номеров осуществляется   с помощью методов   нумератора 

nextRecordldO   и previousRecordldO,  которые возвращают   по порядку все номера 

записей хранилища, или с помощью методов  nextRecordO  и previousRecordO, ко- 

торые возвращают непосредственно записи в формате bytet]. 

Для наглядности приведем небольшой пример кода программы, которая записы- 

вает в хранилище пронумерованные строки, а затем считывает их: 

 1 mport javax.microedition.rms.RecordStore ; 

 import javax.mi croedi ti on.rms.RecordEnumerati on ; 

 import javax.microedi ti on.rms.RecordStoreExcepti on : 

try { 

 String str ; 

     // открыть хранилище записей с именем      "Test Storage " 

     RecordStore recordStore = RecordStore.openRecordStore 

                                  ("Test Storage" , true) : 

     // получить нумератор списка записей 

     RecordEnumeration re - recordStore.enumerateRecords(null . null ,       false) : 

     // добавить в хранилище 5 записей , содержащих пронумерованные строки 

     fordnt    1-1 ;1<-5:1.-м-) { 

         str = "String no " + i; 

          int id = recordStore.addRecord(str.getBytes(),0.str.lengthO) : 

         System.out.println(str+" was stored with recordID = "+id) : 

     } 

     // перестроить список 

     re.rebuildO : 



     // получить все записи хранилища по их номерам 

     while   (re.hasNextElementO ) { 


----------------------- Page 76-----------------------

                                       RecordEnumeration — нумератор списка записей                           77 



          int id - re.nextRecordldO ; 

          str = new String(recordStore.getRecord(id)) : 

          System.out.printlnC'RecordID - "+id+" :   "+str) ; 



     // вернуть нумератор в исходное положение 

     re . reset О  : 



     // получить     все записи    хранилища 

     for   (int i-1 : i<=re.numRecords() : i++ ) { 

          str = new String(re.previousRecordO) ; 

          System.out.println("Record :        "+str) ; 



     // освободить ресурсы        нумератора 

     re . destroy О ; 



     // закрыть хранилище        записей 

     recordStore.closeRecordStore() : 



   catch(RecordStoreException         rse) { 

           System.out.pri ntln(rse.getMessage()) ; 



Результат работы примера выглядит так (рис. 7.2). 



             Device pefaultGrayPhone 



           String no 1 was stored with recordXD = 1 

           String no 2 was stored with recordID - 2 

           String no 3 was stored with recordID * 3 

           String no 4 was stored with recordID • 4 

           String no 5 was stored with recordID = 5 

           RecordID    • 5 : String no 5 

           RecordID = 4 : String no 4 

           RecordID • 3 : String no 3 

           RecordID • 2 : String no 2 

           RecordID • 1 : String no I 

           Record : String no 1 

           Record : String no 2 

           Record : String no 3 

           Record : String no 4 

           Record : String no S 



           —I                    J 



                            Рис.   7.2 . Пример операций с записями хранилища 


----------------------- Page 77-----------------------

7 8        Глава 7 . Сохранение данных   и параметров приложения 



Рассмотрим методы  нумератора, использованные в примере: 

•    byte[ ] nextRecord ()  — возвращает копию следующей записи в списке. Любые дей- 

   ствия  с полученным  массивом не повлияют на запись, содержащуюся   в храни- 

   лище. После вызова функции происходит продвижение по списку, то есть сле- 

   дующий   вызов вернет уже  другую запись. Первый вызов функции возвращает 

   первую в списке запись; 



•    byte[] previousRecordO   — возвращает копию предыдущей записи в списке. Пер- 

   вый вызов функции возвращает  последнюю  в списке запись; 

•    int  nextRecordldC)   — возвращает номер следующей  записи в списке по тому  же 

    принципу, что  и метод  nextRecord; 

•    int  previousRecordldC)  — возвращает   номер предыдущей   записи в списке; 

•    boolean   hasNextElementO   — возвращает   значение  true,  если   список   содержит 

    следующую запись, то есть возможно продвижение с использованием nextRecord; 

•    boolean   hasPreviousElementO   — возвращает   значение  true,  если   список   содер- 

    жит   предыдущую   запись, то   есть   возможно   продвижение   с  использованием 

    previousRecord; 

•    i nt numRecords ()  — получить  количество записей, содержащихся   в списке; 

 •   void reset О   — возвращает  нумератор   в исходное положение; 

 •   void rebui ld()   — перестроить список после того, как записи были удалены   или 

    добавлены  в хранилище; 

 •   void destroy()  — освобождает   все ресурсы, задействованные   нумератором. 



 RecordFilter и RecordComparator — 

фильтр и   компаратор 



Вернемся к вышеприведенному примеру. При получении списка записей в мето- 

де enumerateRecords в качестве параметров RecordFi Her и RecordComparator мы пере- 

дали   null,  тем   самым   получив   список всех  без   исключения записей  хранилища. 

 На самом деле, можно отделить  зерна на этапе получения списка и выбрать  толь- 

ко необходимые записи в определенном порядке. Для достижения этих целей слу- 

жат два  интерфейса RMS — фильтр и компаратор. 

 Начнем  с фильтра .  Чтобы  выбрать   из хранилища  только  интересующие  нас за- 

 писи,  необходимо   реализовать   интерфейс RecordFilter   и   переписать   его   метод 

boolean  matches (byte[] candidate), который должен   проверить запись на  соответ- 

 ствие  установленному   нами критерию  и вернуть  true  в положительном   случае. 

 Компаратор   — интерфейс сравнения записей, позволяющий отсортировать  спи- 

 сок в заданном нами порядке. Для этих целей необходимо реализовать интерфейс 

 RecordComparator и переписать его метод int compare (byte[] reel .  byte[] rec2), кото- 

рый возвращает константы: 



 •    RecordComparator.PRECEDES — если   первая   запись   (reel)  предшествует    второй 

    записи  (гес2)  согласно  нашей логике; 

 •    RecordComparator. FOLLOWS — если  первая запись следует за  второй  согласно   ус- 

    тановленному  нами критерию; 


----------------------- Page 78-----------------------

                            RecordFilter и RecordComparator — фильтр и компаратор                         7 9 



•     RecordComparator. EQUIVALENT — если с точки зрения упорядочивания записи равны. 

Объекты классов фильтра и компаратора передаются   в качестве  аргументов   в ме- 

тод   хранилища   записей   enumerateRecords(RecordFilter   filter .                    RecordComparator 

compa rator . bool ean keepUpdated). Обратим внимание на третий аргумент. Разработчи- 

ки   утверждают, что если передать значение true, то список будет автоматически пе- 

рестраиваться во время работы с хранилищем после добавления или удаления запи- 

сей.   Предупреждение   гласит, что использование этой  опции может   существенно 

повлиять   на производительность  приложения. В приведенном примере для целей 

синхронизации списка и хранилища был использован метод   rebuildC). Эксперимен- 

ты с параметром можете провести сами. 



Для     примера   использования   фильтра   и компаратора  допустим,  что хранилище 

содержит       картотеку      с информацией о высоких   незамужних                  блондинках,  а пер- 

вый     байт   каждой   записи   представляет          их возраст.   Ставим   перед   собой   задачу: 

выбрать всех представительниц моложе тридцати лет в порядке возрастания. Для 

этого реализуем два класса, MyRecordFi Her и MyRecordComparator: 



 // класс фильтра 

private class MyRecordFi H e r      implements RecordFilter { 



public    boolean matches(byte [ ] candidate ) 

 { 

 // проверяем    первый байт    записи 

if(candidate[0]<30)       return true : else return false ; 



 // класс   компаратора 

private    class  MyRecordCompa rato r   implements RecordCornparator 



public    in t   compare(byte[]  reel .  byte[]  rec2) 



 { 

//   устанавливаем   порядок   записей по первому байту 

 if(recl[0]<rec2[0])      retur n   RecordComparator.PRECEDES: 

 if(recl[0]>rec2[0])      retur n   RecordComparator.FOLLOWS: 

 return   RecordComparator.EQUIVALENT: 



 // открыть    хранилище   записей с именем    "Blondes " 

RecordStore     recordStore - RecordStore.openRecordStoreC'Blondes" ,           true) ; 

 // создать    объект фильтра 

MyRecordFi H e r    filter = new MyRecordFi iterO ; 

 // создать объект      компаратора 

MyRecordComparator       comparator = new    MyRecordComparatorO ; 

 // получить список      записей  согласно   критерию в порядке     возрастания 

 RecordEnumeration re = recordStore.enumerateRecords 

                             (filter, comparator ,     false) : 


----------------------- Page 79-----------------------

8 0        Глава   7 . Сохранение  данных   и параметров приложения 



Таким образом, мы получили список интересующих   нас кандидатур, с которы- 

ми   теперь мы можем производить любые действия, как было показано в первом 

примере. 



 Record Listener — лови момент 



Для  контроля над хранилищем записей используют   блок Прослушивания запи- 

сей.   Применение блока прослушивания более актуально для организации много- 

поточных приложений, на которых мы остановимся более подробно в следующей 

главе. Смысл блока в том, чтобы отследить момент удаления, добавления или из- 

менения записи в хранилище. При возникновении одного из этих событий авто- 

матически вызовется соответствующая функция. 



Для   того   чтобы   создать   блок   прослушивания, нужно  реализовать   интерфейс 

RecordListener, в котором объявлены следующие  методы: 

•    recordAddecKRecordStore   recordStore,   int  recordld) — вызывается   сразу   после 

    добавления в хранилище recordStore новой записи с номером recordld; 

 •   recordChanged(RecordStore recordStore,  int   recordld) — вызывается сразу после 

    того, как запись с номером recordld была изменена; 

 •   recordDeleted(RecordStore  recordStore,   int  recordld)  — вызывается после того, 

    как  запись с номером recordld была удалена.  Функция вызывается после уда- 

    ления, поэтому не стоит пытаться получить в ней запись с номером recordld — 

    в этом случае будет сформировано исключение InvalidRecordlDException. 

 Процедура  взаимосвязи блока прослушивания и конкретного хранилища запи- 

сей осуществляется с помощью следующих методов хранилища: addRecord Li stener 

 (RecordListener listener),  removeRecordListener(RecordLi stener listener). Один блок 

прослушивания может обслуживать  одновременно несколько хранилищ. 



 RecordStoreException — 

 возможные проблемы 



 Остановимся на обработке исключительных ситуаций, которые могут возникнуть 

при   работе с системой управления записями. Все виды исключений RMS насле- 

дованы от одного класса RecordStoreException, поэтому в try-блоке  можно отсле- 

живать только объект этого класса, как и было сделано в примере. 

 Если же мы хотим получить более подробную информацию о возникшей ошибке 

 или  предпринять особые действия для устранения возникшей проблемы, то сле- 

дует добавить обработку следующих исключений: 

 •    Inval idRecordlDException — формируется в случае невозможности выполнения 

    операции из-за некорректного номера записи; 

 •   RecordStoreFul I Exception — операция не может быть завершена, так как храни- 

    лище переполнено, то есть памяти, выделенной конкретной моделью, оказа- 

    лось недостаточно; 


----------------------- Page 80-----------------------

                                                        High Score — таблица  рекордов                8 1 



•    RecordStoreNotFoundException — формируется при попытке открыть хранилище 

    с несуществующим именем; 

•    RecordStoreNotOpenException — возникает при  попытке работы   с неоткрытым 

    хранилищем. 

Таким образом, большинства из возможных проблем можно избежать, грамотно 

проектируя приложение. Следует внимательно следить за своевременным откры- 

тием и закрытием хранилища, актуальностью   номеров записей. Распространен- 

ная   ошибка — работа со списком записей, полученным до удаления или добавле- 

ния    некоторых записей, поэтому не забываем про метод rebui I d(). 



 High Score — таблица рекордов 



Вернемся к нашим змеям и попробуем усовершенствовать игру, применив выше- 

изложенное на практике. В долговременном  хранилище записей будем хранить 

запись, состоящую   из двух  байт, первый  из которых   будет содержать   текущий 

рекорд, а второй  — уровень игры, от которого будет зависеть скорость передвиже- 

ния    змейки. Для реализации наших идей нам потребуется импортировать пакеты 

поддержки хранилища данных, а также завести новые поля в классе Snake: 

import    javax.microedition.rms.RecordStore ; 

import    javax.microedition.rms.RecordEnumeration : 

 i mport  javax.microediti on.rms.RecordStoreExcepti on ; 



private RecordStore      recordStore ;    // хранилище   данных 

private    int  recordID ;                // ID записи   параметров 

private byte     level ;                  // уровень сложности    игры 

private byte speed :                      // коэффициент   вычисления   задержки  треда 

private byte highScore :                  // текущий   рекорд 

 В конструкторе класса Snake добавим считывание параметров из хранилища дан- 

ных или создание новой записи для первого запуска игры: 

 //  параметры   для долговременного    хранения 

 //  первый байт - текущий     рекорд 

 //  второй  байт - уровень сложности     игры 

 byte buff[ ] = {0.5} ; 

 try { 

     //   открыть хранилище записей с именем      "SNAKE " 

     recordStore = RecordStore.openRecordStore("SNAKE" ,         true) ; 

     //   получить список   записей   хранилища 

     RecordEnumeration     re = recordStore.enumerateRecordstnull ,      null ,  false) : 

      //  если  хранилище  пусто 

      if  (re.numRecords()==0 ) 

          //  добавить   новую  запись параметров    игры . 

          recordID = recordStore.addRecord(buff .       0 . 2 ) ; 

     else 

          //  получить   id записи  параметров игры 


----------------------- Page 81-----------------------

8 2            Глава   7 . Сохранение   данных          и параметров  приложения 



           recordID     =  re.nextRecprdldO : 

     //   считать    запись   параметров     игры 

      buff   =  recordStore.getRecord(recordID) ; 

     //   первый    байт   -  текущий   рекорд 



      highScore     =  b u f f [ 0 ] : 

     //    второй   байт   -  уровень   сложности   игры 

      level   =  b u f f [ l ] ; 



     //   установить     коэффициент     вычисления     задержки 

     speed   -   100; 



 }  catch(RecordStoreException          rse ) { 

 } 

Теперь при завершении игры будем сравнивать набранный результат с текущим 

рекордом. Сообщение о новом рекорде будем выводить красным цветом, а резуль- 

тат   записывать в хранилище данных (рис.   7.3). 

Добавим эти  изменения                в метод paint О: 



 // если    поднят флаг     конца   игры 

 if ( gameOverFlag ) { 

      // вывести     сообщение     конца   игры 

      g.drawStringC'GAME        OVER".width/2.height/2-10.g.HCENTERfg.TOP) ; 

      // строка     сообщения    счета   игры 

      String    scoreStr ; 

      // массив     параметров для долговременного           хранения 

      byte buff[ ] - new byte[2] ; 

      // если    текущий    рекорд   меньше нового      результата 

      if(highScore < snakeLen ) {                                                                           • • 

           highScore = (byte)snakeLen : 

            // инициализировать        массив с новыми      параметрами 

           biiffTO] - highScore ; 

           buff[l ] = level : 

           try { 

                 // записать      новый  рекорд в хранилище 

                 recordStore.setRecordt recordID.buff.0.2) : 

            }  catch(RecordStoreException          rse ) { 

            } 

            // установить      цвет текста     красным 

           g.setColor(OxffOOOO) : 

            // сформировать       строку с новым      рекордом 

           scoreStr - new StringC"HIGH            SCORE :  "+snakeLen) ; 

      }   else 

            // сформировать       строку со счетом 

           scoreStr - new StringC'YOUR            SCORE :  "+snakeLen) ; 

            // вывести     счет 

           g.drawString(scoreStr.width/2.height/2+10.g.HCENTER|g.TOP); 

 }   else 


----------------------- Page 82-----------------------

                                                             High Score — таблица рекордов                    83 



Готово,  можно  уже   запускать!   Правда,  играть                   # ( 1 )  Nokia 7210 S 

стало  гораздо   интереснее?   Теперь   можно  ста-                   Ffe   look 



                                                                                                           : . : • • : • 



вить новые рекорды. Именно этим параметром 

завлекают   многие и многие игрушки, как ком-                                    Г 

пьютерные, так  и мобильные.  Вспомните, как 

в детстве, раз за разом, вы запускали   игрушку, 

даже такую тупую, как «Ну, погоди!» или «Тай- 

ны океана», и старались   изо всех сил  вписать 

свое имя в таблицу славы, побив чужой рекорд. 

Самые  хитрые,  естественно,  ничего  не прохо- 

дили, а просто вскрывали программу   и припи- 

сывали себе любые результаты.  Как это делает- 

ся, мы рассмотрим в одной из заключительных 

глав. 



Заметим,  что  для   хранения   текущего   рекорда 

у нас отведен всего один байт, то есть максималь- 

ный рекорд, который можно поставить, ограни- 

чивается   длиной змеи, равной  255   элементам. 

Хотя   с такой реализацией увеличения скорос- 

ти после каждого «съеденного» сердечка нам это 

не грозит. При изменении этой логики, возмож- 

но, следует добавить памяти для хранения теку- 

щего рекорда. При проектировании приложения 

всегда стоит задуматься, отвечает ли заведенный 

тип данных характеру хранимых данных. Как мы 

уже видели, тип byte может принимать значения 

 от 0 до 255. 

 В   данный   момент   мы   реализовали   хранение                        Рис. 7.3 . Сообщение о новом 

только одного рекорда, а хотелось  бы, конечно,                                        рекорде 

 иметь  таблицу   лучшей   десятки  с их именами. 

 Всему  свое  время: пока что  нам  не хватает  знаний о реализации  ввода   текста 

 и работе со строками, но и до этого мы обязательно доберемся. 



                                                      * * * 



 В этой главе мы достаточно подробно рассмотрели систему управления записями 

 RMS, которая представляет собой простую абстракцию базы данных (RecordStore), 

 связанную с записями. Записи не имеют определенного типа, а хранятся как мас- 

 сив байтов. Записи можно получать по уникальному идентификатору записи (ID), 

 если он известен, или же получить  список всех записей. Фильтр записей (Record 

 Fi Her) реализует механизм запросов, с помощью которого можно выбирать толь- 

 ко соответствующие             каким-то критериям  записи. Компаратор записей (Record 

 Comparator) определяет порядок извлечения записей из списка и реализует сорти- 

 ровку записей. 

 Стоит   отметить,  что   производительность   RMS даже  в современных   моделях 

 достаточно низка, поэтому стоит использовать RMS только в тех случаях, когда 


----------------------- Page 83-----------------------

84        Глава 7 . Сохранение данных и параметров приложения 



это действительно   необходимо.  Есть мнение, что  производительность   может 

увеличиваться,  если  не обновлять некоторые элементы, а переписать полно- 

стью все содержимое хранилища. 

Чтобы  оценить  производительность   написанной программы, лучше воспользо- 

ваться реальным аппаратом, поскольку эмулятор в этом случае может не отобра- 

зить реальной картины. Вот, собственно, и все. Мы еще вернемся к системе уп- 

равления записями в одном из следующих примеров. 


----------------------- Page 84-----------------------

 Глава 8 



Стандартные средства 

 пользовательского интерфейса 



Если вы смогли   оторваться   от игры, побив все мыслимые  рекорды   по откармли- 

ванию змей, то продолжим уроки мобильного программирования. Посмотрим кри- 

тическим   взглядом   на нашу   игру   и подумаем,  чего   в ней еще не хватает.  Уважа- 

ющий  себя   театр   должен   начинаться   с гардероба,   а приложение для мобильного 

телефона,   как и  ресторан,   начинается      с меню.   Меню   мы изобретать      не будем, 

а воспользуемся      готовым   пользовательским   интерфейсом  высокого   уровня. 



 Класс List 



Простейший   вариант   организации   меню — использование   готового             интерфейса 

высокого уровня, предоставляющего   все необходимые   функции навигации и вы- 

бора одного из пунктов. Меню представлено классом Li st , который наследован от 

класса   Screen, то есть   является   объектом,  готовым   к демонстрации   через   менед- 

жер дисплея.  Создается   объект   меню с помощью  одного из конструкторов: 

ListCString   title ,  int  listType) 

или 



List(String   t i t l e ,  int  listType,  String[]  stringElements, 

      Image[]   imageElements) 



Первый конструктор   создает   пустой  объект, в который можно будет добавить не- 

обходимые      пункты,   второй   же  принимает     пункты   меню   в   виде   массива  строк 

stringElements.   Параметр   imageElements   определяет   массив  изображений,  которые 

можно   поставить   в соответствие      каждому    пункту   меню.  Массив     stringElements, 

как и каждый   его элемент, не должн  быть  пустым. Длина массива  определяет ко- 

личество  пунктов  меню. Массив  imageElements   может   иметь  значение nul 1 или со- 

держать   такое же количество   элементов, как и массив         stringElements. 



Параметр   t i t l e   задает   заголовок   меню, а 11 stType определяет   один из трех   типов 

меню, представленных   следующими   константами класса  Li st : 



 •    IMPLICIT — каждое перемещение по пунктам меню вызывает немедленное опове- 

    щение через блок прослушивания команд. Класс Li st содержит   особую   команду 

    Command SELECT_COMMAND, которая формируется   при выборе  нового пункта и пере- 

    дается в качестве аргумента в метод блока прослушивания commandActi on (Command, 

    Displayable); 


----------------------- Page 85-----------------------

8 6         Глава 8 . Стандартные средства  пользовательского интерфейса 



•    EXCLUSIVE — позволяет   выбор  единственного  пункта меню; оповещения прило- 

    жения   не   генерируются; 



•    MULTIPLE— позволяет выбор нескольких пунктов меню в любом  сочетании; опо- 

    вещения  не   генерируются. 



Каждый  из пунктов меню может  находиться   в двух состояниях   — выбранный или 

невыбранный. В режимах   работы   IMPLICIT и EXCLUSIVE выбранным может быть толь- 

ко один пункт   меню, в режиме  MULTIPLE выбранными могут быть  несколько пунк- 

тов  меню  одновременно. 



Класс List  расширяет   класс  Screen   и реализует      интерфейс Choice. Все  его   методы 

определяются   реализуемым         интерфейсом. 



Interface Choice 



Интерфейс   Choice   определяет       набор   методов   для   элементов    пользовательского 

интерфейса верхнего   уровня, предоставляющих   возможность  выбора  из несколь- 

ких   предложенных       вариантов   (класс   List   и класс  ChoiceGroup). Каждый   из   вари- 

антов представляется   текстовой строкой и необязательным  графическим   изобра- 

жением   — пиктограммой. 



Для   организации  работы   с меню   интерфейс  Choice определяет   следующие             мето- 

ды, реализованные  в классе   Li st : 



 •    int  size()   — возвращает   количество   пунктов   меню.  Все   пункты   меню   индек- 

    сируются   целыми числами  от 0 до size(  )-l; 



 •    int  appencKString  stringPart ,  Image   imagePart)   — добавляет   новый пункт меню, 

    представленный        строкой   stringPart   и   картинкой   imagePart,   в   конец   списка 

    и возвращает   присвоенный элементу   индекс; 



 •    void  insert(int   elementNum,   String  stringPart,   Image   imagePart)   — аналогично 

    предыдущему       методу, вставляет   новый  пункт   меню в позицию списка, задан- 

    ную   параметром  elementNum; 



 •    void deletednt    elementNum)  — удаляет  пункт  меню с индексом elementNum; 



 •    void   set (int  elementNum,   String  stringPart,  Image   imagePart)   — изменяет  пункт 

    меню с индексом el ementNumв соответствии  с параметрами stri ngPart и imagePart; 



 •    String  getStringdnt    elementNum) — возвращает  строку, представляющую            пункт 

    меню  с индексом elementNum; 



 •    Image  getlmage(int    elementNum) — возвращает      картинку,  связанную   с   пунктом 

    меню, имеющим   индекс elementNum; 



 •    boolean  i sSel ected( int  elementNum) —возвращает значение true, если пункт меню 

    с индексом elementNum является   выделенным; 



 •    int  getSe1ectedIndex()    — возвращает   индекс  выделенного   пункта   меню.  В   ре- 

    жиме  MULTIPLE всегда   возвращает   значение - 1 ; 



 •    void  setSelectedlndexUnt      elementNum,    boolean   selected)  — в   режиме MULTIPLE 

    устанавливает      пункт   меню   с   индексом   elementNum в   состояние,    соответству- 

    ющее параметру   sel ected. В остальных  режимах  параметр sel ected должен  иметь 

    значение  true,  при  этом   соответствующий        пункт   меню   переходит    в   выделен- 


----------------------- Page 86-----------------------

                                                                    Класс SnakeGame            8 7 



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

    в  невыделенное  состояние. В режиме   IMPLICIT никаких неявных   вызовов  не 

    происходит; 

•    int   getSeiectedFlags(boolean[]        selectedArray_return)     — в  параметр   selected 

    Array_return    возвращается   массив  флагов, соответствующих              пунктам  меню. 

    Элемент массива содержит значение true, если выделен соответствующий пункт 

    меню. В режиме MULTIPLE значение true может содержать  произвольное число 

    элементов массива, в остальных  режимах  — только один элемент. Сам же ме- 

    тод возвращает количество выделенных элементов; 



•    void   setSel ectedFl ags (bool ean[]  selectedArray)  — устанавливает   пункты меню 

    в  выделенное   состояние   в   соответствии   с   массивом   флагов         selectedArray. 

    В режимах EXCLUSIVE и IMPLICIT только один элемент массива sel ectedArray  дол- 

    жен иметь значение true. Если таких элементов нет, то выделенным становит- 

    ся   первый пункт меню. Если элементов больше, чем один, то первый соответ- 

    ствующий пункт принимает выделенное состояние, остальные игнорируются. 



 Класс SnakeGame 



Добавим в игру   стартовое  меню, которое будет иметь несколько команд: начать 

новую игру, установить  уровень  сложности игры и посмотреть текущий рекорд. 

При    запуске  игры  сначала  будем демонстрировать   меню, а по окончании игры 

возвращаться туда же, а не зависать намертво, как мы делали до этого. Как мы уже 

выяснили, для   воплощения наших идей нам потребуется   создать  объект   класса 

List, который будет представлять  стартовое меню игры, а также реализовать ин- 

терфейс CommandListener для прослушивания команд. 

Для начала нам потребуется  подключить несколько пакетов, реализующих необ- 

ходимые классы; 



import javax.microedition.lcdui.CommandListener: 

import   javax.microedition.lcdui.Command: 

import   javax.microedition.lcdui.Displayable: . 

import javax.microedition.lcdui.List : 



Интерфейс CommandListener мы реализуем   в основном классе мидлета SnakeGame. 

Его декларация теперь будет выглядеть следующим образом: 

public   class  SnakeGame   extends   MIDlet  implements   CommandListener 



Также   нам  потребуются   два   новых   члена  класса, представляющих   собственно 

объект меню, а также команда для выбора пункта меню: 



private   List  menu;         //  стартовое   меню 

private   Command   ok;        //  команда  выбора  пункта   меню 



Дальнейшие изменения произведем  в методе  startAppO,  из которого уберем со- 

здание объекта змейки и старт  нового треда, а добавим создание меню и отобра- 

жение его на экране. Теперь стартовый метод мидлета будет выглядеть так: 

public void startAppO { 

     // массив строк с названиями    пунктов  меню 

     String menuOptions[ ] - {"New   Game","Set Level "."High  Score"} : 


----------------------- Page 87-----------------------

88          Глава 8 . Стандартные средства  пользовательского интерфейса 



     // создать   новое  меню 

    menu = new    ListC",List.IMPLICIT.menuOptions.null) ; 

     // создать   команду   выбора  пункта  меню 

     ok = new Command("Ok" .    Command.OK . 1) ; 

     // добавить    команду в меню 

     menu.addCommand(ok) ; 

     // установить    блок  прослушивания   команд  для  меню 

     menu.setCommandListener(this) ; 

     // получить    ссылку на менеджер   дисплея 

     display = Display.getDisplay(this) : 

     // отобразить    меню на экране 



     display.setCurrent(menu): 



Чтобы откомпилировать и посмотреть, что у нас получилось, нужно   реализовать 

метод   commandAction, представляющий   блок   прослушивания   команд. Этот                      метод 

и   будет предписывать те или иные действия в зависимости от выбранного пункта 

меню. Пока что  можно оставить его пустым, откомпилировать приложение и по- 

                                           смотреть, как выглядит  меню на экране телефо- 

 # ( 1 )  Nokia 7210 SWC 

                                           на  (рис. 8.1). 

  File  Tools  Help 



                                           Итак, мы видим на экране аппарата строки, пред- 

                                           ставляющие пункты меню, заданные нами в мас- 

                                           сиве menuOptions. Один из пунктов подсвечен си- 

                                           ней   полоской. Одна из функциональных клавиш 

                                           связана с командой Ok, при нажатии на которую, 

                                           правда, пока что ничего не происходит. 

                                           Перед тем  как продвигаться   дальше,  стоит   тща- 

                                           тельно обдумать структуру приложения. Это от- 

                                           носится   к любому       программированию   вообще, 

                                           поскольку просчеты в проектировании програм- 

                                           мы   могут привести к тому, что придется начинать 

                                           с  чистого  листа   и все  переписывать заново. Так 

                                           что, как говорится, лучше час  потерять, а потом 

                                           за пять минут   долететь. 



                                           Обратить внимание следует на логику  смены эк- 

                                           ранов.  Если для   начала   новой   игры   нам  просто 

                                           потребуется   создать объект змейки и отобразить 

                                           его на экране, то для демонстрации текущего  ре- 

                                           корда и установки  нового уровня сложности по- 

                                           требуется  новый отображаемый объект, который 

                                           будет нести необходимую   информацию, а также 

                                           обладающий возможностью возврата к стартово- 

                                           му   меню программы. В  качестве   отображаемого 

                                           объекта  выберем уже  знакомую нам форму. Для 

                                           удобства   напишем новый метод,  который  будет 

   Рис.  8 . 1 . Теперь  игра   начинается создавать   форму,  принимая необходимые   пара- 

                 с  меню                   метры, и отображать   ее на экране. 


----------------------- Page 88-----------------------

                                                                           Класс SnakeGame                8 9 



private    void  showNewScreen(String   t i t l e .  Command   c.  Item   item)  { 



     //  создать форму для нового экрана 

     Form newScreenForm = new Form(title) : 

     //  добавить команду     для возврата    из формы 

     newScreenForm.addCommand(c); 

     //  добавить элемент в форму 

     newScreenForm.append(item); 

     // установить блок прослушивания         команд 

     newScreenForm.setCommandLi stener(this) ; 

     //  отобразить форму на экране 

     display.setCurrentCnewScreenForm); 

 } 

Обратим внимание, что и стартовое меню программы, и новая форма  пользуются 

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

се мидлета SnakeGame. 

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

находящимися   в хранилище  данных,  поэтому   напишем еще один  метод, кото- 

рый будет читать  необходимую   запись хранилища  и возвращать   нужный  пара- 

метр: 

private byte getParameter(int        index ) { 

     //   массив параметров игры 

     byte buff[ ] = {0.0} : 

     try { 

          //  открыть хранилище записей с именем         "SNAKE" 

          RecordStore recordStore = RecordStore.openRecordStore 

                                          ("SNAKE", true) ; 

          //   получить список записей хранилища 

          RecordEnumeration      re = recordStore.enumerateRecords 

                                      (null. null . false) ; 

          //   если хранилище не пусто 

          if   (re.numRecordsO !=0) { 

               //  получить id и считать запись параметров игры 

               buff • recordStore.getRecord(re.nextRecordldO) : 

           } 

      }  catch(RecordStoreException       rse ) { 

      } 

      //  вернуть параметр 

     return buff[index] ; 

 } 

 Перейдем  к блоку   прослушивания   команд, который, собственно, и будет дири- 

 жировать   нашей программой   и предписывать   действия   в зависимости   от  выб- 

 ранного  пункта  меню. В обязательном   к реализации   методе   commandAction   есть 

 два  входных   параметра,  которые  определяют                  вызванную   команду   и   отобража- 

 емый экран, из которого была вызвана команда. В зависимости от этих парамет- 


----------------------- Page 89-----------------------

9 0            Глава   8 . Стандартные   средства  пользовательского   интерфейса 



ров  мы и будем определять необходимые действия. В первую очередь обработа- 

ем команду Ok: 



public     void  commandAction(Com(nand        c.  Displayable     d)  { 

      //  если   была   команда    Ok   из  главного   меню  игры 

      i f  (с  ==   ok  &&   d   ==   menu)  { 

           //   получить     выбранный    пункт   меню   • 

           int   sel Index    =   menu.getSelectedlndexO  ; 

           switch(se1Index)        { 

                 //   первый   пункт   меню  -   новая   игра 

                 case   0  : 



                      //   создать    обьект   змейки 

                      curSnake     =   new  SnakeO; 

                      //   создать    обьект    треда   автоматического      передвижения 

                      Thread     moveThread    =  new   Thread(curSnake): 

                      //   начать    выполнение    треда 

                      moveThread.start();                                                                 ч 



                      //   отобразить     змейку    на  экране 



                      display.setCurrent(curSnake); 

                      break: 

                 // второй     пункт   меню - установить уровень           сложности 

                 case 1 : 

                       // массив     строк с названиями       пунктов    меню 

                      String     levelOptions[ ] = {"Level 1","Level 2" . 

                                                        "Level    3"."Level 4"} : 

                       // создать     меню   выбора уровня      игры 

                       levelChoice = 

                            new ChoiceGroupt"".List.EXCLUSIVE,1evelOptions.nul1) : 

                       // выделить      пункт   меню 

                       // соответствующий        текущему уровню       сложности 

                       levelChoice.setSelectedIndex(5-getParameter(l).true) : 

                       // отобразить       новую  форму 

                                                 ( 



                       showNewScreenC'Set        Level".set.level Choice) : 

                      break ; 

                 // третий      пункт меню - посмотреть         текущий    рекорд 

                 case 2 : 

                       // получить      строку с текущим       рекордом 

                      String strScore = (new Integer(getParameter(0))).toString() ; 

                       // создать     объект    элемента-строки 

                       Stringltem     highScoreltem = new StringltemC " .strScore) ; 

                       // отобразить      новую    форму 

                       showNewScreenC'HIGH SCORE".ok,highScoreltem) : 

                      break : 



       // если    была   команда Ok . вызванная из формы 

      if(c==ok && d!=menu ) 


----------------------- Page 90-----------------------

                                                                         Класс SnakeGame             9 1 



          //  отобразить на экране    стартовое   меню          # < 1 )  Nokia 721О51Ж 

          display.setCurrent(menu) : 



Итак,   как   мы   видим      из   кода,  меню    установки 

уровня   сложности   представлено   объектом   клас- 

са ChoiceGroup, который принадлежит  к иерархии 

класса   Item, поэтому   он может   быть  отображен   с 

помощью формы. На этом примере мы  наглядно 

видим   использование   полиморфизма   объектно- 

ориентированного языка: метод showNewScreen при- 

нимает в качестве одного и того же аргумента раз- 

личные объекты  классов Choi ceGroup и Stri ngltem, 

порожденных   одним классом  Item. 



Из    собственного      опыта   у   меня   сформировался 

следующий         подход    к   программированию :   при 

добавлении любой новой функциональности сто- 

ит запустить   программу   и убедиться,  что  все ра- 

ботает     корректно. Такой   подход        позволяет   вов- 

ремя отследить допущенные просчеты  и ошибки. 

 Именно поэтому   рекомендую   не пытаться напи- 

сать сразу   всю программу   целиком, чтобы   потом 

ломать   голову,   где   же   в этой  куче   кода  была  до- 

 пущена ошибка, а продвигаться   от главы   к  главе 

 постепенно. Это  не детектив, чтобы            заглядывать 

 в  конец книжки: кто же убийца?  Полный листинг 

 программы   в конце приведен   не для           бездумного 

 переписывания,  а для   дополнительного   контро- 

ля   и помощи в поиске ошибок. 

                                                                 Рис.   8.2 . Демонстрация   наилучшего 

 Для    того   чтобы    откомпилировать         и  запустить                   результата 

 программу   на данном этапе, требуется             несколь- 

 ко  дополнительных   штрихов. Первое:  требуется 

 импортировать   необходимые   пакеты, классы   которых   мы будем   использовать: 



 import   javax.microedition.lcdui.Form; 

 import   javax.microedition.lcdui.Item; 

 import   javax.microedition.lcdui.ChoiceGroup; 

 import   javax.microedition.lcdui.Stringltem; 



 Второе: в классе  SnakeGame мы завели два  новых   поля для   реализации меню   выбо- 

 ра уровня   игры: 



 private   ChoiceGroup TevelChoice:         //  меню выбора  уровня   сложности игры 



 private   Command set :                    //  команда   выбора  уровня   сложности игры 



 В   стартово м методе мидлет а startAppC ) создади м  объект к о м а н д ы выбор а у р о в н я 



 с л о ж н о с т и  и г р ы : 



 set   =   new  Command("Set".  Command.BACK,  1) ; 



 Вот   теперь   можно   запустить        игру.   Обратите     внимание, что   обработка         команды 

 set   в   блоке  прослушивания   команд  еще  не реализована,  поэтому   пока  что                 удов- 

 летворимся   просмотром   текущего   рекорда   (рис.  8.2). 


----------------------- Page 91-----------------------

9 2           Глава  8 . Стандартные средства  пользовательского интерфейса 



При    нажатии функциональной клавиши формируется команда Ok, и приложение 

снова демонстрирует   на экране стартовое   меню. Выглядит   это все, конечно, не 

очень презентабельно, что обусловлено  ограниченностью возможностей формы. 

Например, наложить текст на картинку мы не сможем при всем желании. Так что 

в   качестве   самостоятельной   работы   можете   попробовать   использовать   вместо 

формы класс, порожденный от класса Canvas. В методе pai nt можно будет нарисо- 

вать все, что душе угодно, а возврат в основное меню отработать с помощью мето- 

да keyPressed. 

Однако не будем отвлекаться   и допишем в блоке прослушивания команд обра- 

ботку   команды set,  где  получим   выбранный уровень   сложности  и запишем его 

в  хранилище записей: 



if(с   —  set)  { 

     try   { 

          //   массив  параметров    игры 

          byte buff[ ] - {0.5} : 

          //   открыть хранилище записей с именем        "SNAKE " 

          RecordStore     recordStore = RecordStore.openRecordStore 

                                           ("SNAKE", true) ; 

          //   получить список    записей   хранилища 

          RecordEnumeration      re - recordStore.enumerateRecords 

                                       (null. null . false) ; 

          //   если  хранилище не пусто 

          if (re.numRecords()!=0 ) { 

               //   получить id записи     параметров   игры 

               int id - re.nextRecordldO ; 

               //   считать запись с параметрами       игры 

               buff = recordStore.getRecord(id) ; 

               //   вычислить текущий уровень игры 

               buff[l ] - (byte)(5 - levelChoice.getSelectedlndexO) ; 

               //   записать параметры     игры 

               recordStore . setRecorddd , buff . 0.2) : 

           } else { 

                //  добавить новую запись параметров        игры 

                recordStore.addRecord(buff .      0 . 2 ) : 

           } 

      }  catch(RecordStoreException       rse ) {                                                 . 

      } 

      //  отобразить на экране стартовое меню 

      display.setCurrent(menu); 

 } 

 Теперь все пункты стартового  меню полностью соответствуют   заявленным дей- 

 ствиям. Попробуем, как работает меню выбора уровня сложности игры (рис. 8.3). 

 В данном случае мы использовали режим EXCLUSIVE, поэтому меню отличается от 

 стартового внешним видом. Команда Select  появилась автоматически и отвечает 

 за выделение одного из пунктов меню точкой, так как перемещение подсвеченной 


----------------------- Page 92-----------------------

                                                                       Класс   Gauge       93 



синей полоски по пунктам не приводит к выде-              Ф(1)   Nokia 7Z1»! 

лению одного  из них. Реализованная нами ко-               File  Tools  (Help 

манда Set вызывает запись нового уровня слож- 

ности, который учитывается  при запуске новой 

игры. Теперь   каждый   может   задать   свой соб- 

ственный темп, что только добавит   увлекатель- 

ности нашей игре. 



 Класс Gauge                                                       о   Level 2 

                                                                   о   Level 3 

Благодаря   гибкой реализации   и полиморфиз-                      ® Level 4 

му, можно легко поменять реализацию выбора 

сложности игры, используя, например, не меню 

выбора  ChoiceGroup, а шкалу  Gauge. Шкала так- 

же  принадлежит   иерархии   класса   Item, поэто- 

му метод  showNewScreen  изменять не потребует- 

ся.  Внешний вид   шкалы, как и всех   объектов 

пользовательского интерфейса высокого уров- 

ня,  зависит   от   конкретной  модели   телефона. 

Например,  в стандартном   цветном   эмуляторе 

шкала будет выглядеть  так (рис. 8.4). 

Здесь мы видим привычные глазу прямоуголь- 

нички, меняющие цвет в зависимости от выбо- 

ра. У разработчиков корпорации Nokia свое ви- 

дение шкалы, поэтому то же самое на эмуляторе 

Nokia 7210 будет выглядеть следующим образом 

 (рис. 8.5). 

 Изменить внешний вид этого элемента у нас нет               Рис. 8.З . Меню выбора  уровня 

                                                                       сложности 

 никакой   возможности,  поэтому   придется   до- 

 вольствоваться   имеющимися методами: 

 •    Gauge(String    label,   boolean   interactive,   int  maxValue,  int   initialValue) — 

    конструктор класса Gauge. Создает шкалу с заголовком 1 abel            и возможным  диа- 

    пазоном значений от 0 до maxVal ue. Значение по умолчанию выставляется рав- 

    ным параметру 1 initial Val ue. Если параметр i nteracti ve имеет значение true, то 

    значение шкалы можно изменять, иначе, как в стрип-клубе: смотреть — мож- 

    но, трогать — нельзя; 

    int   getMaxVal ue() — возвращает максимально возможное значение шкалы; 

    int   getVal ue() — возвращает текущее значение шкалы; 

    boolean    isInteractiveO   — возвращает значение true, если пользователь  может 

    изменять значение шкалы; 

    void   setLabel  (String  label) — устанавливает   новый заголовок шкалы; 

    void    setMaxValuednt     maxValue) — устанавливает   новое максимально возмож- 

     ное значение шкалы; 

    void    setValueCint    value) — возвращает   выбранное  пользователем   значение 

     шкалы. 


----------------------- Page 93-----------------------

94           Глава  8 . Стандартные средства  пользовательского интерфейса 



                                                           # ( 1 )  Nokia 7210 SDK 



                                                           •File  "tools  fcjelp 



            Рис.   8.4 .  Шкала в стандартном                   Рис.   8.5 .  Шкала на эмуляторе 

                         эмуляторе                                          Nokia 7210 



 Последний штрих  для  полного счастья   — заставка, которая будет выводиться пе- 

ред запуском   игры. Сохраним   картинку  для   заставки в файле title.png  и добавим 

 в  стартовом   методе   приложения   startAppC)   следующий   код: 



 Image title = null ; 

 try { 

      // создать     объект   заставки 

      title - Image.createlmageC'/title.phg") ; 

 }  catch   (IOException     ioe ) { } 

 // создать     новый  элемент    формы 

 Imageltem    item - new     ImageltemC " .title . Imageltem.LAYOUT_CENTER,"") ; 

 // отобразить      заставку 

 showNewScreent"SNAKE",ok,item); 



 Для   отображения  заставки мы воспользовались   все тем же универсальным   мето- 

дом showNewScreen, который в качестве аргумента  принимает объект класса Imageltem, 

 содержащий   картинку  (рис.  8.6). 

 Вот  теперь  точно  — все! 


----------------------- Page 94-----------------------

                                                                        Класс Gauge          95 



                          *  * *                           # ( 1 )  Nokia 7218 SMC 

В   этой  главе   мы  разобрались   с организацией 

меню с помощью пользовательского интерфей- 

са высокого уровня.  На этом наше знакомство 

с компонентами интерфейса высокого уровня не 

заканчивается. Мы еще вернемся к некоторым 

из   них   в других   примерах.  Лучше всего  такие 

вещи усваиваются   на практике: почитал — сра- 

зу реализовал — и почувствовал   силу в руках. 

Основной принцип высокоуровнего интерфей- 

са   в   том,  что   все   объекты  иерархии      класса 

Displayabie  можно  отобразить   на экране теле- 

фона, а все объекты иерархии класса  Item мож- 

но  включить   в   отображаемую         форму.  Всегда 

требуется     следить   за   переходами   по  пунктам 

меню: доступен ли необходимый отображаемый 

объект в конкретный момент времени. В нашем 

случае  основной класс  приложения   содержит 

стартовое   меню  в качестве   поля, а форма вто- 

рого уровня   создается   каждый   раз заново при 

вызове соответствующего   пункта меню с необ- 

ходимыми        параметрами.   Немного  практики, 

и у вас все  получится. 



                                                             Рис.  8.6 . Теперь у   игры есть даже 

                                                                         заставка 


----------------------- Page 95-----------------------

Глава 9 



Организация потоков, 

работа с файлами 



Надеюсь, что вы старательно выполняете самостоятельные работы и к моменту 

чтения этой главы уже смогли похвастаться  перед друзьями своими руками со- 

творенной   «Змейкой».  В этой  игре  мы выводили   на экран,  главным  образом, 

картинки, графику и прочую визуализацию, которую приложение считывало из 

файлов-ресурсов. Ресурсами в этом случае являлись картинки в определенном, 

воспринимаемом телефоном, формате. На самом же деле ресурсами   могут яв- 

ляться   не только  картинки. Любая   информация в самом  разнообразном  виде 

может храниться в файле-ресурсе. Каким образом информация из файла title.png 

превратилась в картинку на экране мобильника, осталось для нас за кадром: мы 

просто вызвали метод createlmage  и получили готовый объект  Image. В этой гла- 

ве мы рассмотрим подробнее, что такое потоки, как работать с файлами-ресур- 

сами и как организовать процедуру   чтения из файла. 

На этот раз только мы знаем, какого типа информацию содержит   ресурс, как ее 

следует обрабатывать, использовать или отображать. Черновую работу формиро- 

вания объекта из файла, как это было в случае с картинкой, выполнять теперь за 

нас некому. 



 Класс InputStream 



 Связь   между   источником данных   и приложением  осуществляется   с помощью 

 объекта класса InputStream, который можно сравнить с трубой, пропускающей че- 

рез себя поток данных. Класс InputStream является базовым для всех классов вво- 

да и предоставляет основные методы работы с потоком ввода: 

 •   int   read О — прочитать следующий байт из потока данных. Эта функция явля- 

    ется абстрактной, то есть в любом классе-потомке обязательна ее реализация; 

 •   int  read(byte[]   b) — считать  данные из потока в буфер. Функция читает   все 

    доступные данные, ограничиваясь размером буфера, и возвращает количество 

    прочитанных байт; 

 •   int  read(byte[]   b.  int  off,  int  len) — считать  данные длины   len  из потока 

    данных и разместить их в буфере, начиная с позиции   off; 

 •   int   avai I abi е() — возвращает количество байт, доступных для чтения в данном 

    потоке; 


----------------------- Page 96-----------------------

                                                                 Класс InputStream          9 7 



•    void mark(int  readiimit)   — помечает текущую позицию в потоке данных  с це- 

    лью   предоставления   возможности  повторного чтения с этой позиции. Аргу- 

    мент указывает, сколько байт может быть прочитано, прежде чем метка станет 

    недействительной; 

•    boolean markSupportedO  — проверка  возможности  повторного  чтения данных 

    с помеченной позиции. Возвращает   true, если  позиция была  задана  и число 

    прочитанных после этого данных не превысило лимита; 

•    void reset О — возвращает  поток данных  в состояние, сохраненное при послед- 

    нем   вызове функции mark; 

•    1 ong ski p( 1 ong n) — пропустить п байтов данных потока. Возвращает   реальное 

    количество пропущенных байт; 

•    void close()   — закрывает поток и освобождает  все используемые им ресурсы. 

Следует отметить, что в базовом классе InputStream такие функции, как avai I abl е, 

mark,  reset,   работать   не будут.  Они объявлены  для   последующей   реализации 

в   классах-потомках,  а пока что  не выполняют никаких действий  и возвращают 

нулевое значение. 

Рассмотрим использование класса InputStream на простом примере вывода текста 

из файла на экран телефона. Поместим текстовый файл pushkin.txt в папку ресур- 

сов  приложения /res.  Чтобы  связать   ресурс   с потоком, воспользуемся   методом 

getResourceAsStream(Stringname)        класса Class. Затем с помощью метода потока read 

считаем содержимое файла и поместим его на форму, связанную с экраном. При- 

мер кода будет выглядеть так: 



 import java.io.InputStream ;                              \ 

 import java.io.IOException ; 



Form form = new FormO : 

Display display = Display.getDisplay(this) : 

                      // связать файл с потоком is 

 InputStream is = getClass0.getResourceAsStreamC7pushkin.txt") : 

byte[ ] bArr - new byte[150] ; 

try { 

     is.read(bArr) :   // считать содержимое потока в массив байт 

 catchUOException e ) { } 

                      // сформировать строку из массива байт 

 String str = new String(bArr) ; 

 form.append(str);              // поместить строку в форму 

 display.setCurrent(form);      // отобразить форму на экране 



 Запускаем приложение в стандартном эмуляторе  из WTK   и получаем  вот такой 

 замечательный результат (рис. 9.1). 

 Эмулятор справился с заданием на твердую пятерку: навигацию нам реализовала 

 форма, стрелочки перематывают текст вниз и вверх, на экране не появляется ника- 


----------------------- Page 97-----------------------

 98          Глава  9 . Организация  потоков,  работа с файлами 



 ких лишних символов, связанных с тем, что размер запрошенного нами массива 

 (new byte[150]) превышает  объем текста. Красота! 

 Но, как показала практика, радоваться было рано. Запускаем наш пример на эму- 

 ляторе от Nokia 7210, что более близко соответствует                   запуску   на реальном  аппа- 

 рате, и видим печальную   картину (рис. 9.2). 



                                                       #(l)Nokia72lOSD»:               BlzLsfl 



                    1 вилась  ты , 

                    <ак  мимолетное 

                    i«денье, 

                    <ак  гений чисто !                           Ёаё aaiee тёпш ё 

                    ;расоты. 

                                                                ё б а Ш й Х Ш Ш ] ! 

                                                                 DDDnaaUDDDDQ 



       Рис. 9 . 1 . Вы знали, что  на телефоне         Рис. 9.2 . Осталось только научиться 

                можно читать  книги? 

                                                                    это читать... 



Русский текст превратился в шифровку агентов МОСАДа, а снизу прилипли квад- 

ратики неиспользованных  байтов массива. Отсюда делаем  вывод, что кодировка 

win 1251 не совпала с той, что запрограммирована в аппарате Nokia. Единственное 

правильное решение в данной ситуации — пользоваться универсальной кодиров- 

кой, которая подходила  бы ко всем моделям. Благо, такая кодировка существует. 

Ее название говорит само за себя — Unicode. 

Встает  вопрос:  как   теперь   перевести   текст   с привычной   глазу   кириллицы  на 

Unicode? Если текст находится  в коде программы и является, например, заголов- 

ком  формы   или  текстовым   сообщением,  то   можно   воспользоваться                       утилитой 

native2asci 1 из Java SDK, который мы ставили в самую первую   очередь. 

Вызывается эта утилита из командной строки следующим образом:.. \bin   \   native 

2ascii    -encoding    cpl251  fiTel. Java   > file2.java,  где параметр encoding   указывает 


----------------------- Page 98-----------------------

                                                                     Класс DatalnputStream                9 9 



исходную кодировку,  filel   — исходный  файл, f 11 е2 — конвертированный файл. 

Обратный перевод из Unicode на родной язык осуществляется   с использованием 

параметра   -reverse. 

Например, после преобразования исходная строка 



form.append("Здравствуй,       мир!") ; 



будет   выглядет ь так : 



form.append("\u0417\u0434\u0440\u0430\u0432\u0441\u0442\u0432\u0443\u0439. 

\u043c\u0438\u0440!11); 



Такой   вариант   действительно   решает   проблему                 корректного   отображения           рус- 

ского текста.  Однако в нашем случае текст находится   в файле-ресурсе   приложе- 

ния,    а значит  конверсия исходного   кода программы   нам Не поможет.   Предлага- 

емое решение основывается на поочередном преобразовании каждого символа при 

считывании его из потока. 



 Класс DatalnputStream 



На    этот раз мы воспользуемся классом Data I nputStream, который является наслед- 

ником класса InputStream и реализует чтение основных типов данных. То есть, кроме 

основного  метода  readO, этот   класс  содержит   методы   readBooleanO,                       readByteO, 

readCharC), reacilntC), readLong(), readShortC), readtlnsignedByte(),   readUnsignedShort(). 

Преобразование будем выполнять с помощью кодовой строки, которая   каждому 

символу   кириллицы ставит   в соответствие   его код в универсальной   кодировке. 

Таким образом, наша программа приобретает  следующий   вид: 



import    Java.io.DatalnputStream; 



Form  form  -   new FormC): 

Display    display  •   Display.getDisplay(this); 

 InputStream  is   = getClass0.getResourceAsStreamC7pushkin.txt"): 

DatalnputStream  dis   -   new   DatalnputStream(is): 

int   i  = 0; 

char[] bArr « new char[150] ; 

try {       // преобразовать     символы в Unicode и записать в массив 

     while(true) bArr[i++ ] - (char)convert(dis.readUnsignedByteO) : 

 } 

catchdOException   ioe)   {     }    •                                                 > 

String    st r   =   new   String(bArr.O.i-l) ; 

form.append(str) : 



 char  converttint    ch)   { 

      return   (ch  <  128)   ?  (char)ch  : WIN1251_T0_UNIC0DE.charA«ch-128): 



 } 

//   кодовая  строка   для  преобразования символа  из   winl25 1  в Unicode 



 private   String   WIN1251_TO_UNICODE     - 

 "\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\ 


----------------------- Page 99-----------------------

100          Глава 9 . Организация потоков, работа с файлами 



u045b\u045f\u00a0\u040e\u045e\u0408\u00a4\u0490\u00a6\u00a7\u0401\u00a9\u0404\u00ab\u00ac\ 

U00ad\u00ae\u0407\u00b0\u00bl\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\ 

U043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0439\u0447\ 

U0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f"; 



Символы читаются из потока по одному в виде байта без знака, а после преобра- 

зования заносятся в массив символов. Проблема лишних байт решена с помощью 

конструктора  класса  String, принимающего длину   формируемой строки. Длину 

мы получили при побайтном чтении потока. Запускаем приложение на эмулято- 

ре от Nokia и видим, что все наши проблемы решены (рис. 9.3). Шрифт, конечно, 

немного крупноват, но для начала сойдет. 



         i  • : . ; . ,  ••••.  ; 



                   Как  мимолетное 

                   виденье, 



                   Как  гений чистой 

                   красоты. 



        Рис.   9.3 . Демонстрация практической                  Рис.  9.4 . Так телефон признается 

                    пользы от Unicode                               в  своей  несостоятельности 



 Останавливаться на достигнутом  мы, конечно же, не собираемся и ставим задачу 

 написать удобное приложение для чтения небольших книжек. Очередная проблема 

 приведенных примеров заключается в том, что всю информацию ресурса мы разме- 


----------------------- Page 100-----------------------

                                                                      Класс BookReader            1 0 1 



щаем в памяти программы и захватываем  область памяти, равную по объему все- 

му содержимому   ресурса. Такой подход сводит на нет все преимущества потока, 

способного выдавать данные мелкими порциями по мере надобности. В качестве 

эксперимента помещаем в ресурс  небольшой рассказик объемом  20 килобайтов 

и пробуем, что из этого получится. Стандартный эмулятор  после   двухминутных 

раздумий  все же справился с задачей (поверьте на слово или проверьте, если хо- 

тите), а эмулятор от Nokia опять сошел с дистанции, выдав вот такое милое сооб- 

щение (рис. 9.4). 



 Класс BookReader 



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

Д ля   начал а   хотелось  бы уменьшить   шрифт , выводимы й   на экране . Смену             шрифт а 

поддерживает        класс   Graphics,  содержащий       метод   setFont(Font   font) .   В   примерах 

мы    пользовалис ь     объектом     класса   Form,  которы й    не поддерживает        прорисовку 

объектов     класса   Graphics . В нашем   случае  воспользуемся         отображаемым        классом 

Canvas,  которы й   м ы уж е рассматривали         в одной   из первых     глав . 

Остановимся        на классе   Font, который   и представляет        отображаемый       шрифт . По- 

лучить     новы й   шриф т   можн о   с помощью   метода      Font   getFont(int   face ,  in t  style . 

i nt  size) ,   параметры   которого   определяют ,   как будут   выглядеть        символы : 

 •    face   — вид шрифт а   определяется       константами      FACE_SYSTEM,  FACE_MONOSPACE   ил и 

    FAC E_PROPORTIONAL; 

 •    styl e   — стил ь  шрифт а   задается  константой   STYLE_PLAIN   ил и комбинацие й кон- 

    стант   STYLEJOLD, STYLEJTALIC        и STYLEJJNDERLINED; 

 •    siz e   — размер  шрифт а   задается    одной   из констант     SIZE_SMALL, SIZE_MEDIUM ил и 

    SIZE_LARGE. 

Если   в классе   Form форматировани е   текста   по размеру          экран а  выполнялос ь      авто- 

матически ,   то сейчас   вся ответственность          з а корректно е   позиционировани е тек- 

ста   переходит     к нам . Класс    Font   содержит   ряд методов     для определения       размер а 

текста   в  пискселах : 

 •    i n t  getHeight()  — возвращает      высоту   строки   шрифт а в   пикселах ; 

 •    i n t  charWidth(char  ch) — возвращает       ширину   символ а   ch в   пикселах ; 

 •    in t  charsWidth(char[]    ch . in t offset ,  in t  length)  — возвращает    ширину     набора 

    символов ,  содержащихся          в массив е   ch, начиная с позици и offset      длин ы   1 ength ; 

 •    i n t  stringWidth(String    s t r ) — возвращает   ширину   строки   s t r в   пикселах ; 

 •    i n t  substringWidthCString      s t r ,  i n t  offset ,  i n t  Ten) — возвращает    ш и р и н у 

     фрагмента      строк и   str , начинающегося       с п о з и ц и и   offset  и имеющег о  д л и н у 

     1еп. 



 В классе BookCanvas реализуем следующую логику отображения текста. В стеке 

 Page Index будем хранить смещение от начала текста для каждой страницы, что- 

 бы организовать  возможность обратной прокрутки текста.  Функция paint чи- 

 тает из потока данных по одному слову и отображает   его, если на экране доста- 

 точно пространства.  Клавиша 2 осуществляет   переход   к следующей   странице 


----------------------- Page 101-----------------------

1 02         Глава 9 . Организация  потоков,  работа с файлами 



текста, клавиша 1 — к предыдущей странице, а клавиша 0 возвращает к началу 

текста: 



 import   Java.io.InputStream : 

import java.io.EOFException ; 

import    java.io.IOException ; 

 import   java.util.Stack ; 



public class BookCanvas extends Canvas { 



private Stack     Pagelndex ; // стек смещений     страниц от начала    текста 



public BookCanvasO { 

 Pagelndex = new StackO : 



protected    void  paint(Graphics g ) { 

     //   получить ширину и высоту рабочей       области  экрана 

     int gw = g.getClipWidthO : 

     int gh = g.getClipHeightO : 



     //   создать шрифт 

     Font font = Font.getFont(Font.FACE_MONOSPACE.Font.STYLE_PLAIN.Font.SIZE_SMALL) : 

      // установить шрифт 

     g.setFont(font) : 



      //  инициализировать    поток 

      InputStream    is = getClass0.getResourceAsStreamC7story.txt") : 

     MyDataInputStream      mdis = new MyDatalnputStream(is) ; 



      int x=O.y=O :        // текущее положение    вывода на экране 

      int offset=0 :       // смещение по исходному     тексту 



      //  очистка   экрана 

     g.setColor(255.255.255) : 

      g.fillRect(O.O.gw.gh) : 

     g.setColor(O.O.O) : 



      //  получить смещение для очередной       страницы 

      if ( IPagelndex.emptyO ) 

          offset=( ( Integer)PageIndex.peekO ) .intValueO : 



      try{ 

          //   сместиться   по тексту   до необходимой   страницы 


----------------------- Page 102-----------------------

                                                                    Класс BookReader           1 0 3 



     mdi s.ski pBytes(offset) ; 

}  catchUOException      ioe ) { } 



String   sWord ; 

do { 

     // прочитать очередное слово из потока 

     sWord = mdis.readWordO ; 

     // продвинуть смещение по файлу на одно слово 



     offset+=sWord.1ength(); 

     // если   текущая  позиция, и пиксельная    ширина слова 

     // не выходят за экран 

     if(x+font.stringWidth(sWord)<=gw) { 

          // отобразить слово , начиная с текущей       позиции 

         g.drawString(sWord,      x.y . g.TOPjg.LEFT) : 

          // сместить текущую позицию на пиксельную ширину слова 

         x+-font.stri ngWi dth(sWord) ; 

          // если слово - последнее в строке , перевести        текущую  позицию 

          // в начало новой    строки на дисплее 

          if  (mdis.b_endLine ) { y+=font.getHeight() : x=0 ; } 

     } 

     // слово не входит по ширине с текущей        позиций 

     else { 

          // перейти на новую строку 

          y+=font.getHeight(); 

          // если на экране есть место для новой строки . 

          // отобразить слово с начала      строки 

          i f(y+font.getHei ght()<gh ) g.drawStri ng 

                                           (sWord. O.y . g.TOP|g.LEFT) : 

          // перевести    текущую  позицию на ширину слова 

          x=font.stringWidth(sWord); 

 •    } 

 // повторять до тех     пор . пока есть место для новой     строки 

 // или не кончится файл 

 } while(y+font.getHeight()<gh & & !mdis.b_endFile) ; 



 int index ;      // смещение   начала  страницы 

 // если   цикл  закончился  чтением   слова . 

 // которое не было отображено на экране , 

 // сместить   начало новой страницы     назад на одно слово 

 if(mdis.b_endl_ine) index=offset ; else index=offset-sWord.length() ; 

 // поместить в стек     начало следующей    страницы 

 if(!mdis.b_endFile) Pagelndex.push(new Integer(index)) ; 

 // закрыть   поток 

 try{ 

     mdis.closeO; 


----------------------- Page 103-----------------------

1 0 4         Глава  9 . Организация потоков ,   работа с файлами 



     }  catchCIOException ioe)        {} 



public void keyPressed(int         keyCode) { 

     //   обработка   нажатий   клавиш 

     switch(keyCode) { 

          case KEYJUM1 : 

               // удалить из стека       начала следующей и текущей        страницы 

               PageJ.ndex.popO;                             . ' 

               1f(!PageIndex.empty()) Pagelndex.pop() ; 

               repaint О  ; 

               break ; 

          case KEY_NUM2 : 

               //   перерисовать    экран   (начало следующей     страницы уже в стеке ) 

               repaint О  : 

               break ; 

          case    KEYJUMO : 

               //   очистить стек и перерисовать экран 

               PageIndex.removeAl1Е1ements(); 

                repaintO : 

               break : 



Поток в данном случае представлен  классом, наследованным из DatalnputStream и 

реализующим   функцию чтения   слова   readWordO.   При чтении  очередного   слова 

функция выставляет флаги конца строки и конца файла. Сам класс выглядит сле- 

дующим   образом: 

 import   java.io.InputStream : 

 import   java.io.DatalnputStream ; 

 import   java.io.EOFException ; 

 import   java.io.IOException : 



public class MyDatalnputStream          extends DatalnputStream { 



private String WIN1251_T0_UNIC0DE = "...": // кодовая строка 

                                                      //   преобразования     символов 

public boolean b_endl_ine » false ;                    // флаг   конца строки 

 public boolean b_endFile • false ;                    // флаг   конца файла 



public MyDataInputStream(InputStream            is ) { 

      super(is); 



 public   String   readWordO   { 



      int   1-0;      //  счетчик   символов  в   текущем слове 


----------------------- Page 104-----------------------

                                                                            Класс BookReader            105 



     b_endFile    =   false : 



     char[]   word   =   new   char[100] ;  //  массив   для  чтения   слова 



     try   { 



          //  читать   слово   посимвольно до   пробела   или  перевода строки 



          do   word[i++]=(char)convert(readUnsignedByte()) ; 



          while   (word[i-l]!=32    &&  word[i-l]!=10) : 



     }  catch(EOFException   ioe)     { i - - :  b_endFile  =   true:} 



      catchUOException       ioe) 



     if(word[i-l]==10)      b_endLine=true:   else   b_endLine=false: 



     return    (new String(word.O.i)) : 



char   convert(int   ch)   { 



     return    (ch   <   128)   ?   (char)ch WIN1251_T0JNIC0DE.charAt(ch-128); 



При чтении данных потока могут быть сформированы следующие  исключения: 

•     EOFExcepti on — формируется при достижении                                                     •  j~t,x | 

    конца файла;                                                    FJe  l o o t  Help 

•     IOException   — базовое  исключение для всех 

    функций   ввода-вывода. 

В   программе   мидлета   осталось   лишь   создать 

объект класса BookCanvas и установить  его в ка- 

честве   текущего   дисплея, как мы уже   неодно-                            В  ~   BookReader 

кратно делали.  Запускаем  получившееся   при-                               В общем, Кирилл 

                                                                             вьется с  этим и 

ложение на эмуляторе   и получаем   в пользова-                              «пустышками» уже 

                                                                             почти  гол. Я у него с 

ние вот такую замечательную читалку (рис. 9.5).                              самого начала, но до 

                                                                             сих пор не понимаю 

                                                                             толком, чего он от  них 

 Совершенству, как известно, предела нет, поэто-                             добивается, да,  честно 

                                                                             ГОВОРИ,и понять 

му   некоторые  моменты   остались   на  самостоя-                           особенно не 

тельную       доработку.   Непроработана   ситуация, 

когда длина слова  превышает   размер экрана — 

здесь     нужна     логика   деления        слова    на   части 

и переноса  на следующую строку.  При чтении 

последней страницы очередное смещение в стек 

 не заносится, поэтому при прокрутке текста на- 

 зад   с   конечной  позиции одна  страница  будет 

 пропущена. Кроме этого, текст не должен содер- 

 жать в конце пробелов или переводов строки: это 

 может стать причиной формирования исключе- 

 ния   при  просмотре   последней   страницы. Еще 

 можно  поработать   над   более  удобным,   интуи- 

 тивным перемещением по тексту,   задействовав 

 не цифры, а навигационные клавиши аппарата, 

 помеченные стрелками. Для разных моделей те- 

 лефонов эти клавиши формируют разные коды,                          Рис. 9.5 . «Алло!  Мистер Шухарт?» 


----------------------- Page 105-----------------------

1 06       Глава 9 . Организация потоков, работа с файлами 



поэтому лучше пользоваться API-константами конкретной модели. На API-функ- 

циях некоторых моделей мы остановимся позже. 



Класс ByteArraylnputStream 



На примерах мы рассмотрели работу с файлом-ресурсом, который предоставлял 

приложению все необходимые данные. На самом деле, поток может быть сфор- 

мирован из любых данных и представлен в виде последовательности байтов. Еще 

один класс, который порожден из базового класса InputStream и представляет по- 

ток в виде открытого массива байтов, — это   ByteArraylnputStream. 

Данный   класс  предоставляет   все  объявленные, но не реализованные функции 

класса   InputStream, позволяя получить   заранее число доступных   байтов потока, 

пометить позицию возврата для повторного чтения данных. 

Внутренняя структура потока открыта для нас. Программисту доступны следу- 

ющие поля: 

 •   byte[ ] buf — массив байтов для хранения данных, указанный при создании потока; 

 •   int   count — число доступных  байтов потока; 

 •   int   mark — индекс текущей помеченной позиции для повторного чтения; 

 •   i nt   pos — индекс следующего  байта потока для чтения. 

Использование объектов класса ByteArraylnputStream   мы рассмотрим в следующей 

главе. 



 Класс OutputStream 



До сих пор мы работали с потоком только одного направления — от ресурса к при- 

ложению. Все рассмотренные классы, так или иначе, связаны со словом Input (ввод), 

а методы чтения данных — со словом read. Вся система ввода зеркально отобража- 

 ется на работу с потоком обратного направления. Поток вывода данных сохраняет 

всю иерархию классов потока ввода, с той лишь разницей, что в названиях классов 

вместо  слова   Input фигурирует   слово Output: OutputStream,   ByteArrayOutputStream, 

 DataOutputStream. Каждому методу чтения, содержащему в названии слово read, со- 

ответствует аналогичный метод записи, только со словом wri te. 

 Функции потоков чтения ski p, avai I abl e, mark и reset для потоков вывода не акту- 

 альны, вместо них потоки вывода реализуют   лишь одну функцию flush(),  кото- 

рая очищает все данные выходного потока. 



                                         * * * 



 Это были простейшие примеры использования потоков данных. Но кроме ресур- 

 сов приложения существуют также и сетевые ресурсы, ведь мобильный телефон 

это все-таки  средство  связи. Организация сетей, поддержка  работы с сетевыми 

ресурсами, коммуникация между мидлетами и создание межсетевых приложений 

 реализуются   с помощью потоков данных.  На некоторых  примерах   использова- 

 ния потоков для сетевых данных мы остановимся в заключительных   главах, по- 

этому оставайтесь с нами: впереди еще много интересного. 


----------------------- Page 106-----------------------

 Глава 10 



 Ввод текста, работа со строками 



В этой главе мы рассмотрим стандартные средства связи с пользователем, позво- 

ляющие не только выбрать  один из предложенных   пунктов меню, но и вводить 

с клавиатуры телефона произвольный текст, и реализуем долговременное хране- 

ние введенных  данных, используя  технологии, рассмотренные нами в прошлых 

главах, такие как потоки данных и RMS. 

В наш век научно-технического   прогресса сотовый телефон практически всегда 

под рукой, в отличие от обычной авторучки. Часто ли вы сталкивались с ситуаци- 

ей, когда нужно записать какую-то важную информацию: почтовый адрес, e-mail, 

названия лекарств, маркировку деталей, да мало ли что? Стандартная записная 

книжка  мобильного  телефона  рассчитана  лишь   на запись  телефонного  номера 

и не очень заковыристых имени и фамилии его обладателя.  Можно, конечно, ис- 

пользовать   не по назначению ремайндер, если  он есть, или писать текст   в теле 

сообщения SMS, но в таком случае потерять информацию так же легко, как и слу- 

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

расширив количество хранимой информации согласно нашим требованиям. 

Стандартные средства предлагают нам два варианта для реализации полей ввода 

текста: TextField  и TextBox. Рассмотрим подробно каждый из них. 



 Класс TextBox 



Класс TextBox является потомком класса Screen  и представляет   собой экран, по- 

зволяющий вводить и редактировать текст с клавиатуры  аппарата. При создании 

объекта в конструкторе нужно указать заголовок поля ввода, начальное содержи- 

мое поля, максимально допустимое количество вводимых символов и ограничи- 

тель для вводимой информации: 



TextBox(String t i t l e .   String text ,  intmaxSize,  int   constraints) 

 Ограничители   позволяют   заранее задать   тип  вводимого  текста. Так, например, 

 если информация содержит только цифры, то буквы не будут появляться в этом 

 поле при нажатии на клавиши. Для полей ввода доступны следующие ограничи- 

тели, которые определяются константами класса TextField: 

 •   ANY — допускается ввод любых символов без ограничений; 

 •   EMAILADDR — используется для ввода адреса электронной почты; 

 •   NUMERIC — применяется для ввода  целых чисел. Клавиша * позволяет вводить 

    отрицательные числа и добавляет знак - перед числом; 


----------------------- Page 107-----------------------

1 08        Глава   10 . Ввод текста, работа со строками 



•    PHONENUMBER — ввод   телефонного   номера.  Поле   ввода   такого   типа   привязано 

    к   стандартному   вводу  телефонного  номера  аппарата.  Это  значит, что   в  меню 

    автоматически  появится команда поиска номера в записной книжке Search; 



•    PASSWORD — используется в комбинации с другими константами для скрытия вво- 

    димой   информации. Вводимые   символы   отображаются                  звездочками,  как при 

    вводе  PIN-кода. Комбинации констант задаются   с помощью оператора   |   (или), 

    например: NUMERIC   |     PASSWORD; 



•    URL — ввод сетевого  адреса  интернет-страницы в формате URL; 



•    CONSTRAI NT_MASK — маска ограничителя; константа, используемая   для   получения 

    текущего ограничителя поля из значения, возвращенного методом GetConst га i nts () . 

    Маска   накладывается        с   помощью  оператора   & (и)   на  возвращенное значение 

    и отбрасывает флаги модификаторов ограничителя, например, такие как PASSWORD. 



После того как объект поля ввода создан, он может быть отображен  на экране теле- 

фона так же, как и все отображаемые объекты, наследованные из класса Di spl ayabl e, 

с помощью метода  класса Di spl ay setCurrent(TextBox). Если вводимый текст превы- 

шает   размер   экрана, система   сама   обеспечит   прокрутку           без   каких-либо   усилий 

с нашей стороны. Дальнейшая работа с созданным объектом обеспечивается следу- 

ющими методами  класса TextBox: 



•     void delete   (int   offset,  int   length)   — удаляет   из поля  ввода   length   символов, 

    начиная  с позиции   offset; 



•     int   getCaretPositionO     — возвращает   текущее   положение  позиции ввода   (0 — 

    начало текста); 



•     int   getCharsCchart]   data)   — копирует   весь  текст   из  поля   ввода   в  символьный 

    массив data, возвращает число скопированных символов. Текст, отображаемый 

    в поле ввода, не изменяется; 



•     int  getConstraints()   — возвращает  текущий  ограничитель  поля   ввода; 



•     i nt   getMaxSi ze () — возвращает максимально допустимое количество  вводимых 

    символов; 



 •    String  getStringO     — возвращает   содержимое  поля ввода  в виде строки; 



 •    void  insert (Strings re,     int  position)  — вставляет   строку   src   в поле ввода, на- 

    чиная с позиции position. Если указанная позиция меньше или равна нулю, то 

    строка  вставляется   в начало  поля, если  позиция больше   количества   уже   вве- 

    денных символов, то строка вставляется  сразу за последним символом; 



 •    void  insert(char[]  data,   int  offset,  int  length,   int  position)   — вставляет   в поле 

    ввода с позиции, заданной переменной position, length символов из массива data, 

    начиная с символа с индексом offset.   Логика вставки, как в предыдущем  методе; 



 •    void setChars(char[]  data,  int offset,     int   length)— очищает поле ввода  и встав- 

    ляет   length   символов  из массива data, начиная с позиции массива                offset; 



 •    void setConstrai nts (int   constraints)   — устанавливает   ограничители поля ввода; 



 •    int   setMaxSized'nt   maxSize) — устанавливает   максимально допустимое  количе- 

    ство вводимых   символов; 



 •    void setStri ng(String text)  — заменяет текущее содержимое поля ввода на строку, 

    заданную   параметром   text ; 



 •    int  size()   — возвращает   количество символов, содержащееся   в поле  ввода. 


----------------------- Page 108-----------------------

                                                                               Класс TextField           10 9 



 Класс TextField 



Объект   класса  TextField   представляет   собой  редактируемую                     строку,   отобража- 

емую на экране телефона и позволяющую пользователю редактировать и вводить 

текстовые символы с клавиатуры  аппарата. 

Разница между объектами TextBox и TextField такая же, как и между рассмотрен- 

ными ранее объектами   List  и ChoiceGroup. Они реализуют   один и тот   же набор 

методов, а различаются  только тем, что первый является самостоятельным ото- 

бражаемым   объектом   иерархии   класса  Displayable,   а  второй  для   демонстрации 

должен быть включен в форму. 

Класс TextFi el d расширяет рассмотренный нами в программе слайд-шоу класс Item, 

а это значит, что объект класса TextFi el d можно включить в обычную форму (class 

Form) с помощью стандартного метода формы append. 

Для примера рассмотрим программу калькулятора, выполняющего простые ариф- 

метические действия. В калькуляторе  будут использоваться три поля ввода: два 

для аргументов  и одно для символа арифметического действия: 



import    javax.microedition.lcdui.TextField : 



private    Display display : 

private    Form  form : 



private    Command equal : 

private    MyCommandListener c l    =   new MyCommandListenerO; 



private    TextField   argl.arg2.action :       //  поля ввода 



public    void   startAppO  { 



     //   создать  форму калькулятора 

     display    -  Display.getDisplay(this) : 

     form   =   new   Form("Calculator") : 

     form.setCommandListener(cl): 



      //  команда ,  запускающая    вычисления 

     equal = new Command("Equal" .       Command.OK , 1) : 

      form.addCommand(equal); 



      //  поле ввода   первого   аргумента 



     a r g l  =  new   TextFieldC'Argumentl " ."".5.TextField.NUMERIC) ; 



      form.append(argl) : 



      //  поле  ввода  символа   операции 

      action = new    TextField("Action"."".l.TextField.ANY) : 

      form.append(action) : 



      //  поле  ввода   второго  аргумента 

      arg2 = new    TextField("Argument2"."".5.TextField.NUMERIC) ; 


----------------------- Page 109-----------------------

1 10          Глава   10 . Ввод текста,  работа со строками 



     form.append(arg2); 

     display.setCurrent(form); 



private class MyCommandListener          implements   CommandListener 

 { 

     public    void  commandAction(Command   с      Displayable    d) 



     { 

          if(c==equal) {              // запустить     вычисление 

               // форма для экрана результата          операции 

               Form resForm = new Form("Result") ; 

               resForm.setCommandListener(cl); 

               Command    back = new CommandC'Back",Command.BACK.1) ; 

               resForm.addCommand(back) : 



                // проверить    заполнение всех полей       ввода 

               if(argl.size()==O || arg2.size()==0 || action.size()==O ) { 

                         // ошибка , не все поля       ввода  заполнены 

                         resForm.appendC"Missing        argument") : 

                          // отобразить сообщение 

                         display.setCurrent(resForm); 

                          // выйти 

                          return; 



                // получить символ      операции 

               char[] act = new char[l] ; 

               action.getChars(act); 



                // преобразование      аргументов 

                int first = Integer.parselnt(argl.getStringO) : 

                int second - Integer.parseInt(arg2.getString()) ; 

                int res = 0 : 

                switchCact[O]) { 

                     // выполнить     действие в соответствии 

                     // с символом     операции 

                    case '+' : 

                          res - first + second : 

                         break : 

                    case 

                          res = first - second : 

                         break;            , 

                     case '*': 

                          res = first * second ; 

                          break ; 


----------------------- Page 110-----------------------

                                                                       Класс AddressBook          111 



                  case '/'; 

                       res = first / second ; , 

                       break ; 

                  default : 

                       //  ошибка ,  некорректный  символ  операции 

                       resForm.append("IIlegal     Operation") : 

                       //  отобразить   сообщение 

                       display.setCurrent(resForm); 

                       //  выйти 

                       return; 



              //  преобразовать   результат в строку и добавить в форму 

              resForm.appendUnew      Integer (res) ) .toStringO) ; 

              //  отобразить   результат 

              display.setCurrent(resForm); 

           else 

              //  команда  Back формы   результата   приводит  сюда 

              //  отобразить   первоначальную   форму 

              display.setCurrent(form); 



Вид   и размещение  полей   ввода   на экране зависит  от 

конкретной модели  аппарата. На стандартном  эмуля- 

торе наш калькулятор будет выглядеть следующим об- 

разом (рис. 10.1).                                                              calculator 

Казалось бы, можно радоваться, поскольку  мы полу-                              s.rgument11 

чили  бесплатный   калькулятор,  который отсутствует                            Action i 

во многих моделях.  Главная проблема состоит в том,                             <\rgument2 

что виртуальная   машина CDLC не поддерживает  вы- 

числений с плавающей точкой. Другими словами, наш 

калькулятор сможет работать только с целыми числа- 

ми,   отбрасывая   при делении дробную   часть.  К сожа- 

лению, такой калькулятор сгодится лишь для подсче- 

та сдачи в магазине. 



 Класс AddressBook 



 В  прошлых  главах  мы рассмотрели   методы  хранения 

 информации в памяти телефона, создание меню, а так- 

 же потоковую  передачу   данных с использованием по- 

 токов ввода и вывода. Сейчас нам понадобятся все эти 

 знания, поскольку тот пример, который мы собираем- 

 ся  реализовать, будет хранить данные с помощью RMS                       Рис.   10.1 .  Так  выглядит 

 и  передавать их с помощью потоков.                                       простейший калькулятор 


----------------------- Page 111-----------------------

1 12         Глава  10 . Ввод текста, работа со строками 



Порой мы недовольны стандартной записной книжкой телефона, которая, конеч- 

но   же, очень классная, только в ней для полного счастья не хватает какой-то очень 

важной  функции. Выход есть, ведь  можно реализовать   свою записную   книжку, 

где учесть все свои прихоти, а при надобности добавить   еще и чужие.  Этим мы 

сейчас и займемся: реализуем   основу для  записной книжки, которая будет  хра- 

нить записи, содержащие имя, номер телефона и адрес электронной почты. 

Для  начала  продумаем   сценарий смены экранов. Приложение стартует с экрана 

со списком всех имен, уже добавленных в записную книжку. Команда Ok демонст- 

рирует   форму, содержащую   все параметры  записи, соответствующей   выбранно- 

му имени, а команда Add начинает выполнение сценария добавления новой запи- 

си.  Сценарий добавления записи предоставляет по очереди несколько экранов для 

ввода каждого из параметров, команда Next выполняет переход  к следующему эк- 

рану, а команда Back — возврат к   предыдущему. 

Рассмотрим   основной класс  мидлета   AddressBook, который  в   качестве   полей со- 

держит экран списка имен, экраны сценария добавления записи, а также команды 

перехода между экранами. Стартовый метод startAppC)   инициализирует экраны и 

команды и создает  список имен из уже  имеющихся записей: 

import    javax.microedition.midlet.MIDlet : 

import    javax.microedition.icdui.Command : 

import    javax.microedition.lcdui.CommandUstener ; 

import    javax.microedition.lcdui.Display : 

 import   javax.microedition.lcdui.Displayable : 

 import   javax.microedition.lcdui.Form : 

 import   javax.microedition.lcdui.List : 

 import   javax.microedition.lcdui.TextField : 

 import   javax.microedition.lcdui.TextBox : 

 import   Java.iо.ByteArrayOutputStream : 

 import   java.io.ByteArraylnputStream ; 

 import   java.io.DataOutputStream : 

 import   java.io.DatalnputStream ; 

 import   java.io.IOException : 

 import   javax.microedition.rms.RecordStore : 

 import   javax.microedition.rms.RecordEnumeration : 

 import   javax.microedition.rms.RecordComparator : 

 import   javax.microedition.rms.RecordStoreException : 



public class AddressBook       extends MIDlet   implements   CommandListener 

 { 

     private Display display :                        // менеджер дисплея 

     private RecordStore      recordStore ;           // хранилище   записей 

     private List nameList :                          // список  имен 

     private    int  reclndexes[] :                   // массив ID записей . 

                                                     //  соответствующий    списку  имен 

     private Command     add.ok.next.back :           // команды   переходов  между  экранами 

     private TextBox tbName.tbPhone.tbEMail :         // экраны   ввода  параметров  записи 


----------------------- Page 112-----------------------

                                                                       Класс AddressBook          1 1 3 



    public void startAppO { 

         try { 

              // открыть хранилище записей с именем        "Address-Book" 

              recordStore - RecordStore.openRecordStoreCAddress-Book" ,           true) ; 

         }  catch   (RecordStoreException    rse ) { } 



         // получить ссылку на менеджер дисплея 

         display • Oisplay.getDisplay(this) : 



         // создание объектов     команд  переходов 

         ok - new CommandC'Ok" , Command.OK , 1) ; 

         add = new CommandC'Add" , Command.BACK , 1) : 

         next = new Command("Next" . Command.OK . 1) : 

         back » new Command("Back" , Command.BACK . 1) : 



         // поле ввода имени 

         tbName - new TextBox("Name:"."".15,TextField.ANY) : 



         tbName.addCoiraiand(next): 



         tbName.addCommand(back); 

         tbName.setCommandLi stener(this) : 



         // поле ввода номера      телефона 

         tbPhone = new TextBox("Number:"."".15.TextField.PH0NENUMBER) : 

         tbPhone.addCommand(next) : 

         tbPhone.addCommandCback) : 

         tbPhone . setCommandLi stener (thi s«); 



         // поле ввода     электронной  почты 

         tbEMail = new TextBox("E-Mail:"."".35.TextField.EMAILADDR) : 

         tbEMail.addCommand(next); 

         tbEMa i1.addCommand(back) ; 

         tbEMail.setCommandListener(this) : 



          // создать список имен 

         BuildNameListO ; 

          // отобразить список имен на экране 

         di splay.setCurrent(nameLi st) ; 

     } 

Обратим внимание, что если в предыдущей  программе для ввода информации мы 

использовали   несколько  объектов   класса  TextField,   которые добавляли   в   одну 

форму, то теперь каждое  поле ввода представляет   собой отдельный  экран класса 

TextBox. Каждый экран содержит  команды Next и Back, которые осуществляют  пе- 

реходы  от одного экрана ввода к другому. 

Создание  списка  имен  реализовано   в методе   BuildNameListO,   который   создает 

и инициализирует   новый объект   списка, затем   получает   все записи  хранилища, 

отсортированные по именам в алфавитном-порядке. Имена добавляются в список 


----------------------- Page 113-----------------------

114           Глава  10 . Ввод текста,  работа со строками 



имен, a ID соответствующих   записей — в массив reclndexes, чтобы при надобнос- 

ти можно было легко получить все остальные параметры необходимой записи: 

//   метод создает    список  имен  из адресной    книги 

private void     BuiidNameListO { 

     //   создать объект списка 

     nameList = new List("Address-Book" . List.IMPLICIT) : 

     nameList.setCommandListener(this) : 

     //   команда  добавления    записи 

     nameList.addComiTiand(add); 

     //   команда  получения    параметров   записи 

     nameList.addCommand(ok); 

     try { 

          //   получить   количество   записей 

          int size = recordStore.getNumRecordsO : 

          //   создать  массив   для хранения    ID записей 

          reclndexes = new      int[size] : 

          //   создать объект    компаратора в алфавитном      порядке 

          AlphabeticalOrdering      comparator = new AlphabeticalOrderingO ; 

          //   получить список    записей   хранилища 

          RecordEnumeration      re » recordStore.enumerateRecords 

                             ..        (null , comparator ,   false) ; 

          //   индекс  массива   хранения   ID записей 

          int 1=0 ; 

          //   бесконечный   цикл ; выход   из цикла   происходит 

          //   по формированию    исключения    получения   ID следующей   записи , 

          //   после  того как было    получено   ID последней    записи 

          while(true ) { 

               //   получить   ID следующей   записи 

               int   id = re.nextRecordldO ; 

               //   записать ID в массив 

               reclndexes[i++]=id : 

               //   получить   запись no ID 

               byte[ ] record = recordStore.getRecord(id) ; 

               //   преобразовать    запись в байтовый     поток 

               ByteArraylnputStream       bais = new ByteArraylnputStream(record) ; 

               //   создать поток ,   поддерживающий    чтение по типу 

               DatalnputStream      dis = new DatalnputStream(bais) ; 

               //   считать из потока     первую строку и добавить в список 

               nameList . appendtdis.readUTFO.null) : 



      catch(RecordStoreException       rse ) 

      catchdOException      ioe) ' { } 


----------------------- Page 114-----------------------

                                                                                Класс  AddressBook             115 



Алфавитный  порядок добавления записей в список имен реализован с помощью 

компаратора, представленного классом Alphabetical Ordering: 

//   класс  компаратора записей по алфавиту 



private    class   Alphabetical Ordering      implements    RecordComparator      { 

     //   метод   сравнения    записей 

     public     in t  compare(byte[]   reel .   byte[]   rec2)  { 

           //  преобразовать      записи   в   байтовый   поток 

           ByteArraylnputStream        bais l  -  new  ByteArraylnputStream(recl) : 

           ByteArraylnputStream        bais2  =   new  ByteArrayInputStream(rec2); 

           //   создать   потоки,   поддерживающие   чтение      по   типу 

           DatalnputStream      dis l  =   new  DatalnputStream(baisl) ; 

           OatalnputStream      dis2   =   new  DataInputStream(bais2) : 

           //   строки  имен 

           String    name!   =  null : 

           String    name2   =  null ; 

           t r y  { 



                //   считать   строки   с   именами 

                namel    =   disl.readUTF   С); 

                name2   =   dis2.readUTF    О   : 



           catch    (IOException    ioe ) { } 

           // лексикографическое        сравнение строк 

           int result = namel.compareTo(name2) ; 

           if   (result < 0 ) 

                 // первая    запись   предшествует     второй 

                return    RecordComparator.PRECEDES : 

           else 

                if (result == 0 ) 

                     //   записи   содержат    идентичные   имена 

                      return     RecordComparator.EQUIVALENT: 

                else 



                     //   вторая   запись   предшествует     первой 

                      return    RecordComparator.FOLLOWS: 



 Таким образом, остается реализовать только блок   про- 

 слушивания команд, который   определяет действия для 

 осуществления   переходов  между   экранами.                       Если    же 

 создать пустой метод commandActi on, то приложение уже 

 можно компилировать.               Запустив приложение,   при на- 

 личии   записей,   мы могли бы увидеть список   имен,  по 

 которому можно перемещаться с помощью   навигаци- 

 онных   клавиш   (рис. 10.2).                                                     Рис.   10.2 .   Если встроенная 

                                                                                     записная книжка вас не 

 Осталось сделать последний шаг  и написать   олок   про-                               устраивает, можно 

 слушивания   команд,   который                 будет  контролировать                      создать свою 


----------------------- Page 115-----------------------

116           Глава    10 . Ввод текста, работа со строками 



порядок   отображения экранов   в зависимости от выбранной                          команды,  а также ре- 

ализует сценарий   добавления новой   записи: 



//   блок  прослушивания   команд 



public    void commandAction(Command       с . Displayable d ) { 



     // команда Add отображает         экран  ввода   имени 

     1f(c==add) display.setCurrent(tbName) : 



     // команда Ok 

     if(c==ok) { 

           // если   команда   вызвана из экрана со списком        имен 

          if(d==nameList) { 

               try { 

                     // получить в массиве       reclndexes ID необходимой       записи 

                     int id = recIndexes[nameList.getSelectedIndex()] ; 

                     // считать    необходимую    запись 

                    byte[ ] record = recordStore.getRecord(id) : 

                     // преобразовать      запись в байтовый     поток 

                    ByteArraylnputStream        bais = 

                                               new ByteArraylnputStream(record) : 

                     // создать поток , поддерживающий        чтение по типу 

                     DatalnputStream     dis = new DatalnputStream(bais ) ; 

                     // создать форму для отображения          параметров 

                     Form   infoForm = new     FormC'") : 

                     // считать строки с параметрами и добавить в форму 

                     infoForm.append(dis.readUTF()+"\n"); 

                     infoForm.append(dis.readUTF()+"\n"); 

                     i nfoForm.append{di s.readUTFC)+"\n") ; 

                     // добавить     команду   возврата к списку     имен 

                     infoForm.addCommand(ok); 

                     infoForm.setCommandListener(this) : 

                     // отобразить форму на экране 

                     display.setCurrent(infoFdrm) : 

                } 

                catch(RecordStoreException        rse ) {} 

                catchdOException       ioe ) { } 

           }  else {       // если   команда   вызвана из формы     параметров    записи . 

                          // вернуться к списку имен 

                display.setCurrent(nameList); 



      // команда     Next : перейти к следующему      полю   ввода 

      if(c==next) { 

           //   из  поля  ввода  имени   к  полю   ввода  номера  телефона 

           if(d==tbName)     display.setCurrent(tbPhone) : 


----------------------- Page 116-----------------------

                                                                       Класс AddressBook            1 1 7 



         // из поля    ввода  номера  телефона к полю    ввода e-mail 

         if(d==tbPhone) display.setCurrentCtbEMai        1) : 

         // из поля    ввода e-mail 

         if(d==tbEMail) { 

              .//  создать байтовый   поток  вывода 

              ByteArrayOutputStream     baos - new ByteArrayOutputStreamt) ; 

              //  создать  поток  вывода , поддерживающий    запись по типу 

              DataOutputStream    dos = new DataOutputStream(baos) : 

              try { 

                   //  записать  введенные   параметры в поток    вывода 

                  dos.writeUTF(tbName.getStringO); 

                  dos.wri teUTF(tbPhone.getStri ng С)) : 

                  dos.writeUTF(tbEMail.getStringO); 

                   //  добавить  запись в хранилище 

                   recordStore.addRecord(baos.toByteArray() .       0 .  baos.sizeO) : 

              } 

              catchUOException      ioe ) { } 

              catch   (RecordStoreException    rse ) { } 

              //  создать список    имен 

              BuildNameListO ; 

              //  отобразить   список  имен на   экране 

              display.setCurrent(nameLi st) ; 



     //  команда  Back :  возврат к предыдущему    полю  ввода 

     if(c==back) { 

         // из поля    ввода  имени к списку   имен 

         i f(d==tbName ) di splay.setCurrent(nameLi st) ; 

         // из поля    ввода  номера  телефона к полю    ввода  имени 

         if(d==tbPhone) display.setCurrent(tbName) ; 

         // из поля    ввода e-mail к полю    ввода  номера  телефона 

         if(d--tbEMail) display.setCurrent(tbPhone) : 



На   этом пока остановимся. Теперь все команды должны  работать, можно запус- 

кать  и пробовать.  Экран ввода   параметров   записи будет выглядеть                      следующим 

образом (рис. 10.3). 

После того как мы ввели параметры записи и добавили ее в хранилище, введенное 

имя    записи появится в списке. Можно выбрать его, используя навигационные кла- 

виши, и с помощью команды Ok вывести на экран параметры записи (рис. 10.4). 

Основное направление движения задано. Дальше вы можете продолжить самосто- 

ятельно. Первым делом  нужно реализовать   поиск записи по имени или его фраг- 

менту с помощью фильтра записей хранилища. Не лишней при добавлении новой 

записи будет проверка, существует ли уже запись с подобным именем. Такую про- 

верку можно организовать с помощью того же фильтра. Я уверен, что богатая фан- 


----------------------- Page 117-----------------------

118        Глава   10 .   Ввод текста,  работа  со строками 



тазия программиста подскажет вам еще массу приятных и полезных нововведений. 

Одно из возможных усовершенствований мы подробно рассмотрим в  следующей 

главе. Я думаю, что такого вы еще не видели ни в одной модели телефона. 



                    Щ 



     РИС.   10.3 . Экран  ввода параметров    Рис.  10.4 . Информация из записной  книжки 

      записи  на стандартном эмуляторе                   выведена  на экране 



 Один большой минус заключается в том, что, в общем случае, доступа из мидлета 

 к стандартной адресной книге телефона и ее записям нет, то есть невозможно по- 

лучить программно записи из стандартной адресной книги. Иногда, если повезет, 

 такой доступ  предоставляет   API производителя конкретной модели телефона. 



 В этой главе мы рассмотрели на примерах работу с полями ввода, представленны- 

 ми объектами классов TextBox и TextFi el d. Даже в небольшой программе адресной 

 книги нам понадобились знания почти из всех предыдущих глав. Мы использова- 

ли интерфейс высокого уровня для связи с пользователем  и реализации логики 

 смены экранов, создали список имен с помощью объекта класса Li st, организова- 

 ли долговременное   хранение  записей с использованием  RMS, осуществили   за- 

 пись и чтение данных с помощью потоков ввода-вывода. Возможности мобильно- 

 го языка J2ME очень ограничены, нетрудно разобраться со всеми и попробовать 

 их в действии. Это поможет реально оценивать свои силы при проектировании 

 функциональности программ. 


----------------------- Page 118-----------------------

 Глава 1 1 



 Работа со временем, 

датой и календарем 



Продолжим наше знакомство с программированием для мобильных  телефонов. 

Современный аппарат  — это больше, чем средство  связи, ведь он напичкан все- 

возможными, нужными и не очень, дополнениями, которые к самой связи имеют 

отдаленное отношение. Некоторые из усовершенствований, которые продавцы са- 

лонов   связи  демонстрируют    первым  делом,  покажутся   откровенно лишними, 

а без других уже сложно представить  себе телефон. Например, с появлением те- 

лефона я совсем перестал  пользоваться наручными часами, настольным  будиль- 

ником и карманным калькулятором. 

Реализация J2ME предоставляет  нам возможность пользоваться внутренним вре- 

менем и календарем телефона. Стоит подключить фантазию и таким возможнос- 

тям можно легко найти применение. Например, составление ежедневных индиви- 

дуальных   гороскопов или организация испытательных   тридцатидневных   версий 

приложений. В этой главе мы рассмотрим использование объектов календаря и даты 

для расширения записной книжки функцией напоминания о днях рождения. 



 Класс Date 



Объект класса Date представляет собой текущее время с точностью до миллисекунд. 

Время представлено в миллисекундах, прошедших с полуночи 1 января 1970 года. 

Информация, безусловно, нужная, но в таком виде совершенно бесполезная. К со- 

жалению, класс Date не содержит интерпретаторов для перевода даты в более по- 

нятный для нас формат. Для этого нужно использовать класс Calendar, который мы 

рассмотрим позже. 

Класс Date имеет всего несколько методов. Все они оперируют с датой и временем 

лишь в указанном формате. Рассмотрим эти методы: 

 •   Date() — конструктор; создает новый объект даты, содержащий текущее время; 

 •   Date( long date) — конструктор; создает новый объект даты, содержащий время, 

   заданное в аргументе date; 

 •   boolean equals (Object obj) — возвращает  значение true в том случае,  если две 

   даты идентичны с точностью до миллисекунды; 

 •   long getTime()   — возвращает время, представленное объектом даты; 

 •   void  setTimef long time) — устанавливает   объект даты в момент времени, пред- 

    ставленный параметром time. 


----------------------- Page 119-----------------------

120         Глава  1 1 .  Работа со временем, датой и календарем 



Вот, собственно, и все. Разнообразием  и гибкостью   методов   класс  Date нас не ра- 

дует. Самое время сформулировать   задачу, которую  мы хотим реализовать.  К про- 

грамме записной книжки, которую   мы разбирали   в прошлой главе, добавим   фун- 

кцию напоминания о днях рождения записанных абонентов. Для этого   потребуется 

расширить формат записи, содержащейся  в хранилище записей, добавив поле даты 

рождения.  В сценарий ввода   данных   о новом  абоненте  потребуется                  добавить   эк- 

ран для ввода даты  рождения. Здесь нас ждет   приятный сюрприз, потому что поле 

ввода   даты     предоставляет      нам   пользовательский         интерфейс   высокого        уровня 

в виде  класса  DateField. 



 Класс DateField 



Класс  DateField  является   потомком   класса   Item, то  есть  для   его  демонстрации   на 

экране нам потребуется   еще и форма. Поле может   работать   в трех   режимах                  ввода: 

ввод   только   даты,  ввод   только   времени, ввод   даты   и времени.  При вводе   только 

времени дата   по умолчанию   будет соответствовать               1 января   1970   года.  Для   рабо- 

ты  с полем  ввода   класс  DateField  предоставляет   следующие             методы: 



 •    DateField(String   1 abel.     int  mode) — конструктор;  создает   поле  ввода  даты   с за- 

                                  головком   1 abel.  Режим  поля   ввода   определяется   аргу- 

                                  ментом   mode, который   может         принимать   значение   од- 

                                  ной  из следующих        определяющих        констант: 



                                       •  DATE — ввод только   даты; 



                                       •  DATE_TIME — ввод даты   и времени; 



                                       •  TIME — ввод только   времени; 



                                   •      Date getDateO     — возвращает       введенное     значение 

                                        даты   и времени  в формате  класса   Date. При созда- 

                                        нии объекта  исходное  значение даты   и времени не 

                                        определено,  поэтому   при запросе           отсутствующей 

                                        введенной даты   будет возвращено  значение  nul 1; 



                                   •      i nt get I nputMode ()  — возвращает   режим ввода  поля 

                                        в виде  определяющей   константы; 



                                   •      voi d setDate (Date date)  — устанавливает   в поле ввода 

                                        новое значение, представленное   параметром   date; 



                                   •      void setInputMode( int mode) — устанавливает   новый 

                                        режим ввода, заданный аргументом   mode в виде оп- 

                                        ределяющей   константы; 



                                   •      void setLabel   (String   label)  — устанавливает   новый 

                                        заголовок   поля   ввода. 



                                   Поскольку   поле  ввода   даты   предоставлено   интерфей- 

                                   сом   высокого   уровня,  то   внешний  вид   поля   и   логика 

                                   ввода зависят от конкретной модели телефона.  Напри- 



    ИС                        8    м е  в 

      даты   в  стандартной ™       Р >    стандартном эмуляторе от WTK             поле ввода   даты 

         эмуляторе WTK              выглядит   следующим   образом   (рис.   11.1). 


----------------------- Page 120-----------------------

                                                                           Класс DateFieid          1 2 1 



На    данном этапе информации достаточно, чтобы   начать дополнение нашей за- 

писной книжки. Прежде всего, импортируем необходимые нам библиотеки и до- 

бавим в класс AddressBook два новых поля, которые будут предоставлять возмож- 

ность ввода даты рождения: 



import   javax.microedition.1cdui.DateField : 

import java.util.Date ; 

import java.util.Calendar : 



private Form dateForm ;                    // форма  ввода дня рождения 

private DateFieid dateField :              // поле ввода дня рождения 



В методе startApp()   проинициализируем поле ввода даты аналогично полям вво- 

да остальной личной информации: 



 //  экран ввода дня рождения 

dateField = new DateField("Birthday".DateFieid.DATE) : 

dateForm = new     FormC'") : 

dateForm.append(dateField); 

dateForm.addCommand(next) : 

dateForm.addCommand(back); 

dateForm.setCommandLi stener(thi s) ; 



В   блок   прослушивания   команд  добавим   логику   демонстрации   экрана   ввода 

дня    рождения,  а также  дополнительное   поле  в форме отображения   парамет- 

ров записи. Хранимая запись теперь будет состоять из четырех  частей: три тек- 

стовых   части   и одна  типа   long, содержащая   дату  рождения   в формате   класса 

Date. 



Экран информации записи будет теперь формироваться следующим  образом: 

 //  если  команда  вызвана из   экрана со списком     имен 

 if(d==nameList) { 

     try { 



          //  создать объект даты рождения 

          Date birthday = new Date(dis.readLongO) ; 

          //  создать объект поля    ввода даты 

          DateField df = new DateFi el d ( " \ DateField . DATE) : 

          // установить дату рождения в поле ввода даты 

          df.setDate(birthday) : 

          //  добавить даты рождения в форму отображения        параметров 

          infoForm.append(df); 



          display.setCurrent(infoForm); 


----------------------- Page 121-----------------------

122         Глава   11 .   Работа со временем, датой и календарем 



Запись, в свою  очередь,  будет формироваться   так: 



try   { 



     //  записать   введенные  параметры   в   поток   вывода 



     dos.wri teUTF(tbName.getSt r i n g ( ) ) ; 



     dos.writeUTF(tbPhone.getStringO): 



     dos.wnteUTF(tbEMail.getStnngO): 



     dos.writeLong(dateField.getDateO.getTimeO); 



     // добавить    запись в хранилище 

     recordStore.addRecord(baos.toByteArray(), 0 . baos.sizeO) : 



                                   С   корректной   сменой   экранов   при   обработке           команд 

                                   Next и Back, думаю, вы  справитесь   самостоятельно.  Те- 

                                   перь   во   время    ввода   нового   контакта   появится        поле 

                                   ввода даты  рождения, а форма информации об абонен- 

                                   те будет выглядеть   как на рис.  11.2. 



                                   Заметим,  что   записи,  введенные   в   программе   из   пре- 

                                   дыдущей       главы,   имеют   другой   формат,   поэтому         при 

                                   попытке  считать   их   в новом  формате   будет  формиро- 

                                   ваться   исключение,  а форма  с информацией   демонст- 

                                   рироваться   не будет. 



                                   Осталось      реализовать      самый   важный   момент:   нужно 

                                   получить   текущую дату и сравнить ее с датами   рожде- 

                                   ния,   содержащимися          во   всех   записях .   Сейчас    самое 

                                   время   познакомиться с классом   Calendar, без   которого 

                                   пересчет     компьютерных        миллисекунд        в  человеческие 

                                   дни  и месяцы изрядно попортил бы нам нервы. 



                                   Класс Calendar 



                                   Класс  Calendar   является   абстрактным   классом   для   ра- 

                                   боты с датой  и временем, содержащим   набор констант, 

                                   представляющих          дни    недели    и   месяцы.  Для     работы 

                                   с месяцами определены константы от JANUARY до DECEMBER, 

 Р и с -                         |  а   для  работы   с   днями   недели    константы   от   MONDAY до 

   абоненте добавлена  дата         SUNDAY. Поскольку класс Calendar является  абстрактным, 

            рождения               м ы  н е  м о ж е м  просто создать объект этого класса, но мы 

                                   можем   получить   его с помощью  специального   метода: 

 •    stati c   Calendar   getlnstanceO     — получить   объект   календаря,   представляющий 

    текущие  дату  и время. 



Класс Cai endar связан с уже   рассмотренным  классом  Date следующими                       методами: 



 •    Date getTime()     — возвращает   текущее   время, представленное   объектом   кален- 

    даря   в виде  объекта   класса Date; 

 •    void  setTime(Date  date)   — устанавливает          календарю   текущее   время,   представ- 

    ленное объектом   класса Date. 


----------------------- Page 122-----------------------

                                                                         Класс TimeZone          1 2 3 



Преобразование   времени   и даты   осуществляется             с помощью   методов       get  и set : 



•     i n t get (int   field )  — возвращает  преобразованно е  значени е  одного   из  парамет- 

    ров   календаря .  Параметр   определяется         аргументом      field .   Аргумент   file d   мо- 

    жет    п р и н и м а т ь  значени е  одно й  из   следующих     констант :   YEAR,  MONTH, DATE, 

    DAY_OF_WEEK,   HOUR_OF_DAY, HOUR, AM_PM, MINUTE, SECOND, MILLISECOND. Метод   возвраща- 

    ет   значени е  запрошенног о  параметра   в  виде   константы ,  определяющей              месяц 

    или день  недели , либ о числовое значени е запрошенной единицы времени . Кон- 

    станта   HOUR_OF_DAY определяет   значение  часа  в  24-часовом           формате , а констан- 

    та  HOUR — в   12-часовом .     Констант а   АМ_РМ используется        в  12-часовом     формат е 

    для   определения   значения   «до полудня»          (AM)  или «после   полудня» (РМ); 



•    void   s e t d n t  field ,  int  value)  — устанавливает   значени е   value   в  один   из  пара- 

    метров   даты    или времени , который   определяется   аргументом             field .  Параметр 

    DAY_OF_WEEK   не может   быть      установлен . 



Таким   образом ,  чтобы   из объекта   класса   Date получить   дату   и время   в  виде       удоб- 

ных констант , необходимо  получить  объект  календаря , присвоить ему время , пред- 

ставленное   объектом   Date, и лиш ь  затем   интерпретировать   его, используя              методы 

класса   Calendar .   Так ж е  как и в   классе   Date,   календарь   может    работать    со  време- 

нем, представленным   количеством   миллисекунд ,   прошедших   от начала   70-х: 



•    long getTimelnMi11 is()     — возвращает   время календаря  в   миллисекундах; 



•     void  setTimelnMi Hi sdong  mill is)      — устанавливает       время   календаря,  заданное 

    параметром mi 11 is . 



Класс Calendar организует   сравнение времени двух календарей с помощью следу- 

ющих   методов: 



 •    boolean   equals (Object   obj)  — возвращает       значение  true,  если   аргумент      obj  не 

    равен   null,  и   представляет   то  же  самое   время, что   и  вызывающий   объект   ка- 

    лендаря; 

 •    boolean   before(Object    when)  — возвращает   значение true, если   время,  представ- 

    ленное   вызывающим   объектом,  ранее  времени, представленного                     аргументом 

    obj; 

 •    boolean after(Object     when) — возвращает значение true, если время, представлен- 

    ное   вызывающим объектом, позднее времени, представленного аргументом   obj. 



Кроме того, класс Calendar   поддерживает   работу   с часовыми поясами: 



 •    TimeZone getTimeZone()   — получить   часовой  пояс календаря; 



 •    void  setTimeZone(TimeZone value)  — установить  часовой пояс календаря. 



 Класс   TimeZone 



 Для   полноты   картины  предоставляемых   реализацией J2ME   возможностей, рас- 

 смотрим абстрактный класс TimeZone, представляющий часовой пояс. Класс TimeZone 

 позволяет узнать, какой часовой пояс установлен на аппарате, выполняющем про- 

 грамму,  и осуществляется   ли переход   на летнее  время. Класс TimeZone имеет   сле- 

 дующий   набор   методов: 



 •    String[]   getAvailablelDsO      — возвращает   аббревиатуры          всех   поддерживаемых 

     аппаратом часовых   поясов в виде  массива  строк; 


----------------------- Page 123-----------------------

1 24         Глава  1 1 .   Работа со временем, датой и календарем 



•    TimeZone getDefaultC)       — возвращает часовой пояс, используемый аппаратом по 

    умолчанию; 

•    String  getID() — возвращает аббревиатуру   текущего  часового пояса, использу- 

    емого аппаратом; 

•     i nt getRawOf f set () — возвращает смещение данного часового пояса относительно 

    времени по  Гринвичу; 



•    TimeZone getTimeZone(String   ID) — получить  объект часового пояса по его аббре- 

    виатуре; 



•     boolean   useDaylightTimeO      — возвращает   значение  true,  если   часовой   пояс ис- 

    пользует   переход   на летнее  время (DST — Daylight   Savings Time). 



 Класс BirthdayFilter 



Теперь мы располагаем  всеми необходимыми средствами для  реализации функции 

напоминания о дне рождения в нашей записной книжке. Для  выбора именинников 

из  хранилища записей реализуем  класс фильтра Bi rthdayFi lter , который будет срав- 

нивать даты рождения с текущей датой и выбирать лишь интересующие нас записи: 

// класс фильтра     записей по дням рождения 

private class BirthdayFilter implements RecordFilter { 

     // метод сравнения     записей 

     public boolean matches(byte[ ] candidate ) { 

          // преобразовать запись в байтовый       поток 

         ByteArraylnputStream bais • new ByteArraylnputStream(candidate) ; 

          // создать поток , поддерживающий     чтение по типу 

         DatalnputStream dis - new DatalnputStream(bais) : 

          // день рождения 

          Date birthDate = new DateO : 

         try { 

              // считать строковые параметры записи 

              dis.readUTFO : 

              dis.readUTFO ; 

              dis.readUTFO : 

               // считать дату рождения 

              bi rthDate.setTime(di s.readLong()) ; 

          } 

          catch   (IOException ioe ) { return false ; } 



          // получить два календарях       текущей датой 

    ,      Calendar rightNow » Calendar.getlnstanceO : 

          Calendar birthday - Calendar.getlnstanceC) ; 

          // установить дату рождения именинника 

          birthday.setTime(birthDate) : 

          // сравнить день и месяц рождения с текущей датой 

          if(rightNow.get(Calendar.DAY_OF_MONTH) == birthday.get(Calendaг.DAY_OF_MONTH ) & & 


----------------------- Page 124-----------------------

                                                                            Класс BirthdayFilter           1 2 5 



               rightNow.get(Calendar.MONTH) ~           birthday.get(Calendar.MONTH) ) 

               return true : 

          else 

               return    false ; 



Реализуем функцию, которая будет фильтровать хранилище записей по текущей 

дате и возвращать имя человека, у которого сегодня день рождения, или значение 

null, если таковых не имеется: 

//   метод SearchBirthday      возвращает   строку с именем     именинника ; 

//   если в этот день именинников        нет . то возвращает    null 

private String SearchBirthday( ) { 

     String    name = null ; 

     try { 

          //   создать объект фильтра      по дню рождения 

          BirthdayFilter      filter = new    BirthdayFilterO ; 

          //   получить список     записей с подходящим      днем  рождения 

          RecordEnumeration       re = recordStore.enumerateRecords(filter .         null ,  false) ; 

          //   получить   ID записи 

          int   id • re.nextRecordldO ; 

          //   получить   запись по ID 

          byte[ ] record = recordStore.getRecord(id) : 

          //   преобразовать    запись в байтовый     поток 

          ByteArraylnputStream       bais = new ByteArrayInputStream(record) ; 

           //  создать   поток ,  поддерживающий    чтение   по типу 

          DatalnputStream      dis - new DatalnputStream(bais) : 

           //  считать из   потока строку с именем 

          name = dis.readUTFO : 

      } 

     catch(RecordStoreException         rse ) { } 

     catch(IOException       ioe ) { } 

      //  вернуть   имя  именинника 

      return name ; 

 } 

 Все, что нам остается сделать, это вызвать в стартовом методе приложения sta rtApp() 

 функцию  поиска именинников и при необходимости   вывести                                соответствующее 

 напоминание: 



 //  создать список     имен 

 BuildNameListO ; 

 //  поиск   именинника 



 String   name   •   SearchBirthdayO ; 

 //  если   имя  найдено 

 if(name!=null) { 

      //  создать форму     напоминания 


----------------------- Page 125-----------------------

1 26         Глава   11 .  Работа  со временем, датой  и календарем 



     Form   remindForm   =   new  Form("Reminder"):• 



    //   добавить  напоминание   в форму 



     remindForm.append(name     +  "  has  a  birthday  today!") : 



    //   добавить  команду   возврата 



     remindForm.addCommand(ok); 



     remindForm.setCormiandListener(this); 



     //  отобразить   форму 



    di splay.setCurrent(remi ndForm); 



}  else 



     //  отобразить   список   имен   на   экране 



     display.setCurrent(nameList); 



                                  Теперь все готово. При первом запуске нужно добавить 

                                  контакт с текущей   датой  рождения, а затем заново за- 

                                  пустить приложение. Если все запрограммировано вер- 

                                  но,  то   при  запуске   появится   сообщение, показанное 

                                  на рис. 11.3. 

                                   Основная недоработка  программы заключается   в том, 

                                  что если у нескольких ваших знакомых день рождения 

                                  в   один  день,  то  напоминание все  равно   будет   только 

                                  одно.  Неплохо   было   бы  также  добавить            возможность 

                                  удаления       и редактирования   контактов. Напоминание 

                                   можно красочно оформить, а также с легкостью высчи- 

                                  тать  возраст   именинника. Все эти детали,  а также лю- 

                                   бые другие капризы вашей фантазии, остаются   вам на 

                                   самостоятельную         доработку. 



                                   Итак,  в   этой   главе  мы  изучили   работу         с датой,  вре- 

                                   менем, календарем   и часовым   поясом. Мы   рассмот- 

                                   рели   лишь  один  пример   из  всего   многообразия   воз- 

                                   можных       приложений,   имеющих            дело    со  временем 

                                   и  датой.   Часовой   пояс, например, можно   использо- 

                                   вать   для   интернационализации  программ   и   автома- 

                                   тического  выбора языка интерфейса. А теперь може- 

Рис. 11.3. Телефон напомнит                                        л                      - 

  вам, кого надо поздравить        т  е  сделать   паузу   и опробовать   изученный   материал. 



       с днем рождения              В добрый   путь! 


----------------------- Page 126-----------------------

 Глава 12 



 Работа над ошибками 



С обработкой  исключительных   ситуаций мы уже  сталкивались  неоднократно во 

время   реализации  наших  нехитрых   примеров. Пришло время   подробнее  разоб- 

раться со структурой и механизмом  формирования исключений. 

Во время выполнения программы  может  случиться   много разных  неприятнос- 

тей, таких  как деление на ноль, выход индекса массива за допустимые   пределы, 

неверное имя загружаемого файла и масса других. В функционально-ориенти- 

рованных   языках   программирования, таких   как  С или  Pascal,  такие   ситуации 

обрабатывались       с   помощью    многочисленных 

операторов   if...else, которые значительно   уве-       Pfe  .lools .  Help 

личивают   как объем,  так  и время выполнения 

программы. В объектно-ориентированных язы- 

ках реализован другой подход к обработке вне- 

штатных   ситуаций. 



                                                                  a—»»TestSarnple ™ 

 Класс Exception                                                 java/lang/ArithrneticExce 

                                                                 ption 



При возникновении исключительной   ситуации 

система   формирует      соответствующий       объект- 

исключение,  содержащий   сведения   об ошибке, 

и передает   его на обработку   программе, в кото- 

рой произошла ошибка. Если программа не пре- 

дусматривает особую обработку данного исклю- 

чения,  то   объект   передается    системе,  которая 

останавливает   выполнение программы   и выво- 

дит на экран тип сформированного исключения. 

 При делении на ноль, например, это происходит 

 как показано на рис. 12.1. 



 Остановку   выполнения программы можно пре- 

 дотвратить,  реализовав   в программе   обработку 

 возможных  исключений. Как мы уже  видели на 

 примерах,     команды,   потенциально       формиру- 

 ющие  исключения, заключаются           в блок try{...}, 

 а  действия,  которые   следует   выполнить   в ис- 

 ключительной       ситуации,  заключаются      в  блок  Рис.  12.1 . В случае деления на ноль 

 catch (Exception   e) {...}. В качестве аргумента опе-         программа прерывается 


----------------------- Page 127-----------------------

128          Глава   12 . Работа над ошибками 



ратор catch принимает объект исключения определенного типа в зависимости от 

ситуации, которую   мы хотим   обработать.   После одного  блока  try   может   следо- 

вать несколько блоков catch с обработкой разных исключений. Управление будет 

передано первому блоку, аргумент которого будет соответствовать  сформирован- 

ному исключению. Таким образом, синтаксис блока проверки и обработки исклю- 

чений в общем случае выглядит   так: 



t r y  {  . . . 



} 

c a t c h C . . . )  { . . . } 



c a t c h C . )  { . . . } 



Остановимся на объектах  исключений, которые формируются системой. Все они 

наследованы  от одного и того же класса  Exception, который, в свою очередь, по- 

рожден   от   класса   Throwable.  Только  объекты,  порожденные   от   класса   Throwable, 

могут быть  обработаны   в блоке  catch  или искусственно   сформированы  с помо- 

щью оператора throw, который мы рассмотрим немного позже. 

 Класс Throwabl e и все его расширения содержат два  конструктора: 

 •    Throwablе()   — конструктор по умолчанию; 

 •    ThrowableCString        message) — объект   исключения   будет  содержать                 текстовую 

     информацию, переданную  в строке message. 

 Класс Throwable   несет дополнительную   информацию о сформированном исклю- 

чении, которую можно получить  с помощью следующих   методов: 

 •    String    getMessage() — возвращает  строку с текстовой информацией об исклю- 

     чительной  ситуации. Если объект был создан пользователем   с помощью кон- 

     структора  по умолчанию, то возвращает значение nul 1; 

 •    String    toStringO   — возвращает   строку   с текстовым   описанием  исключения, 

   .   состоящим из имени класса сформированного исключения, двоеточия   (разде- 

     лителя) и текста, возвращаемого  методом getMessage; 

 •     void   printStackTraceO   — выводит   в область  сообщения об ошибках  название 

     класса и метода, сформировавших исключение, со всей предыдущей   историей 

     вызовов, включая  внутреннюю реализацию. Первая выводимая строка содер- 

     жит результат работы метода toStri ng. 

 Система  определяет   два   потока  вывода   сообщений: поток  вывода  информации 

 и поток вывода сообщений об ошибках. Потоки вывода доступны из любой точки 

 программы. Мы можем писать туда любую полезную  информацию, включая со- 

 держимое  переменных, с помощью системных методов:   System.out.printlnCString 

 text)   и  System.err.println(String           text).  В нашем случае сообщения обоих  пото- 

 ков появятся в основном окне WTK.  При запуске приложения на реальном аппа- 

 рате никаких сообщений мы можем и не увидеть. 

 Теперь, если деление на ноль поместить в блок try, а в блоке catch вывести  в сис- 

 темную   область   всю  информацию о сформированном  исключении                              следующим 

 образом: 



 int   n=0: 

 try  { 


----------------------- Page 128-----------------------

                                                                               Класс Exception           129 



     int   к •  5/n: 

}   catch(Exception   exc) { 

     System.out.printlnC'Get       Massage:   " + exc.getMessageO): 

     System.out.printlnC'To       String :  " + exc.toStringO) : 

     System.err.printlnC'Print        Stack   Trace:"): 

     exc.pri ntStackTrace(): 

} 

System.out.printlnC'Continue         program...") ; 

то в области вывода системных сообщений мы увидим такую картину (рис. 12.2). 



           •Ш2МЕ Wireless Toolkit - TestSample 



            File  Ed*  Project 



           I  V-NewProject..  | fig Open Protect  [.  9, , Settings ...  § ' Build V t  Run  F > Cle*r  Console 



           ,  Device  [L-laultGrayPhone 



           Gee   Message:   null 

          !: Го  String :  java.lang.ArithmeticException 

           Print   Stack   Trace: 

           java . lang. ArithmeticException 

                     at   TestSample.startApp(+2) 

                     at   javax.microedition.midlet.HIDletProxy,startApp(+7) 

                     at   com.sun.midp.midlet.Scheduler.schedule(+225) 

                     at   com.sun.midp.dev.DevMIDletSuitelmpl.schedule(+7) 

                     at  com.sun.midp.Hain.runLocalClass(+20) 

                     at  com.sun.midp.Hain.main(+68) 



           Continue    program... 



    Рис.   12.2 . В основное  окно WTK   может быть выведена подробная  информация об ошибке 



 Заметим, что программа после формирования исключения и вывода сообщений в 

 системную область не зависла, не упала, не вылетела, а спокойно продолжила свое 

 выполнение с команды, следующей за обработкой исключения. 

 Обработка  некоторых  исключений, называемых  проверяемыми, является обяза- 

 тельной для  программиста.  Ее отсутствие   приведет к ошибке на этапе компиля- 

 ции.   Например, если убрать обработку   исключения при открытии хранилища за- 

 писей, то программа не откомпилируется и выдаст следующую ошибку: 

 AddressBook.Java:44 :      unreported   exception    javax.microedition.rms.RecordStoreException : 

 must be caught or declared to be thrown 

 recordStore • RecordStore.openRecordStore("Address-Book" .              true) : 

 Рассмотрим  некоторые  наиболее распространенные  исключения,  формируемые 

 реализацией J2ME и уже использованные нами в примерах. Основная группа ис- 

 ключений, наследованных   от класса   RuntimeException,  является   непроверяемой, 

 поэтому к ним нужно относиться особо внимательно. В наших примерах обработ- 

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

 •     Ari thmeti cExcepti on — формируется   при ошибках в арифметических операци- 

     ях, например при делении на ноль; 


----------------------- Page 129-----------------------

130        Глава   12 . Работа над ошибками 



•    ArrayStoreException   — формируется   при попытке записать  в массив данные не- 

   верного   типа; 



•    EmptyStackException   — формируется      во   время   работы  со   стеком   при   попытке 

   получения   данных, когда  стек   пуст; 



•    111 еда 1 Argument Except i on — возникает при передаче  в метод некорректного зна- 

   чения аргумента.   Если в качестве  аргумента   предусмотрена   константа, то  луч- 

   ше пользоваться   ей в чистом  виде  во избежание  подобных   ситуаций; 



•    IndexOutOfBoundsException    — указывает   на то, что  используемый   индекс масси- 

    ва, вектора   или  строки выходит за  пределы   допустимых   значений; 



•    NegativeArraySizeException  — формируется   при попытке  создания   массива   от- 

    рицательной   длины; 



м    NullPointerException   — формируется   при попытке вызвать  метод  или модифи- 

    цировать   поле объекта,  значение которого  nul 1. 



Исключения,   сформированные   хранилищем             записей,  наследованы      от  класса 

RecordStoreException     и являются   проверяемыми,  поэтому   для   успешной  компи- 

ляции   всю  работу  с хранилищем   мы оформляли  в try...catch   блоки.   Возможные 

исключения       при   работе   с  хранилищем     записей    (Inval  idRecordlDExceotion, 

RecordStoreFul1Excepti  on, RecordStoreNotFoundExcepti on, RecordStoreNotOpenExcepti on) 

мы уже   рассмотрели   в соответствующей        главе,  немного забегая    вперед. 



Еще  одно   исключение,   InterruptedException,  которое   мы   использовали   при   работе 

с тредами, является прямым наследником класса Exception. Оно формируется  в слу- 

чае, когда один тред выводит другой из спящего или приостановленного состояния. 

Исключения,   наследованные        от   класса  IOException,  формируются    при   ошибках 

с процедурами      ввода-вывода: 



 •   Connect!onNotFoundException — цель соединения не найдена; 



 •   EOFException  — конец файла был достигнут  при работе  с потоком   ввода; 



 •    InterruptedlOException   — формируется     в   случае,  если   операция  ввода-вывода 

    была   прервана; 



 •   UnsupportedEncodingException     — данная  кодировка  не   поддерживается; 



 •   UTFDataFormatException — формируется при работе с Unicode, если символы стро- 

    ки не являются   символами  универсальной   кодировки. 



 В  рамках   данной  главы   мы  рассмотрели   не все  возможные   исключения.  В доку- 

ментации, без которой вам в любом случае не обойтись, в описании каждого   мето- 

да содержится   список всех возможных   исключений, которые могут быть сформи- 

рованы  при выполнении данного   метода. 



 Формирование исключений 



 Кроме существующих классов  исключений можно формировать собственные ис- 

 ключения, реализовав   свой  класс, наследованный  от  класса  Exception   или от  лю- 

бого  из его  многочисленных   потомков. 



 Рассмотрим формирование собственного исключения на примере программы каль- 

 кулятора,   которую   мы  разбирали   в  одной  из  прошлых   глав.  Оформим     проверку 


----------------------- Page 130-----------------------

                                                                   Формирование  исключений                    1 3 1 



наличия   всех   аргументов   в форме  ввода   в виде  формирования исключения.  Для 

этого  реализуем   класс исключения                  MissingArgException: 

class   MissingArgException       extends    Exception   { 

      // текстовое сообщение        исключения 

     private String msg : 

      // конструктор      без  параметров 

     MissingArgExceptionf) {        msg - null; } 

      // конструктор ,     принимающий    строку 



     MissingArgExceptionCString          s t r )  {  msg  -  s t r ; } 



      // метод    возврата   текстового сообщения 

     public   String   toString(){ 

           return   "Exception (" + msg +        "  )  " ; 



Исключения           активизируются           с   помощью   оператора          throw,   который       позволяет 

выбрасывать   любые  исключения иерархии  класса Throwable   из любого  места  про- 

граммы. Команда throw               new   Ari thmeticException()   приведет   к такому   асе эффекту, 

как    если бы здесь произошло деление на ноль, и обработка этого исключения пой- 

дет    своим   чередом.   Тот   факт,  что   метод   формирует                исключение,   обозначается 

в   заголовке  метода   ключевым   словом  throws   и названием класса  исключения. Реа- 

лизуем   метод  проверки наличия аргументов   в форме: 



void checkArgsO        throws MissingArgException { 

      // проверить заполнение        всех полей    ввода 

      // сформировать      исключение в случае отсутствия         аргумента 

      if(argl.size()-=O) 

           throw new MissingArgExceptionC"Missing            First Argument") : 

      ifCarg2.size()==0) 

           throw new MissingArgExceptionC'Missing            Second   Argument") ; 

      if(action.size()==0) 



           throw   new  MissingArgExceptionC'Missin g        Actio n  S i g n " ) ; 



 } 



Проверка заполнения формы ввода в методе commandActi on будет выглядеть   теперь 

следующим          образом: 

//   проверить   заполнение   всех     полей  ввода 

try   { 



     checkArgsO; 

 }  catchCMi ssingArgException   maexc)       { 

      //  ошибка:   не   все   поля  ввода   заполнены 

      resForm.append(maexc.toSt r i  ng()) : 

      //  отобразить  сообщение 

      display.setCurrenttresForm): 

      //  выйти 

      return; 


----------------------- Page 131-----------------------

132        Глава   12 . Работа над ошибками 



# ( i ) Nokia 72Ю *аж;                Если запустить   программу   и вызвать   команду 

 Ed?  lools  Help                     вычисления результатов, не задавая аргументов, 

                                      то сформируется соответствующее исключение, 

                                      а мы увидим на экране сообщение (рис. 12.3). 

                                      Для отображения сообщения об ошибке мы вос- 

                                       пользовались обычной формой. На самом деле 

                                      существует  специально  предназначенный для 

         a—-"Result                   этого класс. 

         Exception (Missing 

         First   flrgument) 



                                       Класс Alert 



                                       Неважно, пользуется   ли  программой  ваш друг 

                                       или человек, заплативший за это деньги, — си- 

                                       туация, когда приложение вылетает, оставляя за 

                                       собой лишь  непонятное сообщение  на экране, 

                                       считается дурным  тоном в программировании. 

                                       Большинство исключений не интересует конеч- 

                                       ного пользователя программы, но иногда нужно 

                                       дать понять, что его действия некорректны. 

                                       Поскольку  системная область  сообщений недо- 

                                       ступна пользователю, существует цивилизован- 

                                       ный способ вывода подобной информации. Класс 

                                       Alert  представляет   собой сообщение, состоящее 

                                       из изображения и текста, которое  отображается 

                                       на экране в течение заданного количества време- 

                                       ни, после чего автоматически происходит смена 

                                       экрана. Класс Alert  используется для информи- 

      Рис.  12.3 . При  отсутствии     рования пользователя об ошибках и возникших 

       аргументов формируется 

       исключение  и выводится         исключительных ситуациях, а также для кратко- 

        сообщение об ошибке            временных   информационных сообщений о ре- 

                                       зультатах выполнения операций. 

Время демонстрации сообщения может   быть  бесконечно большим. Тогда  сооб- 

щение будет отображаться   на экране до тех   пор, пока пользователь  вручную не 

закроет его. Аналогичная   ситуация возникает, если большое количество инфор- 

мации требует прокрутки экрана. Остановимся на методах  класса Alert: 

•    AlertCString   title)   —конструктор создает пустое сообщение с заголовком title ; 

•    AlertCString   title .  String   alertText,  Image alertlmage.   AlertType  alertType) — 

    конструктор   создает   сообщение с заголовком   title ,  текстом   alertText       и кар- 

    тинкой а 1 еrt Image. Параметр а 1 ertTyре задает тип сообщения, представленный 

    классом А1 ertType, который мы еще рассмотрим подробнее. Параметр а 1 ertType 

    может принимать значение nul 1 для создания обычного сообщения; 

•    void   addCommand(Command   cmd) — разумное  объяснение наличию этого   метода 

    придумать сложно, поскольку класс Al ert не поддерживает добавление команд. 

    При вызове метода будет сформировано исключение 111 еда 1 StateExcepti on; 

•     int   getDefauHTimeoutC) — возвращает значение времени демонстрации сообще- 

    ния   в миллисекундах,  устанавливаемое   по умолчанию   при создании  объекта 


----------------------- Page 132-----------------------

                                                                    Класс AlertType      1 3 3 



   класса Al ert . Может также возвратить значение константы FOREVER, если по умол- 

   чанию сообщение демонстрируется   неограниченное количество времени; 



    Image   getlmageO   — возвращает   объект   картинки, установленной  для   сообще- 

   ния, или значение nul 1, если картинка не  установлена; 



    String   getStringO   — возвращает   текст данного сообщения; 



    int  getTimeoutC)  — возвращает время демонстрации данного сообщения в мил- 

   лисекундах   или константу FOREVER; 



   AlertType   getType()   — возвращает тип данного сообщения в виде объекта  клас- 

    са AlertType; 



    void   setCommandListener(CommanciListener   1)  — также.какиметодаййСоттагк!, при 

    вызове всегда  формирует  исключение  111 egal StateExcepti on; 



    void   setlmage(Image   img)  — устанавливает   новую  картинку для сообщения; 



    void   setString(String   str)   — устанавливает   текст str   данному сообщению; 



    void   setTimeoutC int  time)  — устанавливает   новое время демонстрации сообще- 

    ния. Время задается  аргументом  time в миллисекундах   или константой FOREVER 

    класса Alert  для   неограниченного времени демонстрации сообщения; 



    void   setType(AlertType     type)   — задает  новый  тип   сообщения   в   виде  объекта 

    класса AlertType. 



 Класс AlertType 



Класс Al ertType задает тип сообщения класса Al ert . Тип сообщения определяет шаб- 

лон, в котором будет продемонстрировано сообщение. Константы класса AlertType 

определяют  следующие   шаблоны сообщений: 



•    ALARM — события, привязанные к определенным моментам, например напоми- 

    нание о назначенной встрече; 



•    CONFIRMATION   — подтверждение   успешных   результатов   действий   пользователя, 

    например  «Сообщение   отправлено»; 



•    ERROR — сообщение об ошибке в результате действий   пользователя, например 

    «Недостаточно   памяти для сохранения   данных»; 



 •    INFO — отображение  информационных сообщений; 



 •   WARNING — предупреждение  об опасных действиях  пользователя, например «Все 

    данные будут   уничтожены». 



Класс   AlertType   содержит      лишь   два   метода:   конструктор,   не   принимающий 

аргументов,   и   метод    воспроизведения   звукового       сигнала,   связанного   с   типом 

сообщения: 



 •    boolean  playSound   (Display   display)  — воспроизводит  звук,   соответствующий 

    данному   типу   сообщения. В   определенных   устройствах         некоторые звуки  мо- 

    гут   отсутствовать.    Возвращает    значение  true,  если   звук   был  воспроизведен, 

    и   false—   в   противоположном   случае.   Метод   playSound      может   быть   вызван 

    и без использования сообщений класса Alert, например для   организации  зву- 

    ковых  сигналов во время игры. 


----------------------- Page 133-----------------------

134         Глава   12 . Работа над  ошибками 



# 0  )  Nokia 7210 SDK                    Внешний вид и звуковой сигнал сообщений каж- 

                                          дого   типа  зависят   от конкретной  модели   теле- 

                                          фона. Например, на телефоне Nokia сообщение 

                                          информационного типа выглядит, как показано 

                                          на  рис. 12.4. 

                                          Класс Al ert является потомком класса Screen, то 

                                          есть экраном, готовым к демонстрации. Посколь- 

                                          ку смена экрана по истечении тайм-аута  проис- 

                                          ходит автоматически, то демонстрация сообще- 

                                          ния   осуществляется   с помощью особого  метода 

                                          менеджера дисплея (объекта класса Display), ко- 

                                          торый  принимает объект   класса Alert, а  также 

                                          объект следующего   экрана: 

                                          •      void   setCurrent     (Alert   alert,    Displayable 

                                               nextDi spl ayabl e) — устанавливает в качестве 

                                               текущего экрана сообщение, представленное 

                                               аргументом al ert, и задает экран nextDi spl ay- 

                                               abl е, который станет текущим после закры- 

                                               тия сообщения. Объект   nextDi spl ayabl e не 

                                               может быть объектом класса Al ert или иметь 

                                               значение null. 

                                          Рассмотрим использование класса Al ert на при- 

                                          мере все той же программы   калькулятора. До- 

                                          полним программу   проверкой деления на ноль, 

                                          которую   мы проигнорировали,  из-за чего при- 

                                          ложение в такой ситуации вылетало с ошибкой. 

                                          В  блоке  catch   перехватим   исключение  ошибки 

 Рис.  12.4 . Пример информационного      в  арифметической   операции, создадим               объект 

               сообщения                  сообщения об ошибке с неограниченным време- 

                                          нем демонстрации и отобразим его, указав основ- 

                                          ную форму ввода в качестве следующего  демон- 

                                          стрируемого экрана: 



import   javax.microedition.lcdui.Alert; 

import   javax.microedition.lcdui.AlertType; 



try 



     switch(act[O]) { 

          //  выполнить  действие в соответствии 

          // с символом   операции 

         case '+' : 

              res = first + second : 

              break : 

         case '-': 

              res " first - second : 

              break : 

         case '*': 


----------------------- Page 134-----------------------

                                                                             Класс AlertType         135 



              res = first * second ; 

              break : 

         case 'У' : 

              res • first / second : 

              break; 

         default : 

              // ошибка : некорректный     символ   операции 

              resForm.appendCIllegal       Operation") : 

              // отобразить сообщение 

              display.setCurrent(resForm): 

              //   выйти 

              return : 

     } 

     //  преобразовать    результат в строку и добавить в форму 

     resForm.append((new Integer(res)).toStringO) : 

     // отобразить результат 

     displ ay.setCurrent(resForra) : 



// блок перехвата     арифметического    исключения 

}  catch   (ArithmeticException    aexc ) { 

     // создать сообщение об ошибке 



     Alert   aler t  =   new AlertCError","Divid e by   zero" .nul l .AlertType.ERROR): 



     //  установить бесконечное     время   демонстрации 



     alert.setTimeout(Alert.FOREVER); 



     //  установить сообщение     в   качестве  текущего  экрана 



     di  spl ay.setCurrent(alert.form) ; 



Аналогичным  образом вы самостоятельно можете об- 

работать ошибку некорректного символа арифметиче- 

ского   действия.   Попробуем            программу       в   действии 

и разделим что-нибудь на ноль. Теперь вместо останов- 

ки   программа  продолжает   свою работу, отображая  на 

экране сообщение, показанное на (рис. 12.5). 

После нажатия клавиши  Done программа возвращает- 

ся   к первоначальному   экрану ввода. Заметим, что до- 

бавление  и обработку   этой команды мы  в программе 

не   предусматривали, она появилась в результате уста- 

новки объекту   класса Alert бесконечного времени де- 

монстрации. В случае установки фиксированного вре- 

мени демонстрации  команда  Done не отображается   и 

смена экранов происходит автоматически по истечении 

заданного  тайм-аута. 

                                                                            Рис . 12.5 . Теперь при де- 

До  сих   пор в  примерах   мы лишь перехватывали   воз-                    лении на ноль программа 

                                                                             не падает , а сообщает , 

можные исключения, чтобы программа компилирова-                                 что пользователь 

лась  и не вылетала   при возможных  ошибках. Внима-                               был не прав 


----------------------- Page 135-----------------------

1 36       Глава   12 . Работа над ошибками 



ние на этом не заострялось, чтобы не отвлекаться от основной темы. В действи- 

тельности же грамотная обработка исключений и вовремя предпринятые действия 

помогут решить много проблем. Также неверно оставлять блок обработки исклю- 

чения   catch   пустым,  как мы  это делали   в  примерах. Даже   простое  сообщение 

в области ошибок принесет немало пользы в процессе отладки программы. 

При обработке нескольких типов исключений в одном try-блоке следует быть вни- 

мательными, не перехватывается ли необходимое исключение в одном из блоков 

catch, расположенных выше. Так, если в первом же блоке catch   перехватывается 

исключение самого общего типа Excepti on, то до остальных блоков дело не дойдет 

никогда. Немного практики и терпения, и вы научитесь лавировать меж подвод- 

ных камней объектно-ориентированных программ, а главное, помните, что акку- 

ратность — главный залог успеха программиста. 


----------------------- Page 136-----------------------

 Глава 13 



 Расширение 

функциональности с помощью API 



На   данный   момент   мы  рассмотрели  большинство   возможностей     реализации 

MIDP    1.0  мобильного языка J2ME. Возможности  эти, прямо скажем,  небезгра- 

ничны, а порой просто разочаровывают. Это и есть плата за универсальность  плат- 

формы и совместимость  приложений для многих моделей телефонов. Расширять 

возможности J2ME можно в двух направлениях: с помощью профайла МШР 2.0 

и с использованием специальных классов от производителей  конкретной модели 

телефона. Оба варианта значительно сокращают ряд моделей, где наша програм- 

ма будет чувствовать себя комфортно, то есть работать  как следует. 



Возможно, в ближайшем  будущем телефоны с поддержкой   профайла  MIDP          2.0 

не будут дорогостоящей   редкостью, что позволит   освоить   новые горизонты мо- 

бильного программирования. А пока что подавляющее большинство моделей те- 

лефонов на душу населения заключено  в жесткие рамки профайла MIDP           1.0, на 

который и сделан основной акцент в этой книге. 

В этой главе мы остановимся подробнее на втором варианте расширения функцио- 

нальности, то есть на API-функциях компаний-производителей. API   (Application 

Programming   Interface,  интерфейс программирования   приложений) является на- 

бором классов и методов для конкретных моделей определенного  производителя. 

Мы рассмотрим несколько разных API и начнем с самой распространенной моде- 

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

Nokia 7210  уже включает   в себя все необходимые  классы API, и если вы следова- 

ли инструкциям и устанавливали   все в указанные в первой главе папки, то J2ME 

Wireless Toolkit обнаружит   все, что нужно, самостоятельно. Расширять мы будем 

нашу любимую «Змейку», в которую добавим несколько приятных моментов. 



 Nokia   API 



Посмотрим, чем же нас могут      порадовать   разработчики   от  корпорации Nokia. 

На   данный   момент   нам   предлагаются   два   пакета  классов  com.nokia.mid.ui 

и com. nokia .mid. sound, которые предоставляют   возможности полноэкранного ре- 

жима, низкоуровневого доступа  к экрану, управления звуком, подсветкой, а так- 

же  вибрацией. 

Помним, на чем мы остановились,  программируя   нашу   змею?   Название прило- 

жения, отображенное на экране, благополучно отъело у нас по 16 пикселов не только 


----------------------- Page 137-----------------------

138          Глава   13 . Расширение функциональности  с помощью API 



сверху, но и  снизу       — так, для   симметрии       (рис.  13.1). Экран   и так   не  слишком      ве- 

лик, чтобы во  время   игры  занимать   его  никому   не нужными   заголовками.                  Решим 

эту   проблему     в нашей   игре   с помощью   класса   Ful I Canvas из Nokia         API. 



Делается   это     не   просто,   а   очень  просто.   Импортируем        необходимый        нам   класс 

com. noki a. mi d. ui . Full Canvas и наследуем наш класс  Snake не от стандартного              Canvas, 

а от  расширенного        Full Canvas. 



import   com. noki a. mi ci. in   .FullCanvas; 



private   class  Snake   extends  FullCanvas   implements   Runnable   { 



Вот, собственно, и все. Класс Ful I Canvas сам наследован от стандартного класса Canvas, 

то есть   содержит   все  его  поля   и методы,  а это значит, что  больше   нам ничего пере- 

писывать   не придется.  Новых   методов   класс   Full Canvas   не реализует,   зато   опреде- 

ляет   несколько дополнительных             констант для   клавиш, специфичных для   телефо- 

нов   Nokia.   Например ,  константы   KEY_DOWN_ARROW,   KEY_LEFT_ARROW,   KEY_RIGHT_ARROW, 

KEY_UP_ARROW могут   быть использованы в процедуре   keyPressed(   int               keyCode) для   реа- 

                                           лизации   управления   джойстиком,   характерным 

      look   Help                          для   аппаратов  Nokia. 



                                            Итак, компилируем   исправленное  приложение . 

                                            Обратим  внимание на то, что в строке  Device (Ап- 

                                            парат) главного окна KToolbar должен быть выб- 

                                           ран   эмулятор   Nokia,  иначе  WTK          не  обнаружит 

                                            API-класса  и выдаст следующую            ошибку:  package 

                                            com.nokia.mid.ui       does   not  exist .  Запускаем   но- 

                                            вую   игру   и видим, что  мы, не особо   напрягаясь, 

                                           достигли       желаемого       результата      и  отвоевали 

                                            у   аппарата   128  х   32   = 4096 пикселов  (рис .   13.2). 



                                            Следующий        класс,  который   мы   рассмотрим,  — 

                                            DeviceControl, из того  же  пакета   com.nokia.mid.ui. 

                                            Он    дает   возможность       управления       подсветкой 

                                            и   вибрацией   телефона.   В   нашем   распоряжении 

                                            оказываются   следующие          методы: 



                                            •      void   flashLightsClong      duration)    —   мигание 

                                                 подсветки на протяжении durat i on миллисе- 

                                                 кунд.   Метод   возвращается         сразу   после   вы- 

                                                 зова   и   не  блокирует    исполняющий  тред         на 

                                                 время   выполнения эффекта ; 



                                            •      void   setLightsCint    num.   int   level)—управ- 

                                                 ление   иллюминацией   телефона .             Аргумент 

                                                 num представляет   номер источника света  для 

                                                 аппаратов с дополнительными   лампочками 

                                                 и   подсветками .   Фоновая         подсветка     имеет 

                                                 номер,  равный   нулю.   Аргумент           level  зада- 

     Рис.  1 3 . 1 . Во время игры  часть        ет  уровень   интенсивности света   в  диапазо- 

    экрана оказывается недоступной               не 0-100 . Значение 0 означает           выключение 


----------------------- Page 138-----------------------

                                                                              Nokia  API       139 



    света. В аппаратах   без поддержки уровня ин- 

   тенсивности света любое значение аргумента, 

    большее  нуля, означает включение света. Та- 

    ким   образом,   команда   погашения   фоновой 

    подсветки будет выглядеть так: Devi ceControl. 

    setLightsCO.    0); 

•    void   startVibra(int     freq.   long   duration) — 

    включает      вибрацию      телефона     на   duration 

    миллисекунд.  Аргумент  freq   задает         частоту 

    вибрации в диапазоне 0-100. Значение 0 мо- 

    жет быть использовано для определения, под- 

    держивается   ли вибрация вообще. Если при 

    нулевом   аргументе   будет сформировано ис- 

    ключение 111 egal StateExcepti on, значит теле- 

    фон в принципе не вибрирует.  Такое же ис- 

    ключение при другом аргументе  может  быть 

    сформировано, если система не разрешает ис- 

    пользование   вибрации.   Метод,   так   же  как 

    и flashLights,  возвращается   сразу   после вы- 

    зова и не блокирует   выполнение программы 

    на время вибрации; 



•    void    stopVibraO     — остановить   вибрацию, 

    организованную предыдущим  методом. Если 

    вибрация не активна, то метод не выполня- 

    ет   никаких   действий       и   не  формирует     ис- 

    ключений. 

Посмотрим на примере, как можно использовать                  Рис.  13.2 .   Класс FullCanvas предо- 

этот класс в нашей игре. Пусть в момент проиг-                  ставляет  в наше распоряжение 

рыша телефон забьется в конвульсиях вместе со                         весь экран целиком 

змеей, включив   вибрацию.  Первым делом  им- 

портируем   необходимый   класс  com.nokia.            mid. 

ui .Devi ceControl. Затем в методе paintCGraphics g), отрабатывающем  действия конца 

игры, включим вибрацию максимальной частоты  на 100   миллисекунд: 



import com.nokia.mid.ui.DeviceControl ; 



public   void   paintCGraphics   g)  { 



     //  если   поднят   флаг  конца  игры 

     i f  (  gameOverFlag  )  { 



         //  включить   вибрацию на   ЮОис. 

         DeviceControl.startViЬгаСЮО. 100): 



Заметим, что эмулятор умело подражает даже этой функции аппарата, и телефон, 

изображенный на экране компьютера, заметно   потряхивает. 


----------------------- Page 139-----------------------

140        Глава   13 .  Расширение функциональности с помощью API 



Используем   также   для   усовершенствования   нашей   игры   класс   Sound   из   пакета 

com. noki a.mid. sound. Для   начала рассмотрим методы  класса Sound: 

•    Sound(int  freq.  long   duration)  — конструктор; создает  звуковой  объект   часто- 

   той сигнала freq  герц, продолжительностью duration миллисекунд. В докумен- 

   тации   можно   найти  таблицу     перевода   частот  в   обычные   ноты, хотя  кому-то 

   это одинаково непонятно; 

•    Sound(byte[]   data,  int  type)  — конструктор; создает   звуковой   объект,  пред- 

    ставленный массивом байтов data. Аргумент type задает формат звуковых дан- 

    ных.  Класс  Sound определяет   две   константы  представления   формата : FORMAT_ 

    TONE — мелодия  в формате Nokia, FORMATJJAV   — мелодия  в стандартном   MIDI- 

    формате; 

•    int  getConcurrentSoundCount(int   type)   — возвращает   максимально  возможное 

    количество одновременно проигрываемых   мелодий типа  type; 

•    int  getGainO   — возвращает   уровень   громкости  звука   в  диапазоне 0-255  или 

    значение - 1 , если используется   громкость по умолчанию; 

•    int  getStateC)   — возвращает текущее состояние звукового объекта, представлен- 

    ное одной из трех констант: SOUND_PLAYING, SOUND_STOPPED, SOUNDJJNINITIALIZED -    звук 

    проигрывается, остановлен или не инициализирован соответственно; 

 •   int[ ]  getSupportedFormatsO   — возвращает   поддерживаемые   аппаратом   аудио- 

    форматы  в виде  массива  констант FORMAT_TONE, FORMAT_WAV. Если воспроизведе- 

    ние звука   не поддерживается   аппаратом, то возвращается   пустой  массив; 

 •   void  init(in t  freq,   long  duration)   — инициализирует звуковой   объект,  аргу- 

    менты аналогичны первому  рассмотренному   конструктору; 

 •   void   init(byte[ ]  data,   int  type)   — инициализирует звуковой объект,  аргумен- 

    ты аналогичны второму   рассмотренному   конструктору; 

 •   void  play(int  loop)   — воспроизвести звуковой  объект   loop раз. Если   аргумент 

    1 оор принимает значение 0, то объект будет воспроизводиться до тех   пор, пока 

    не будет   принудительно  остановлен. Во  время  воспроизведения объект   нахо- 

    дится   в состоянии SOL)ND_PLAYING; 

 •    void  releaseO   — освобождает   все  используемые   звуковым   объектом     ресурсы 

    и   переводит   его  в  состояние SOUNDJJNINITIALIZED. Если объект   находится   в со- 

    стоянии  SOUND_PLAYING, то сначала автоматически вызовется метод   stop() ; 

 •    void   resumeC) — возобновить воспроизведение остановленного звукового объек- 

    та.  Если  аппарат   поддерживает   такую   возможность, то воспроизведение нач- 

    нется с того  места, где  оно было остановлено; 

 •    void  setGaindnt     gain)  — установить    уровень   громкости   воспроизведения 

    в диапазоне 0—255; 

 •    void  stop О   — останавливает   воспроизведение звукового  объекта   и   переводит 

    его в состояние SOUND_STOPPED. 

 Хотим   теперь,  чтобы   при   поедании  очередного   сердечка   змея   издавала   радост- 

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

 объект   и   воспроизвести  его   в   методе   checkMove, контролирующем   передвижение 

 в заданном направлении : 


----------------------- Page 140-----------------------

                                                                              Samsung API           1 4 1 



import com.nokia . mi d.sound.Sound ; 



//  проверить   возможность передвижения 

// в точку с координатами      (хН.уН ) 

public   void checkMove(int xH . int уН ) { 



     //  проверить совпадение координат передвижения 

     // с координатами     сердца 

     if ( xH   == xHeart & & уН -» уHeart ) { 

         eatFlag = true : 

          //  создать  звуковой объект 

          //  частоты  1000  герц длиной   полсекунды 

         Sound beep = new Sound(1000.500) : 

          //  воспроизвести   звуковой объект 

         beep.play(l) : 

          // увеличить скорость 

          speed--; 



Осталось   запустить   приложение  на эмуляторе   и не забыть   включить колонки. 

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

эффект, прогнав в цикле воспроизведение звуков  разных   частот,  например вот 

таким образом: 

//   пройти в цикле частоты от 100 до 1000 герц с шагом 50 

forCint 1-100 : i<=1000 : i+=50 ) { 

     //  создать   звуковой  объект 

     Sound   beep = new Soundd ,50) ; 

     //  воспроизвести    звуковой  объект 

     beep.play(l) : 

 } 

Теперь у вас есть большое поле для экспериментов, чтобы начинить игру звуковы- 

ми    и тактильными спецэффектами. Кроме использованных  нами классов  пакет 

com.nokia.mid.ui  содержит также интерфейс DirectGraphics и класс DirectUti Is, рас- 

ширяющие возможности работы с графикой. Здесь нам предоставляется масса ин- 

тересных   методов, таких  как прорисовка и заливка треугольников   и полигонов, 

 прямой доступ к любой точке экрана, повороты изображения, смена форматов цве- 

товой гаммы и многие другие полезные вещи, интересные лишь Nokia-ориентиро- 

 ванным программистам. 



 Samsung AP I 



 На   остальных   производителях  мы не будем останавливаться   настолько подроб- 

 но.   Рассмотрим, для   сравнения, предоставляемые   ими  классы.  Разработчики 


----------------------- Page 141-----------------------

1 42         Глава   13 . Расширение функциональности с помощью API 



телефонов  Samsung   предлагают   пакет com.Samsung.util, который содержит  анало- 

ги уже  знакомых нам классов контроля звука, подсветки и вибрации, а также   два 

новых  класса, поддерживающих   создание и рассылку   SMS-сообщений. Рассмот- 

рим вкратце все предложенные  нам классы. 



Класс Audi oCl i p отличается   от Nokia-аналога  расширенным списком воспроизво- 

димых   форматов, а также  отсутствием   простой пищалки, задающей   частоту   тона 

в герцах.  Конструктор AudioClipdnt              type,  byte[]    audioData,    int  audioOffset,     int 

audioLength)      создает    звуковой   объект      из   байтового   массива   audioData, начиная 

с позиции audi oOf f set, длины audi oLength   байтов. Второй конструктор Audi oCl i p( i nt 

type.    Stri ng   f i 1 ename) получает   звуковой  объект  прямо из файла ресурса  по име- 

ни f 11 ename. Аргумент   type может принимать значения следующих  констант: TYPE_MMF, 

TYPE_MP3, TYPE_MIDI, с оговоркой, что на данный момент аппаратами   поддерживает- 

ся только TYPE_MMF. 



 Методы   play (int      loop,   int  volume), stopO, pauseO   и   resume О   служат   для   начала, 

прекращения, приостановки и возобновления воспроизведения звукового  объек- 

та   соответственно.   Метод   boolean         isSupportedO     возвращает   значение  true,  если 

такая технология   воспроизведения звука  поддерживается   аппаратом. 



                                                                                                  r 

 Класс   Vibration     заведует    вибрацией   телефона.   Его   метод        boolean    isSuppo ted() 

аналогичен  такому   же  методу   класса AudioClip,  а методы   start (int               duration,    int 

 strength)   и stop()   начинают и прекращают вибрацию соответственно. 



 Класс LCDLight занимается лишь фоновой подсветкой, так как других источников 

 света Samsung  не предусматривает.  Методы bool ean   1 sSupported(), on(int   durati on) 

 и of f ()  говорят   о своих действиях   сами за себя.   • 



 Рассылка  SMS  поддержана   в Samsung   API двумя   классами: первый класс, SM,  со- 

держит      все   необходимые       методы   для   формирования   сообщения, а   второй, SMS, 

 предоставляет   лишь  два   метода:  уже   набивший оскомину   boolean                  isSupportedO, 

 а также send(SM  sin), посылающий сообщение адресату.  Конструктор SM(String   dest, 

 String   callback,    String   textMessage)    создает объект сообщения для номера dest   от 

 номера call back   с текстом textMessage.   Методы String            getCallbackAddressO,        String 

 getDataO,  String      getDestAddressO,       void   setCallbackAddress(String         address),    void 

 setData(String      textMessage),     void   setDestAddress(String        address)   предназначены 

 для получения  и установки  трех   параметров сообщения, рассмотренных  в конст- 

 рукторе. Обратим внимание на интересный параметр номера отправителя са 11 back. 

 Не  хотят   ли  они сказать, что  мы  с телефона  можем  послать   SMS  от чужого   име- 

 ни?  В документации  говорится, что данный параметр не поддерживается, однако 

 на форуме умельцев   с Самсунгами я уже   видел ссылку  на подобную   программку. 

 Хотя   не знаю, работает   ли она: у  меня, как вы уже,  наверное, догадались,  Nokia. 



 При   попытке   найти   в   Интернете   эмуляторы            телефонов   Samsung,        поддержива- 

 ющие вышеописанные классы, обнаружилась занятная вещь под названием Samsung 

 JaUmi   Wireless   Toolkit.  Дизайн   программки   оказался   до   боли   знакомым, за  не- 

 большим  отличием  списка эмулируемых                 аппаратов (рис.  13.3). 



 В меню About обнаружилась следующая надпись: «Copyright  © SAMSUNG Electronics 

 2003.  All   rights   reserved».  Интереса ради  заглянул   в аналогичный пункт  меню при- 

 вычного  нам J2ME   Wireless  Toolkit:  «Copyright            © 2002  Sun   Microsystems,   Inc., 90 1 

 San Antonio  Road, Palo Alto,  California   94303,  U.S.A.  All   rights  reserved». Видим, что, 


----------------------- Page 142-----------------------

                                                                                 Samsung API            1 4 3 



оказывается, ничего зазорного в использовании чужого кода нету. А если серьезно, 

следите за соблюдением авторских прав в производимых и распространяемых вами 

программных продуктах. 



           Щ   SAMSUNG JaUmi Wireless Toolkit - SnakeGame 



                     ;GH-Z130 



                     ;GH_C207 



                    SGH_D410 



                    SGH_E100 



                    SGH_E300 



                     ;GH_E618 



                     ;GH_E710 



           Рис.  13.3 . Интерфейс этой  программы очень напоминает J2ME Wireless Toolkit 



 Посмотрим, как будет себя чувствовать  наша змейка на телефонах типа Samsung, 

предварительно заменив классы Nokia API на аналоги от Samsung, a Canvas вернув 

 в исходное положение. Змейка демонстрирует свою живучесть  и хорошую совме- 

 стимость моделей (рис. 13.4). 



                                                       SGH-E1O0 



                   Рис.  13.4 . Самсунги тоже  пригодны для обитания нашей змейки 


----------------------- Page 143-----------------------

144         Глава   13 . Расширение функциональности с помощью API                          . 



Siemens API 



Нужно отдать должное, разработчики компании Siemens переплюнули всех и дали 

самый широкий набор классов для  управления  их телефонами. Классы объедине- 

ны аж в семь пакетов: com. Siemens, mp. game, com. Siemens, mp. colorjpne, com. Siemens .tnp.gsm, 

com.Siemens.mp.io,com.Siemens.mp.media,com.Siemens.mp.media.control,  com.Siemens.mp.ui. 

Возможностей   у   этих   классов   масса, поэтому   не будем  вдаваться   в   подробности, 

а лишь рассмотрим назначение каждого  из них. 



Пакет   com.Siemens.mp.game      содержит    средства   разработки   игровых   приложений. 

Здесь   присутствуют      аналоги   уже   знакомых   нам  классов.  Класс  Light     руководит 

фоновой   подсветкой  телефона, а класс  с двусмысленным   названием Vibrator   за- 

ведует вибрацией. Класс Sound является обычной пищалкой, воспроизводящей тон 

заданной частоты, зато классы Mel ody и Mel odyComposer позволяют составлять и вос- 

производить мелодии из нот, представленных   константами класса Mel odyComposer. 

Намеков  на поддержку   стандартных   звуковых   форматов не  обнаружено. 



Следующий набор классов заведует графическими возможностями аппарата. Класс 

Extendedlmage дает прямой низкоуровневый доступ  к точкам экрана, а также прори- 

совку изображения в обход стандартного метода  paint. Графические объекты пред- 

ставлены классом GraphicObject, являющимся базовым для классов Ti ledBackground 

и Sprite. Первый из них осуществляет   поддержку   графического фона экрана, пред- 

ставленного мозаикой. 



 Класс   Sprite   не  имеет   отношения  к   напитку, дающему       смелые   идеи,  этот   класс 

 осуществляет   поддержку       графических  объектов   типа  «спрайт».   Спрайтом назы- 

 вается   фигура   или  элемент  экрана, который  может   быть   перемещен  независимо 

 от остальных. Спрайт может содержать  несколько кадров, сменяющих друг друга. 

 С  помощью класса  Sprite   реализуется   простейшая анимация. 



 Следующие   два   пакета com.Siemens.mp.ui   и  com.siemens.mp.color_game   дают  новый 

 подход    к   управлению    графическими   объектами.   Первый   пакет   содержит         лишь 

 один   класс   Image,  расширяющий   оригинал   возможностями              масштабирования 

 и зеркального  отражения   картинки. Второй   пакет  организует   работу   с графиче- 

 скими объектами через базовый класс слоя графики Layer, от которого наследова- 

 ны знакомые нам по прошлому  пакету классы Sprite и Ti I ed'Layer, а также с помо- 

 щью   класса   менеджера      слоев   LayerManager.   Кроме   этого   пакет   содержит    класс 

 GameCanvas, аналогично классу   от Nokia, расширяющий стандартный  Canvas. 

 Пакет   com.Siemens.mp.gsm     предоставляет     средства    связи, представленные   следу- 

 ющими  классами: PhoneBook для  доступа   к адресной   книге, Call   для   организации 

 телефонного  звонка   по  указанному   номеру,  SMS для   формирования  и   рассылки 

 SMS-сообщений. 



 Пакеты com. si emens. mp. medi а и com. si emens. mp. medi a. control  реализуют   универсаль- 

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

 прослушивания треков прямо из Интернета. 



 Последний   пакет   com. si emens. mp. i о позволяет   приложению   организовать           свою 

 файловую   систему   с помощью класса Fi le .  Это позволит не мучаться   с хранили- 

 щем записей, а использовать обычную  файловую  систему   с привычными нам ме- 

 тодами  доступа.  Интерфейс ConnectionListener и класс  Connection реализуют   воз- 


----------------------- Page 144-----------------------

                                                                      Siemens   API     145 



можность работы с внешними устройствами  ввода-вывода телефона, такими как 

мультимедийные карты ММС или инфракрасный порт IRDA. 

Звучит   это все, конечно, заманчиво, осталось  только узнать, какие модели под- 

держивают   все эти прелести, а главное — по карману ли они нам. С точки зрения 

программного обеспечения Siemens опять оказался на высоте и предлагает свой 

собственный Siemens  Mobile Toolkit  (SMTK), который интегрируется  со средой 

разработки J Builder любой версии, начиная с пятой. В этой же программе можно 

установить   различные  эмуляторы   телефонов.  Змейка же   продолжает   успешно 

ползать по экранам разных моделей, теперь это Siemens M55 (рис. 13.5). 



                             Рис. 13.5 . И в Сименсах водятся змеи 



 На этом этапе мы прощаемся с нашей змейкой, так многому нас научившей. Пол- 

 ный листинг программы для аппарата Nokia приведен в конце книги, однако, на- 

деюсь, что вы не принялись сразу аккуратно списывать код, а действовали вместе 

 со мной поэтапно. Игра, конечно, далека от совершенства, поскольку реализована 

 как демонстрация различных  возможностей языка J2ME, в ваших силах  теперь 

 довести ее до ума, добавить необходимую логику, красочные картинки, проявив 

 фантазию и смекалку. 



                                            * * * 



 По-хорошему,  каждая   модель  телефона требует  особой доработки   программы, 

 чтобы максимально задействовать возможности каждой конкретной модели. Про- 


----------------------- Page 145-----------------------

1 46       Глава   13 . Расширение функциональности с помощью API 



граммное обеспечение, API-классы, эмуляторы, документацию  и прочие необхо- 

димые вещи можно скачать на официальных сайтах компаний-производителей. 

Как правило, делятся они этим безвозмездно. Также в Сети существует большое 

количество «фан-клубов» (сайтов, порталов, форумов) телефонов основных про- 

изводителей, где собираются разные умельцы, которые делятся идеями и програм- 

мами. Там вы сможете получить много интересной информации конкретно о ва- 

шем телефоне, а также скачать все необходимое программное обеспечение и даже 

больше. Перефразируем слова Мичурина: не нужно ждать милостей от телефона, 

взять их у него — наша задача! 


----------------------- Page 146-----------------------

 Глава 14 



 Приложения 

для        работы с Интернетом 



Большая часть возможностей мобильного телефона и реализации J2ME уже поза- 

ди.  Мы написали несколько различных   приложений, реализующих   интеракцию 

с пользователем, хранение и обмен данными, а также другие функции языка. Мо- 

бильный телефон, однако, является все же средством связи и позволяет связывать- 

ся  не только с помощью голоса или SMS. Профайл MIDP предоставляет  нам воз- 

можности связи с Интернетом, получения и передачи различного вида данных. 

Поскольку речь в этой главе пойдет о взаимодействии телефона с Интернетом, то 

придется расширять сферу нашего обучения с мобильного программирования до 

разработки   серверных   приложений на других  объектно-ориентированных язы- 

ках. Тем, кто не знает, что такое сокет (socket), и не сталкивался   с разработкой 

компьютерных приложений типа «клиент-сервер», придется трудновато. Однако 

один из приведенных примеров, демонстрирующий работу   по протоколу   HTTP, 

если уж   вы дошли до этого места, реализовать  вам вполне по силам. Разработка 

приложений типа  «клиент-сервер»        — это отдельная   наука. Мы же ограничимся 

самыми простыми примерами, демонстрирующими   первоначальное взаимодей- 

ствие телефона с Интернетом. 



 Класс Connector 



Не   буду забивать вам голову тем, как устроена связь между беспроводной сетью 

и  Всемирной паутиной, а перейду сразу к делу, то есть к программированию. Се- 

тевая работа и беспроводная коммуникация осуществляются в реализации MIDP 

средствами   пакета  javax.m1croedition.io,   который содержит   набор интерфейсов 

для различных типов соединений, а также единственный класс Connector. Органи- 

зация соединений с любыми ресурсами  осуществляется  только через этот класс, 

который предоставляет  следующие  методы: 

 •   Connection   open (String  name) — основной метод  класса Connector, обеспечива- 

    ющий открытие соединения с ресурсом, представленным аргументом name. Ар- 

    гумент   имеет   формат универсального   идентификатора ресурса   (URI), кото- 

    рый   можно представить следующей  схемой: 

 <протокол>://[<адрес>][<параметры>] 

    Протокол  представляет   собой тип передаваемых   данных. Реализация  MIDP 

    может   поддерживать   такие протоколы: http, socket,  datagram,  file, ftp, comm. 

    Совсем не обязательно, чтобы ваша реализация MIDP  поддерживала   все эти 


----------------------- Page 147-----------------------

1 48        Глава   14 . Приложения для работы с Интернетом 



   протоколы, поскольку спецификация MIDP требует обязательной поддержки 

   только одного протокола HTTP  1.1. Не исключена также поддержка протоко- 

   лов,   не указанных  в списке. Адресом является обычный URL; в качестве до- 

    полнительных   параметров   может   выступать   номер порта.  Ниже  приведены 

    примеры создания различных типов соединений. 

Connector.open("http://www.voolkansoft.com"); 

Connector.open("socket://129.144.111.222:9000"); 

Connector.open("datagram://129.144.111.333"); 

Connector.open("file:/foo.dat") : 

Connector.open("comm:0;baudrate=9600"); 



    Метод   возвращает     объект   Connection, являющийся   базовым   для интерфейсов, 

    представляющих   различные типы соединений, которые мы рассмотрим  позже; 

•    Connection   open(String    name,  int  mode) — метод,  аналогичный       предыдущему, 

    с тем отличием, что параметр mode задает режим  создаваемого   соединения. Ре- 

    жимы соединений  представлены   следующими   константами класса Connector: 

     •  READ — открыть  соединение с доступом   только на чтение  данных; 

     •  WRITE — открыть  соединение с доступом   только на запись   данных; 

     •  READ_WRITE — открыть соединение с доступом   и на чтение, и на запись данных; 

 •   Connection   open (String  name,   int  mode,   boolean   timeouts)— метод  отличается от 

    двух предыдущих   наличием дополнительного  параметра timeouts   — флага, ко- 

    торый  разрешает   или запрещает   исключения от  тайм-аутов. 



Следующие       методы    класса   Connector   позволяют   сразу  открывать    потоки   ввода 

и  вывода  данных, рассмотренные в одной из предыдущих              глав, минуя   интерфейс 

Connection. Когда мы уже работали   с потоками  ввода  и вывода, мы получали дан- 

ные   из файла,  являющегося   локальным   ресурсом         приложения.  Вся разница за- 

ключается   в том, что ресурс будет размещаться в Интернете и предоставлять дан- 

ные в своем   формате: 



 •   DatalnputStream   openDatalnputStreairK String    name) — открывает   поток ввода дан- 

    ных. Аргумент  name имеет  формат уникального  идентификатора   ресурса; 

 •   DataOutputStream   openDataOutputStream(String       name) — открывает   поток   вывода 

    данных; 

 •    InputStream open InputSt ream (String   name) — открывает базовый поток ввода дан- 

    ных; 

 •   OutputStream   openOutputStream(String     name) — открывает   базовый  поток   вывода 

    данных. 



 Интерфейс Connection 



 Интерфейс Connection является базовым для всех последующих   типов соедине- 

 ний.   Он содержит   единственный метод closeO,  закрывающий соединение, ко- 

торое открывается   с помощью метода  open О   класса  Connector. Этот  интерфейс 

является самым абстрактным во всей структуре возможных типов соединений. 

Дальнейшие  интерфейсы будут его расширять  и дополнять  новыми  методами 

и возможностями. 


----------------------- Page 148-----------------------

                                                 Интерфейс StreamConnection      1 4 9 



Итак, процесс установки  соединения и работы  с ним выглядит   следующим   обра- 

зом. Приложение использует   класс Connector для открытия соединения с сетевым 

ресурсом.  Метод  ореп(...) класса  Connector анализирует   универсальный   идентифи- 

катор   ресурса   и проверяет,  поддерживается   ли  необходимый   протокол   в данной 

платформе. В случае ошибки формируется исключение, иначе возвращается объект 

Connection, который содержит   ссылки  на входной   и выходной   потоки к   сетевому 

ресурсу. Приложение получает  объекты типа InputStream и OutputStream из объекта 

Connection и организует   обмен данными с сетевым ресурсом через эти потоки. При 

завершении работы приложение закрывает  соединение с помощью метода   close() 

интерфейса Connection. 



 Интерфейсы InputConnection 

и  OutputConnection 



Интерфейсы InputConnecti on и OutputConnecti on наследованы от интерфейса Connecti on 

и расширяют его потоками ввода и вывода соответственно. Каждый их них опреде- 

ляет по два новых метода для открытия потоков. Названия этих методов такие же, 

как и у методов открытия потоков класса Connector, и отличаются   они лишь отсут- 

ствием  аргументов.   Использование этих  методов   открытия   потоков мы еще уви- 

дим в примерах. 



 Интерфейс StreamConnection 



Интерфейс   StreamConnection   объединяет      в   себе   два   предыдущих  интерфейса 

 InputConnection и OutputConnection, но не определяет   никаких новых методов. За- 

дача этого интерфейса в том, чтобы представить  любой тип соединения, чьи дан- 

ные могут быть обработаны как поток байтов. В зависимости от протокола соеди- 

нения   активизируются     разные  реализации   интерфейса  StreamConnection. Наша 

среда  разработки J2ME WTK        предоставляет   две реализации: одну для   соедине- 

ния с портами связи, например с инфракрасным портом, вторую для соединения 

через   сокеты.  Мы рассмотрим   второй   тип  соединения   и реализуем    пример, де- 

монстрирующий работу   соединения такого типа. 



 Сокет (socket) — это сетевой механизм транспортного уровня, который  реализует 

работу   по протоколу TCP/IP. Грубо говоря, это та дверь, которая связывает про- 

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

 свой адрес, состоящий из сетевого адреса URL и номера порта, через который бу- 

дет   осуществляться    соединение.  Номер  порта   может   быть   любым   в   диапазоне 

 0-65   535, однако номера, меньшие чем  1024, обычно зарезервированы   системой. 

 Структура   сокета универсальна,  поэтому  программа, принимающая соединение, 

 может быть запущена  на любой  платформе и написана на любом языке, поддер- 

 живающем  сокеты. 

 Итак, пришло  время   реализовать   пример  соединения, основанного  на   сокетах. 

 Работа приложения будет заключаться   лишь в том, чтобы  соединиться с сервер- 

 ной частью, послать туда приветствие, получить   в ответ  сообщение и продемон- 


----------------------- Page 149-----------------------

150           Глава 14 . Приложения для работы с Интернетом 



стрировать   его  на экране телефона.  Для   начала   рассмотрим   клиентскую                              часть 

программы, написанную на уже  привычном для нас языке J2ME: 

import javax.microedition.mi diet.MIDIet ; 

import    javax.microedition.10.StreamConnection ; 

import    javax.microedition.io.Connector ; 

import    java.io.IOException : 

import    java.io.DataOutputStream ; 

import    java.io.DatalnputStream : 

import     javax.microedition.lcdui.Form : 

import     javax.microedition.lcdui.Display ; 



try { 

      // открыть соединение       через сокет с портом      3000 локальной     машины 

     StreamConnection sc - 

                                                                                м 

          (StreamConnection)Connector.open("socket://127.0.0.1:3000 ): 

      // получить    поток   ввода   соединения 

     DatalnputStream      dis • sc.openDatalnputStreamO ; 

      // получить     поток  вывода   соединения 

      DataOutputStream      dos = sc.openDataOutputStreamO ; 

      // записать     приветствие в поток      вывода 

      dos,write("Hel 1 о . Server!\0".getBytes0.0,15) ; 

      // буфер для приема       сообщения 

     byte[ ] buff = new byte[15] ; 

      // получить сообщение из потока          ввода 

      dis.read(buff); 

      // преобразовать      сообщение в строку 

      String msg - new String(buff) : 

      // создать форму      для отображения     сообщения 

      Form form = new Form("Message         received") ; 

      // добавить     полученное сообщение в форму 

      form.append(msg); 

      // получить     менеджер   дисплея 

      Display display - Display.getDisplay(this) : 

      // отобразить форму       сообщения 

      display.setCurrent<form); 

      // закрыть     соединение 

      sc.closeO : 



 }  catchdOException       ioe ) { 

      // вывести     исключение в область системных         сообщений 

      System.out.println("ERROR :        "+ioe.getMessage()) ; 

 } 

 С  этой стороны нет ничего сложного. Приложение открывает соединение через сокет 

 с портом 3000 локальной машины, представленной постоянным для любого компью- 

 тера  IP-адресом   127.0.0.1.  После установки  соединения приложение получает   пото- 

 ки   ввода и вывода соединения и через них реализует   интеракцию с серверной частью. 


----------------------- Page 150-----------------------

                                                             Интерфейс StreamConnection                1 5 1 



Перейдем   к   тому,   что   представляет         собой   серверная   часть.   В   нашем   случае       это 

программа, запущенная на локальном компьютере, открывающая сокет и   ждущая 

связи   с клиентом. Программу   напишем на языке  C++ с использованием   классов 

MFC .   С таким же  успехом   она может   быть   написана на любом   языке,  поддержи- 

вающем  технологию   сокетов. 



Язык  C++ не входит   в нашу учебную программу, поэтому не будем особо   вдаваться 

в подробности, а просто посмотрим на код некоторых функций, который может быть 

кому-то полезен. Рассмотрим две основные функции: OnListerK), которая открыва- 

ет сокет, ожидающий клиентского соединения, и OnRecei ve(), которая   обрабатывает 

событие получения  сообщения. Функция отображаемого   диалога  OnListerK)   вызы- 

вается  при нажатии на кнопку Listen основного  диалога: 

void   CServerDlg::OnListen() 



 { 

     // получить    информацию из поля     ввода  порта 

     UpdateDataO ; 

     // создать сокет с локальным        адресом 

     if    (m_Sock->Create(m_Port.SOCK_STREAM."127.0.0.1") ) 

     { 

          // ждать связи с клиентом 

          if (m_Sock->Listen() ) { 

               // вывести сообщение с номером       порта   сокета 

               CStatic* stat = (CStatic*)GetDlgItem(IDC_STATIC) ; 

               CString str ; 

               str.Format("Listening on port %6", m_Port) : 

               stat->SetWindowText(str); 

               // вывести    IP-адрес сокета 

               stat = (CStatic*)GetDlgItem(IDC_MSG) : 

               UINT port ; 

               m_Sock->GetSockName(str.port) ; 

               stat->SetWindowText(str); 



 Функция   использует   член класса диалога   m_Port, связанный с полем ввода   порта, 

 и m_Sock, являющийся объектом  класса CLi stenSocket,  наследованный от   стандарт- 

 ного  MFC-класса CSocket.  При нажатии  на кнопку   Listen   диалог   выдаст   такие со- 

 общения (рис.  14.1). 

 Вторая функция OnRecei ve()  класса CLi stenSocket   отслеживает   момент приема сооб- 

 щения через сокет, выводит  его в окне диалога  и посылает ответ  клиентскому  при- 

 ложению: 

 void CListenSocket::OnReceive(int        nErrorCode ) 

 { 

      // поля   диалога для вывода     сообщений 

      CStatic* stat = (CStatic*)(m_pDlg->GetDlgItem(IDC_STATIC)) • 

      CStatic* statjnsg = (CStatic*)(m_pDlg->GetDlgItem(IDC_MSG)) ; 

      // буфер приема     сообщения 


----------------------- Page 151-----------------------

152          Глава   14 . Приложения для работы с Интернетом 



     TCHAR   buff[20] ; 

     // прочитать сообщение в буфер 

     Rece1ve(&buff:20) : 

     //  вывести   подтверждение    получения    сообщения 

     stat->SetWindowText(_T("Message Received...")) ; 

     CString str ; 

     str.Format("Is",buff) : 

     //  вывести   полученное сообщение 

     stat_msg->SetWindowText(str) : 

     //   копировать ответ в буфер 

     strcpy(buff."Hello .      MIDlet!\0") : 

     //   послать ответное сообщение 

     Send(&buff,15); 



                                           Uiening on port 3000 



                                      1270.0.1 



                                        Port  J3Q00"  Ш 0  : 



              Рис.   1 4 . 1 .  Сервер сообщает, что слушает порт 3000 локальной  машины 



Те,   кто уже  знаком со студиями   р'азработки  C++,                  без труда смогут  реализовать 

данный пример, остальные же без труда смогут найти книжки о разработке визу- 

альных приложений на языке C++. 

Перейдем к самому интересному, сначала запускаем серверную часть и ждем со- 

единения  клиента, в качестве  которого будет  выступать   стандартный   эмулятор 

среды разработки J2ME WTK.   При запуске мобильного приложения сервер об- 

наружит связь и получит сообщение от телефона (рис. 14.2). 



                                           Met.'age Received. 



                                      Hello   Servefi 



                       Рис.   14.2 . Сервер получил сообщение и вывел его в окне 



После приема сообщения сервер формирует в ответ свое приветствие и посылает 

его мобильному   приложению, которое отображает   принятое сообщение с помо- 

щью обычной формы (рис. 14.3). 


----------------------- Page 152-----------------------

                                                 Интерфейс DatagramConnection           1 5 3 



Это  был   простейший  пример, шапочное  знакомство 

сервера  с телефоном  на уровне   «привет-привет».   Тем 

не менее, это и есть самый сложный момент. Если уж 

наши программы встретились и поздоровались, то и до- 

говориться   смогут, а о чем им договариваться   — зави- 

сит  только   от  нашей фантазии, простор для   которой 

здесь  ничем не ограничен. Можно разработать  инфор- 

мационную систему,  многопользовательскую   онлайн- 

игру, оперативный центр сбора и обработки информа- 

ции и многое другое. Сервер — программа, размещенная 

в Интернете, — должна  взять  на себя все организаци- 

онные функции общения с клиентами, которых   может 

быть сколь угодно большое число, причем одновремен- 

но. В классических сервер-приложениях отдельный тред 

отводится для ожидания связи с новым клиентом, для 

каждого из которых выделяется  собственный тред. Та- 

ким образом, клиенты не будут мешать друг другу, од- 

нако вопросы синхронизации при доступе к общим дан- 

ным встают  особо остро. 

Заметим, что  не все реализации  поддерживают           сокет- 

ные соединения, поэтому вполне возможна ситуация, что 

при попытке создания  соединения на вашем   аппарате 

будет сформировано исключение. Например, при попыт- 

ке запуска того же самого приложения на эмуляторе от 

Nokia мы получим  в системной области следующее  со- 

общение: «ERROR: The requested  protocol does not exist         Р и с -   " - 3 -   Эмулятор принял 

 socket://127.0.0.1:3000».                                        И      ^  ^    ? 



 Интерфейс StreamConnectionNotifier 



 Функции   сервера   может   выполнять   и мобильный  телефон   с помощью  интер- 

 фейса StreamConnectionNotifier.   Этот интерфейс содержит   единственный   метод 

 StreamConnection acceptAndOpen О, который блокирует все операции до тех пор, пока 

 не появится клиентский запрос на соединение. После чего метод создает новый 

 объект  соединения, через  который и связывает  серверную  часть с клиентской. 

 При использовании данного интерфейса в качестве сервера будет выступать  мо- 

 бильное устройство.   Недостатки такой реализации заключаются   в том, что  уст- 

 ройство должно быть постоянно на связи в ожидании подключения клиентов, что 

 может быть дорого и неудобно. 



 Интерфейс DatagramConnection 



 Еще один тип соединений представлен  интерфейсом DatagramConnection, который 

 напрямую  наследован   от   интерфейса  Connection. Если соединение  через   сокеты 

 основывалось на протоколе TCP, то данное соединение использует   протокол пе- 

 редачи дейтаграмм   UDP. Данный протокол используется  различными интернет- 


----------------------- Page 153-----------------------

154        Глава   14 . Приложения для работы с Интернетом 



службами     потоковых   трансляций  и  основан  на  передаче   дейтаграмм.   В некото- 

рых случаях   он является более предпочтительным  из-за эффективной и быстрой 

пересылки данных, поскольку там нет гарантированной доставки сообщений, ис- 

правлений искаженных сообщений и другого   контроля, нагружающего   связь. 



Дейтаграмма  представлена  интерфейсом Datagram, который объединяет в себе два 

интерфейса Data Input и DataOutput. Кроме этого интерфейс содержит информацию 

о передаваемых   или принимаемых данных и об адресе пункта назначения. Интер- 

фейс Datagram предоставляет   следующие   методы: 



 •   String   getAddressO   — установить  адрес   дейтаграммы; 



 •   byte[]  getDataO   — возвращает данные  дейтаграммы; 



 •   int  getLengthO   — возвращает длину   дейтаграммы; 



 •   int  getOffsetC)  — возвращает   текущее  смещение указателя   буфера   данных; 



•    void   reset()   — восстанавливает   позицию указателя   буфера   данных; 



 •   void  setAddress( Datagram  reference)—устанавливает      такой же адрес, как и у дей- 

    таграммы, переданной в параметре; 



 •   void   setAddress(String  addr)   — устанавливает   адрес   дейтаграммы; 



 •   void   setData(byte[]  buffer,  int  offset,  int  1 en)— устанавливает   буфер дан- 

    ных, смещение указателя   буфера  и длину   данных; 



 •   void  setLengthd'nt  I en)—устанавливает длину полезной нагрузки дейтаграммы. 



 Интерфейс   DatagramConnection   позволяет   отправлять   и   получать      дейтаграммы 

с помощью методов   send (Datagram   dgram)  и   receive(Datagram    dgram). Также интер- 

 фейс позволяет создавать   новые дейтаграммы   с помощью нескольких   методов: 



 •   Datagram   newDatagram(byte[]   buf,  int  size)   — возвращает   новую   дейтаграмму 

    с данными буфера  buff   длины   size; 



 •    Datagram newDatagram(byte[]    buf,  int  size,  String  addr)—   аналогиченпредыду- 

    щему  методу,  параметр   addr задает   адрес   дейтаграммы; 



 •   Datagram  newDatagram(int   size)—создает   новую дейтаграмму  заданного размера; 



 •   Datagram   newDatagramd'nt    size .  String  addr)  — создает   новую    дейтаграмму 

    с заданным размером и   адресом. 



 Максимально допустимую   длину дейтаграммы, а также номинальную длину дей- 

 таграммы     можно   получить    с   помощью   методов    int  getMaximumLengthO     и  int 

 getNominalLengthO. 



 Так же как и StreamConnection, интерфейс DatagramConnection может быть использо- 

 ван как для   клиентской, так  и для   серверной частей  приложения. Если при созда- 

 нии дейтаграммы в параметре адреса не будет указано имя хоста (datagram: // : 3000), 

 то  соединение будет  работать   в серверном  режиме.  Если имя хоста   будет  указано 

 (datagram://127.0.0.1:3000),   то соединение будет работать  в клиентском режиме. 



 Интерфейс ContentConnection 



 Интерфейс ContentConnection дополняет  интерфейс StreamConnection. Главное  от- 

 личие заключается   в том, что  если раньше интерфейс не вникал в суть           передава- 


----------------------- Page 154-----------------------

                                                          Интерфейс HttpConnection           15 5 



емых  данных, то  теперь  из него можно извлечь  некоторую  информацию, опреде- 

ленную самим протоколом, с помощью следующих                   методов: 



•    String   getTypeO    — возвращает   тип  данных, передаваемых          по протоколу.   Для 

    протокола  HTTP   метод   возвратит   содержимое  поля content-type заголовка; 



•    String   getEncodingO     — возвращает   кодировку   данных.  Для   протокола   HTTP 

    возвращается   содержимое  поля content-encoding  заголовка; 



•    1 ong  getLength () — возвращает длину доступных данных. Для протокола  HTTP 

    возвращается содержимое поля content-length заголовка. Если сведения о дли- 

    не недоступны, то метод   возвращает  значение - 1 . 



 Интерфейс HttpConnection 



Самым   продвинутым         интерфейсом   во   всей   этой   иерархии   является   интерфейс 

HttpConnection, представляющий соединения, использующие протокол HTTP . По 

этому   протоколу   организуется   передача  всех  знакомых нам интернет-страниц. 



Интерфейс HttpConnection реализует   возможность получения  информации полей 

HTTP-заголовка  ресурса,  а также   поддерживает   передачу   запросов и   получение 

откликов  по протоколу   HTTP .  Кроме этого интерфейс определяет   полный набор 

констант,  представляющих          коды   ошибок   и   статуса  протокола   HTTP .  Интерак- 

ция с веб-сервером  осуществляется   с помощью следующих                 методов   интерфейса: 



 •    long  getDate()   — возвращает   дату   из соответствующего        поля   заголовка.   Дата 

    по традиции   представлена   количеством   миллисекунд, прошедших   с   1 января 

     1970   года; 



 •    long   getExpi ration()   — выдает   значение поля expires заголовка; 



 •    String  getFi 1е()   — возвращает   имя файла из поля URL данного соединения; 



 •    String  getHeaderField(int     n) — выдает   значение поля заголовка  с номером п; 



 •    String  getHeaderFi el d( Stri ng   name) — возвращает  значение поля заголовка, оп- 

    ределенного   ключом   name. Если  заголовок   не содержит           поля   с запрошенным 

    именем, то возвращается значение nul 1; 

 •    long  getHeaderFieldlntCString       name,   long  def)  — возвращает   поле   заголовка 

    с   именем   name, представленное   как  целое   число.  Значение def        возвращается, 

    если поле с данным именем отсутствует или значение поля нельзя преобразо- 

    вать в целое число; 



 •    long  getHeaderFieldDateCString       name,   long   def)  —аналогично       предыдущему 

    методу   возвращает   запрошенное поле заголовка, представленное  в виде  даты; 



 •    String   getHeaderFi el dKey(int   n)  — возвращает   имя поля заголовка   с порядко- 

    вым номером п; 

 •    String  getHost()   — получить   имя хоста соединения или IP-адрес, если имя хо- 

    ста не задано; 

 •    long   getLastModifiedO     — возвращает   дату   последней   модификации   ресурса, 

     представленную   полем  last-modified       заголовка; 



 •    int  getPort()   — возвращает   номер порта соединения; 80  — стандартный  номер 

     порта HTTP-соединений; 


----------------------- Page 155-----------------------

1 56         Глава   14 .  Приложения      для   работы   с Интернетом 



•    Strin g   getProtocol   ()  — получить       имя   протокол а   соединения ; 



•    Strin g   getQuery()    — выдает   область   запрос а  адреса   URL . Эт а   часть   начинается 

    после  знак а  ?   в   строке   адреса ; 



•    Strin g   getRef  ()   — выдает    область   адреса   URL , следующую          посл е  знак а #; 



•     String   getRequestMethocK)       — возвращает       установленный   метод         HTTP-запроса ; 



•     Strin g  getRequestProperty(String           key)  —   возвращает      установленно е       свойство 

    key   HTTP-запроса ; 



•     i n t  getResponseCode()     — получить      код  ответа   сервера .   Все   возможны е   коды   от- 

    вета   представлены        в   интерфейс е   константами   HTTP_ACCEPTED,  HTTP_BAD_GATEWAY, 

    HTTP_NOT_FOUND, HTTPJORBIDDEN,  HTTP_OK и   т.  д .   Все   3 8   констант   приводить             здесь 

    не  вижу     смысла :  кому   понадобится ,  найдет   их   в   документации ; 



•     String   getResponseMessage()        — получить      ответ   сервера   в   вид е   строки   с   сообще- 

    нием ; 



•     String   getURLC)  — возвращает         адрес   U R L   соединения ; 



•     void   setRequestMethodCString         method)   — установить        метод    HTTP-запроса .  До- 

    пустимы   методы ,  представленны е   следующими                  константами   интерфейса : GET, 

    POST,  HEAD; 



•     void  setRequestProperty(String          key.   String   value)—установит ь         значени е   value 

    свойству      key  запроса . 



Следующий          пример , которы й   мы   реализуем ,   получит          по  протоколу      H T T P   дан- 

ные   интернет-страницы ,   а   затем          пошлет      сообщени е      в   гостевую    книгу ,   разме- 

щенную   в   Интернете .  В данно м   случа е          нам   потребуется       реализация   л и ш ь   кли- 

ентской  част и   п р и л о ж е н и я , поскольку   серверная   част ь   представлен а           обычны м 

веб-сервером ,   с   которы м   мы   связываемся   с   помощь ю   браузера ,   заходя              на   стра- 

ницу     в   Интернете . 



Д ля    реализаци и     нам   потребуются       некоторы е   знания   самого        протокола ,   а   также 

язык а   H T M L .  Опыт ы   мы   будем   производить   над   гостевой   книгой   официальног о 

сайта   Ф К   «Томь» ,   расположенной   по адресу            football.tomsk.ru/gb .    Тольк о   лиш ь   из 

большого       уважения      к   этой   команде ,   а   не   с   целью  как-то   навредить .   Д л я  начала 

стоит     взглянуть ,    как   устроен а   гостевая     книг а   внутри ,   благо   н а   любо й  страниц е 

можно      щелкнуть      правой     кнопкой   и   выбрать      команду     Просмотр     в   виде  HTML, что 

позволит   нам  увидеть         весь   код   страницы   в   текстовом   виде .  Немног о  поковыряв- 

шись ,   выясняе м       всю   необходимую        нам   информацию :   сообщения             добавляются 

с  помощью   HTTP-запрос а  POST и   скрипт а   add.php. Параметры   сообщения                      переда- 

ются   скрипту   из форм ы в переменных   username, ci ty , emai 1, homepage и message.  Этой 

информации   нам   вполн е  достаточно ,  чтобы   реализовать   нашу                 затею . 



Вот    как   будет   выглядеть      наше   приложение ,  которое   соединится            с  HTTP-серве- 

ром ,  отправит   запро с  с помощью   метода           POST, а  затем   получит    различную   инфор- 

мацию   о   сервер е   и   параметрах      соединения : 



 import    javax.microedition.midlet.MIDIet: 

 import    javax.microedition.io.HttpConnection: 

 i mport   javax.microedi  t i  on.io.Connector: 

 import    java.io.OutputStream; 

 import   java.io.IOException; 


----------------------- Page 156-----------------------

                                                               Интерфейс HttpConnection               1 5 7 



try { 

     // открыть соединение с HTTP-сервером 

     HttpConnection    conn • 

          (HttpConnection)Connector.openC'http://football.tomsk.ru/gb/add.php"): 

     // установить    метод  запроса 

     conn.setRequestMethod("POST"); 

     // установить    свойства   запроса 

     conn.setRequestProperty("Connection",         "close") : 

     conn.setRequestProperty("User-Agent" .        "ConnectDemo   (MIDP-1.0 : J2ME)") : 

     conn.setRequestProperty("Content-Type" . 

                                   "application/x-www-form-urlencoded"); 

     conn.setRequestProperty("Accept" .       "text/plain") ; 

     // получить    поток  вывода  соединения 

     OutputStream os - conn.openOutputStreamO ; 

     // записать    параметры   запроса  POST в поток   вывода 

     os.write("username=Noki a7210&message=WeAreTheChampi ons".getBytes()) : 

     // получить    код ответа   сервера 

     System.out.printlnC'Response       Code :  "+conn.getResponseCodeO) : 

     // получить    текстовый   ответ  сервера 

     System.out.println("Response       Msg :  "+conn.getResponseMessage()) : 

     //получить     базовые  параметры   соединения 

     System.out.println("Type :     "+conn.getType()) : 

     System.out.println("Length :      "+conn.getLength()) ; 

     System.out.pri ntln("Encodi ng ; "+conn.getEncodi ng()) ; 

     // получить в цикле      имена и значения   полей   заголовка 

     for(int    i=0;i<12;i++ ) 

          System.out.pri ntln(conn.getHeaderFi eldKey(i)+ 

                                ": "+conn.getHeaderField(i)) : 

     // получить и вывести      параметры   соединения 

     System.out.printlnC'Data :      " + conn.getDateO) ; 

     System.out.printinCHost :       " + conn.getHostO) : 

     System, out . pri ntl n ( "Port : " + conn.getPortO) ; 

     System, out . pri ntl n ( "Protocol : " + conn.getProtocolO) : 

     System, out . pri ntl n ( "Query : " + conn.getQueryO) ; 

     System.out.printlnC'Ref :      " + conn.getRefO) ; 

     System.out.printlnt"Request       Method : " + conn.getRequestMethodO) : 

     System, out . pri ntl n ( "URL : " + conn.getURLO) : 

     // закрыть    соединение 

     conn.cioseO : 



 } 

 catchCIOException     ioe ) { 

     // вывести    сообщение об исключении 

     System.out.println("ERR:"+ioe.getMessageO) : 


----------------------- Page 157-----------------------

158           Глава   14 . Приложения для работы с Интернетом 



Если у вас есть выход в Интернет, то можно запускать приложение прямо на эму- 

ляторе. Если вы используете  прокси-сервер, то следует указать его адрес в свой- 

ствах эмулятора.  После запуска смотрим в области системных сообщений J2ME 

WTK,  насколько успешно мы соединились, анализируя ответы и параметры сер- 

вера (рис. 14.4). 



 «Ж J2ME Wireless Toolkit - TestSample                                                                           La 



                                        %   Settings ... I   Ц   Build I %   Run li   I j j Clear Console: 



   device: |oei**Or»vPnon*                                                 2J 



 Response    Code:  302 



  Response  list}:   Found 

 Гуре:   text/htal ;   charset-Mindows-1251 

 Length:   - 1 

  Encoding : null 



 date : Wed , OS Oct 200S 21:25:01     GHT 

 server: Apache/1.3.2 9     (Unix) mod_maoro/1.1.2    PHP/4.3.4 mod_ssl/2.8.16     OpenSSL/0.9. 6g   гиэ/Р130.1 9 

  <-poKered-by :   PHP/4.3.4 

 set-cookie :   PHPSESSID*eSc83e4c6b599791f60e4f88ebS50a25 ;       path- / 

 expires : Thu , 19 Nov 1981 08:52:00      GMT 

  cache-control : no-store , no-cache , must-revalidate , post-check=0 , pre-check-0 

 pragma : no-cache 

  set-cookie : gbcookieid=1128547501 ; expires»Fri , 04-Nov-05 21:25:01         GHT 



  set-cookie: gbusername=Nokia7210 ;      expires«Fri , 04-Nov-05   21:25:02 GHT 



  set-cookie: gbcity=deleted ;     expires=Tue , 05-0ct-04 21:25:01     GKT 



  set-cookie: gbemail=deleted ;     expires=Tue , 05-0ct-04 21:25:01 GHT 

  set-cookie :  gbhomepage=deleted ;   expires=Tue , 05-0ct-04 21:25:01     GHT 



  Data :  1128547501000 

  Host :  football.tomsk.ru 

  Port : 80 



 Protocol:    http 

  Query:  null 

  Ret:  null 

 Request   Method:  POST 

  URL:  http://football.tonsk.ru/gb/add.php 



   Рис.  14.4 . В области системных сообщений выведена   информация о соединении с сервером 



 Видим, что свойства сервера нами успешно получены. Заметьте, что для тестиро- 

 вания программы на реальном аппарате и для успешного соединения с интернет- 

 ресурсом   на телефоне должен  быть активирован WAP-сервис.   Теперь  осталось 

 зайти на гостевую книгу по вышеуказанной ссылке и посмотреть результат наше- 

 го эксперимента (рис. 14.5). 

 Интересна реализация следующей идеи: отправка SMS-сообщений с сайта сотовой 

 компании, например www.mts.ru, что, возможно, даст некоторую экономию при по- 

 сылке SMS. Основная проблема заключается  в том, что на сайте введена спам-за- 

 щита в виде картинки с трудночитаемыми цифрами и буквами, которые надо ввес- 

 ти в специальном поле. Обойти это можно следующим способом: проанализировать 

 HTML-текст сайта, получить ссылку на файл картинки, продемонстрировать ее на 

 экране мобильника и попросить пользователя   ввести  пароль вручную.  Здесь нас 

 ожидает очередная проблема: формат картинки JPEG не совпадает с форматом PNG, 

 поддерживаемым телефонами, поэтому для отображения следует сначала конвер- 

 тировать картинку в нужный нам формат. Можно поискать готовый класс конвер- 


----------------------- Page 158-----------------------

                                                                      Интерфейс HttpConnection                  159 



тора (что-нибудь с именем JpegViewer, например), который можно встроить в при- 

ложение, или самому   связаться с таблицами  Хофмана и прочими   премудростями 

структур графических файлов. Первый путь значительно проще, однако над реали- 

зацией такого приложения придется изрядно потрудиться. А вам слабо? 



 -«Гостевая книга | Официальный сайт ФК "Turn," - Microsoft Internet ЕиЫогкг 



 I   файл   Правка   Бид   Избранное   Сервис  Pпрэи;э 



                                          4k 



              Объявление! 



             I Уважаемые болелыМики! До 6-го октября вы можете ; 

             • дублирующего состава "Томи" Эдуарду Барышеву, 



     Но  J'J96d4  Nokia7Z10 .   05-10-2005,04:25:01 



     WeAreTheChampions 



     Na.33964 3   He.,   Город: Moo-ва  06-10-2005,  02.40:34 



                    Рис.   14.5 . В гостевой книге появилось  сообщение от   Nokia7210 



                                                        *  *   * 



Таким образом, полную иерархию всех рассмотренных типов соединений можно 

представить  следующей   схемой  (рис. 14.6). 



        DatagramConnection           InputConnection       OutputConnection        StreamConnectionNotifer 



                                    Рис.   14.6 . Иерархия   типов  соединений 


----------------------- Page 159-----------------------

1 60       Глава   14 . Приложения для работы с Интернетом 



Мы рассмотрели все типы соединений этой иерархии, выделив четыре основных 

типа: потоковые  соединения  с коммуникационными портами, например IRDA, 

соединения уровня приложений по протоколу   HTTP, либо через сокеты по про- 

токолу TCP/IP, дейтаграммные  соединения по протоколу  UDP. Из всех этих ти- 

пов спецификация MIDP определяет   обязательную   реализацию  лишь для  под- 

держки протокола  HTTP. Попытка использования остальных   типов соединений 

может сформировать исключение на вашей модели  телефона. 

Информация, приведенная в этой главе, является лишь базовыми сведениями для 

программирования сетевых приложений. Никто не говорит, что, потратив несколь- 

ко минут  на чтение, вы сможете  написать интернет-браузер   для вашего мобиль- 

ника, однако, приложив  некоторые усилия, организовать   общение вашего  теле- 

фона с интернет-ресурсами   вам вполне по зубам. И да пребудет с вами сила! 


----------------------- Page 160-----------------------

 Глава 15 



Защита приложений 



Если вы добрались до этого места книги, значит указанная тема должна быть для 

вас уже   актуальна.  Допускаю,  что  бессонными ночами, тернистым   путем   проб 

и ошибок, вы улучшали   игру в змейку, довели ее до такого совершенства, что раз- 

работчики корпорации Nokia ахнули  бы и обзавидовались. Допускаю также, что 

вы разработали  уникальные алгоритмы для реализации игры, использовали не- 

стандартные решения и феноменальные ходы. 

Проблема заключается в том, что наряду с процессом компиляции, который пере- 

водит программу из текстового вида в машинные команды, существует обратный 

процесс декомпиляции, который позволяет восстановить исходный вид програм- 

мы из конечного приложения. Конечно, есть  альтруисты,  которые  практикуют 

«open зоигсе»-программирование и публикуют   все исходники программ, но если 

мы хотим  получать  за наши труды хоть какое-то материальное   вознаграждение, 

то попытки расковырять наш продукт нужно пресекать. 



Java Decompiler 



Посмотрим для начала врагу в лицо и разберемся, как получить текст чужой про- 

граммы (конечно же, только с образовательной точки зрения). Брать чужое нехо- 

рошо, особенно извлекать  из этого коммерческую   выгоду, поэтому   мы ограни- 

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

адаптировать   приложение для  своей модели телефона, жизней побольше поста- 

вить в игрушке или модернизировать полезную программку. 

В  первой главе  мы рассматривали,  как при помощи дата-кабеля   и   соответству- 

ющих программ закачать приложение в телефон. Аналогичным образом можно из- 

влечь приложение из телефона. Далеко за примером ходить  не будем и вытащим 

стандартный сборник шахматных  задач  «Chess Puzzle» из Nokia 6100  или чего-то 

похожего. Если вы уже благополучно его стерли или никогда и не имели, то найти 

это приложение в Интернете в свободном доступе не составит труда. Итак, вот он, 

заветный файл chesspuzzle. jar,  и нам, как совсем юным мобильным программис- 

там, ну очень интересно развинтить его и посмотреть, как он устроен. Вооружаемся 

отверткой типа WinRAR и смотрим, что же внутри у этого приложения (рис. 15.1). 



Здесь мы наблюдаем два файла ресурсов  1 ang. xx и Puzzles. BIN, папку с картинками 

icons и папку с манифестом приложения МЕТА-INF. Все самое интересное запрятано 

в глубине папки с названием com. В ней мы обнаружим несколько файлов с расши- 

рением *. cl ass. Это файлы откомпилированных классов приложения. Вытащим их 


----------------------- Page 161-----------------------

162          Глава   15 . Защита  приложений 



из архива и сохраним у себя на компьютере. Внутрь этих файлов лучше и не совать- 

ся: машинный язык доступен нам, простым смертным, лишь с переводчиком. 



    ••Echesspuzzle.jar " WinRAR (evaluation copy) 



          Commands    Favorites/Options  Help 



                 chesspuzzle.jar - ZIP archive, unpacked size 5S 475 bytes 



     tone                          Sire |     Packed]  Type                                        : 

                                                                                                   ;;CRC|2j 



     •   com                                          Папка                10,06.2005 16:40 



     Cj   icons                                       Папка                10.06.2005 16:40 



     QMETA-INF                                        Папка                10,06,2005 16:40 



     (agllang.xx                   697           453   File xx             10.06.2005 16:40     0A33DB1A 



     Ш   Puzzles, BIN             8  586       7   120  File BIN           10.06.2005 16:40      2A48634B 



                                                            TuU;.to,der> nd9;33b/ts5in2r 

                                                                         a 



                           Рис.  15.1 . Архив  мобильного   приложения       изнутри 



Теперь самое интересное. Простой отверткой нам уже не обойтись, нужны более 

мощные средства. Благодаря идеологии языка Java, связанной с транспортабель- 

ностью приложений, очень много информации можно восстановить из конечного 

приложения. Переводчик с машинного языка на привычный уже  нам язык J2ME 

называется Java-декомпилятором   (Java  Decompiler). Думаю, вы без труда найде- 

те программное обеспечение подобного рода. В рамках  этой книги мы рассмот- 

рим лишь один из возможных декомпиляторов под названием DJ Java Decompiler 

 (по адресу dj.navexpress.com  — информация о программе и разработчиках, а также 

свободное распространение последней версии). 

Качаем, устанавливаем,  запускаем. А теперь   смело  открываем   основной класс 

приложения ChessPuzzleMidlet.dass  и видим вот такую чудесную картину (рис. 15.2). 

 Создадим в обычном J2ME WTK  проект с названием ChessPuzzleMidlet, деком или- 

руем все классы приложения и сохраним в папке исходного кода /src с расшире- 

нием .Java. Классы со знаком $ в названии декомпилировать не нужно, поскольку 

они уже содержатся внутри класса, название которого предшествует знаку $. Фай- 

лы  ресурсов   Lang.xx, Puzzles.BIN и картинки разархивируем   и сохраним в папке 

ресурсов /res с сохранением структуры   каталогов. 



 Обратим внимание, что все классы объединены в один пакет package   com.nokia. 

mid. appl.chep, поэтому, чтобы компилятор обнаружил необходимые классы, нуж- 

но в свойствах  созданного проекта заменить название основного класса мидлета 

на его полное  имя, содержащее   название пакета: com.nokia.mid.appl  .chep.Chess 

 PuzzleMidlet. Делается   это следующим   образом: в пункте меню   Project (Проект) 

выбираем команду Settings  (Параметры), где идем на вкладку   MIDlets и редакти- 

руем поле Class (рис. 15.3). 


----------------------- Page 162-----------------------

                                                                                                                       Java  Decompiler                         1 6 3 



     | ChessPu/rleMidlet. jad - ОЛ Java Decompiler 



         Ed*      Se*c h     View     Seftngs      language        loot ,   Це1р 



                       в     х      ц   .V *           3     :E  g      : -/' Q 



           i          1          J         f 



  it   {жятр1Ш           by DJ   v3.5.S.7?  Copyright 200$ Alarms Neehkov                           Date: 06.10.1:006   0:52.31 

   •'•' Home Page:            littp'J/itWfit6iu.foiUmeciy.curnfi>euM<cv^.Mmi                            ••   Check   often !ы теи; vei-siom 

  !.' Dvcdinpitor options:  pacHimport&($) 



   packaae   com.nokia.mid.appl.chep; 



   import   java.io.PrintStrearn; 

   import Java.util.Timer; 

   impart javax.microedition.lcdui.'*; 

   import   javax.microedition.midlet.MIDIet; 



  // Referenced classes               of package   corn nokis. mid appl. chep: 

   II             FuiiCawasScreen,             CbessRMS, CheasBosrd, HtghScoms, 

                 Loca! 



   public  class   ChessPuzzleMidlet   extends   MIDIet 

       implOTisni-s   CommandListener 



  class CountDown 0 

  public ChessPuzzleMidletil {) 



 .«•e    75 



           Рис.      15.2 . Декомпилированное   приложение   снова   доступно   для                                          нашего       понимания 



 | «Isettings for project "ChessPurcleMJdtet'* 



   Frequ«etl I   Optional | User Defined         [MO**] ! 



           : 

  ,     ...  ГКву           ] * .           Name   [      Ж      I                 teon                  J                           Class                           j 



  MICXet-1                    i   he  PuzzleMidlel                 ChessPuzzleMidlet .png             __Jcom.nokia.mid.appl.chep.ChessPuzzteMidlet                   I 



   ':Шф              \     •;••• -л  •' m         ]             1                   j 



                                                                                                   •   •  •  :  •  .  ,  •  ;  '  .  -  : ч  . • - . : . . . , • Cancel  I 



                                 Рис.      15.3 . Изменение   названия   основного класса                                мидлета 



Пришло время попытаться откомпилировать наш проект. Здесь все, как в детстве 

с заводными игрушками: после того как разобрал и снова собрал, главное — чтобы 

опять заработало. Возможны небольшие различия в трактовке кода разными ком- 

пиляторами.   Мне   пришлось   добавить                                               несколько   приведений   возвращаемых 


----------------------- Page 163-----------------------

 164       Глава   15 . Защита  приложений 



типов, чтобы WTK   перестал ругаться  и выдавать ошибки. В остальном операция 

прошла успешно, и пациент ожил на эмуляторе в лучшем  виде. 

Теперь  весь  исходный  код приложения в нашем полном распоряжении. Разби- 

раться в чужой  программе всегда не очень просто, однако ничего нового для нас 

мы не увидим, поскольку практически все классы и методы нами уже рассмотре- 

ны. Любое приложение начинается с метода startApp(), это и есть та печка, от ко- 

торой мы будем плясать. 

Как мы видим, в программе используются классы Ful I Canvas и Devi ceControl из Nokia 

API, то есть кроме как на аппаратах Nokia данное приложение нигде работать не 

будет. Благо, мы уже знаем, как адаптировать мидлет и расширить его модельный 

ряд. Для этого нужно заменить класс Full Canvas на обычный Canvas, а также акку- 

ратно вырезать  всю  иллюминацию и любые упоминания о классе Devi ceControl. 

В успешной транспортабельности   приложения вы можете  сами убедиться.  Хотя 

немного не совпали размеры экрана и часы заехали на доску, но это не такая боль- 

шая проблема (рис. 15.4). 



                                                SGH-E710S«r 



                Рис.  15.4 . Перекомпилированное приложение успешно запущено 

                               на телефоне другого производителя 



Вопроса локализации (перевода  на другой язык) приложений мы уже  касались 

в одной из глав. В программе шахматных задач у нас есть возможность поучиться 

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

английском языке, — инструкции, пункты меню и прочее — содержится в файле 

ресурса   lang.xx. Все  вопросы локализации решаются   в одном классе  Local. Рас- 

смотрим процедуру   получения требуемого  текста: 



public   static  synchronized  String   getText(int  i .   String   as[]) { 

try   { 


----------------------- Page 164-----------------------

                                                                             Java  Decompiler          1 6 5 



     if(loc   —  null) 

     loc   -   new Local О; 

     InputStream   inputstream   =    loc.getClassO.getResourceAsStream 

                                                      (Viang.11   + phoneLang); 



     if (inputstream   —  null) 

          inputstream   =    loc.getClassO.getResourceAsStreamC71ang.xx"); 

     if(inputstream   —  null) 

          return   "NoLang"; 

     DataInputStream     datainputstream = new DatalnputStream(inputstream) ; 

     datainputstream.skipBytes(i * 2 ) ; 

     short wordO » datainputstream.readShortO : 

     datainputstream.skipBytes(wordt) - i * 2 - 2) ; 

     String s = datainputstream.readUTFO ; 

     datainputstream.close() : 

     if(as != null ) 

          if(as.length == 1 ) { 

               s = replaced .     "XU", as[O]) : 

          }  else { 

               forOint j = 0; з < as.length : j++ ) 

                   s - replace(s , "%" + j + "U", as[j]) : 

          } 

     return s ; 

 } 

catch(Exception     exception ) { 

     return "Err" ; 



public   static   fina l  String  phoneLang = 

                                System.getProperty("mi croediti on. 1 ocal e") : 

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

локализации  телефона,  возвращаемый   методом   getProperty                        системного   класса 

System, и создать файл  1 ang с полученным расширением. Ниже в классе Local опре- 

делены числовые константы для всех выводимых строчек. По коду следует разоб- 

раться, каким образом проиндексирован файл ресурса. Сначала указаны номера 

позиций файла, с которых начинаются требуемые строчки, затем идут сами строч- 

ки.   Осталось корректно сформировать файл с русским переводом. Возможно, по- 

требуется дополнительная логика перевода в Unicode, которой мы пользовались 

в программе   BookReader.   Вот   и вся локализация. Если вы  внимательно   читали 

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

самостоятельно. 

Как    вы уже   поняли, все необходимое   в наших руках, и можно воротить  все, что 

душе угодно. Например, не нравится нам возможность обнуления установленно- 

го рекорда: что это за рекорд такой, который можно отменить  одним  нажатием. 

Дело за малым: найти место, где формируется меню Top Scores, а затем  исключить 


----------------------- Page 165-----------------------

166           Глава   15 . Защита  приложений 



из   него последний  пункт   Reset. В основном классе  приложения видим, что   любое 

меню формируется   с помощью метода   InitMenuScrdnt   i ,                              int   a i [ ] .  ...)спомощью 

следующих  команд: 



f o r ( i n t  k   =   0;  k   <   ai.length :  k++) 

     m_ListScr.append(Local,getText(ai[k]) .                null) : 



Видим,  что   пункты   меню   формируются                      из   строчек,  представленных               константа- 

ми,    переданными методу в массиве ai . Теперь ищем вызов метода   InitMenuScr, со- 

ответствующий   формированию раздела  меню Top Scores. Без труда   обнаруживаем 

нужный   вызов  в методе MainMenuScreenDone: 



case   4:   //   Ч0041 



      int   ai3[]   -  { 

           11.    10.  22 



      } : 

      InitMenuScr(HIGHSCORE_MENU_SCR,             ai3 . Local.getText(25) .       null .   null . 0 ) ; 

      break ; 



 В   массиве   ai3   содержатся            константы,  представляющие                   три   пункта       меню: Easy, 

 Difficult  и  Reset. Очевидно, что следует оставить  только два  первых   элемента мас- 

 сива, а последний  аккуратно                 удалить: 



 int   ai3[]   =  {  11.  10  };                                              ' 



Ломать  — не строить. Мы удалили   всего лишь несколько символов. Главное -                                          най- 

 ти  точку   применения, а сами  усилия   могут быть   мизерными. Компилируем  и за- 

 пускаем  приложение. А теперь, как говорится, найдите десять  отличий  (рис.  15.5). 



                                                                                                     J-J.XJ 

                                                               •File  Tools  Help 



           Рис.    15.5 . Ликвидация   пункта меню сводится к удалению из кода трех символов 



 Кроме  бесценного   опыта   программистов   от  Nokia,  что   еще  полезного   можно из- 

 влечь из кода данного приложения? Решили вы, например, написать шахматы   для 


----------------------- Page 166-----------------------

                                                               Обфускация       16 7 



мобильного телефона, чтобы в дороге играть с товарищем. Кое-что можно будет 

использовать, например доску, фигурки, алгоритм  перемещения фигур по доске 

и секундомер. Однако если вы решите продавать свои шахматы, то придется ри- 

совать и программировать  все заново, чтобы не нарушить авторские права. Пре- 

имущество  заключается   в том, что  всегда   можно проконсультироваться   с гото- 

вым кодом и разобраться, как это делают профессионалы. 



Защита  приложений 



Легальное распространение мобильных приложений происходит через WAP-сай- 

ты или SMS-сервис специализированных мобильных издательств, которые вни- 

мательно следят за авторскими правами и отчислениями разработчикам продук- 

тов. При уведомлении об успешной загрузке приложения на ваш телефон система 

инициализации генерирует  счет, который вы оплачиваете через вашего операто- 

ра сотовой связи. Удобно, надежно, а если что, можно обратиться в службу под- 

держки издательства:  крупные и солидные конторы обычно заботливо относятся 

к своим клиентам. 

Теперь допустим, что вы приобрели какое-то приложение легальным образом. Кто 

мешает скачать его с телефона в компьютер и выложить на общий доступ в Интер- 

нет? Кто после этого будет платить деньги, когда ту же самую игру можно получить 

бесплатно? Для защиты от подобного копирования можно ввести регистрацию: сам 

продукт   распространяется бесплатно, а вот его активация или расширение проб- 

ной версии до  полнофункциональной стоит денег.  Вам  потребуется   реализовать 

алгоритм регистрации и организовать удобную систему оплаты. Как вариант, мож- 

но при первом запуске  приложения сгенерировать  случайным  образом уникаль- 

ный ключ и сохранить его в хранилище записей. Регистрационный механизм дол- 

жен выслать код активации, соответствующий лишь данному ключу. При вводе кода 

активации приложение генерирует собственный код, соответствующий сгенериро- 

ванному ключу, и при совпадении разблокирует необходимую функциональность. 

Существует   масса методов защиты и регистрирования, однако вернемся к началу 

этой главы. Казалось бы, что может быть проще: декомпилировать упрямое прило- 

жение, вырезать   все  проверки авторизации  и запросы  регистрационных  кодов, 

а затем вернуть в исходное положение. Против таких умельцев, как мы, и действий, 

именуемых не иначе, как «взлом», существует способ защиты, называемый обфус- 

кацией. Не пугайтесь этого слова. Оно образовано от английского obfuscate — запу- 

тывать, сбивать с толку. Сам метод заключается в предварительной модификации 

исходного  кода программы с целью затруднить   процесс декомпиляции и анализа 

приложения, 



 Обфускация 



 Более всего  распространена символьная обфускация, которая меняет имена пе- 

ременных, названия классов, полей и методов   на бессмысленный набор симво- 

лов. Например, был у нас метод GetPassword(), а после обфускации он превратил- 

ся   в  LK23gdcasjJKHKJh().   Однако  многие декомпиляторы  уже   справились  с этим 


----------------------- Page 167-----------------------

168          Глава   15 . Защита  приложений 



подвохом и заменяют длинные нечитаемые имена на более удобные, типа Method_l (), 

Method_2() и т. п. 

Существует        и другой  метод   символьной  обфускации, реализованный  в J2ME 

WTK, работу которого мы рассмотрим на примере. Этот метод действует с точно- 

стью до наоборот, и переименовывают  классы, поля и методы  однобуквенными 

именами, повторяющимися в каждом  классе. Согласитесь, чтобы понять вот та- 

кой   пассаж: 

b a = а.с(а.   с.а); 

потребуется  немало усилий и тщательного изучения всей структуры  программы, 

которая может быть совсем не маленькой. 

Обфускатор — это не что иное, как утилита, модифицирующая исходный код при- 

ложения до неузнаваемости. К символьным относится, также обфускатор, встро- 

енный в J2ME  WTK.   Для  того чтобы  скомпилировать приложение с предвари- 

тельной обфускацией, при упаковке проекта вместо пункта Create Package (Создать 

архив) следует выбрать команду Create Obfuscated   Package (Создать архив с обфус- 

кацией). Попробуем в деле встроенный обфускатор на тех же  шахматах. 

Попробуем   теперь   декомпилировать   получившиеся                     классы.  Сразу      бросается 

в   глаза, что название сохранил лишь основной класс приложения, остальные на- 

зываются теперь нехитро: а, Ь, с, d, e, f,...          Посмотрим на оригинал  конструктора 

основного приложения. Так он выглядел до нашей обфускации: 



 public ChessPuzzleMidletO 



 { 



     m_Display   =  n u l l ; 

     m_HighScoreArray     =  n u l l ; 

     m_CurrentScreen = 0 : 

     m_LastScreen » 0 ; 

     MAIN_MENU_SCR = 1; 

     MODE_MENU_SCR - 2 ; 

     PUZZLE_MENU_SCR = 3 ; 

     HIGHSCORE_MENU_SCR = 4 ; 

     SPLASH_SCR = 5 : 

     PUZZLE_SCR = 6 : 

     LEVEL_MENU_SCR = 7  : 

     HIGHSCORE_DIS_SCR - 8 : 

     HIGHSCORE_DEL_SCR = 9 : 

      INSTRUCTION_SCR = 10 ; 

     TIMEO_MODE = 1 : 

     CASUAL_MODE = 0; 

     m_LastSelectedMenuIndex = 0; 

     m_AppMessage - "": 

     m_PlayMenu   -  false ; 

     m_ScreenWidth = 128 ; 

     m_ScreenHeight - 128 ; 


----------------------- Page 168-----------------------

                                                                                                    Обфускация                 1 6 9 



С  такими  именами  констант и переменных   разобраться   в  программе  и внести 

в нее изменения нам не составило труда. Боюсь даже предположить, сколько бы 

это заняло времени и сил, если бы код выглядел   следующим образом: 



public     ChessPuzzleMidletO 



{ 

      d   =  n u l l ; 

      flddo     -  n u l l ; 

      ab   -  0: 

      s   -  0; 

      p   -  1 ; 

      W =   2: 



      Y   =   3  :                      • 



      f   =  4 : 

      1 - 5 ; 

      z   -  6; 

      aa   =   7; 

      T   =   8; 

      D   = 9 : 

      ac    = 10; 

      _fldchar   =   1; 

      Q =   0; 

      _fldcase   =   0; 



      P   =  "  "   • 



      ae    -  false ; 



      v   -  128;    ' 



      m = 128; 



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

 становится пыткой не для слабонервных, и поверьте, потраченное на расшифров- 

 ку время уж точно не стоит двух-трех долларов. 

 Кроме  символьной, существуют   и другие  методы   обфускации, реализованные 

 в обфускаторах  второго порядка. Такие обфускаторы наряду с методами символь- 

 ной обфускации используют  также обфускацию данных и графа потока управле- 

 ния.  Здесь  есть большой простор для введения людей  в заблуждение: изменение 

 структур и местоположения данных, кодировка данных, объединение данных в мас- 

 сивы или наоборот, разделение  массивов  на отдельные   переменные; добавление 

 неиспользуемых   данных   и формирование на их основе ложных  условий, замена 

 статических   присваиваний на вызовы методов; замена условий   циклов на более 

 сложные и масса других хитростей, об которые можно сломать мозги, пытаясь ра- 

 зобраться в зашифрованном алгоритме. Следует отметить, что если обфускаторы 

 первого порядка немного сокращали размер программы, экономя на названиях, то 

 второй порядок обфускации может разбазарить столь ценные нам килобайты. 

 Как вы сами понимаете, обфускация не является панацеей от взлома, будь то не- 

 санкционированное расширение пробной версии или просмотр секретного алго- 


----------------------- Page 169-----------------------

 170       Глава   15 . Защита  приложений 



ритма. Единственная задача обфускаторов  — сбить взломщика с толку  и макси- 

мально затруднить   понимание, анализ и модификацию программы. В этой главе 

мы подошли к проблеме с двух сторон — декомпиляция и защита. Нам придется 

сталкиваться   и с тем, и с другим. В любом случае, при компиляции своих про- 

грамм не брезгуйте обфускаторами, даже если ваши программные изыски никого 

не заинтересуют, символьная обфускация немного оптимизирует вашу програм- 

му и освободит пару килобайтов для дополнительной функциональности или лиш- 

ней картинки. 

Как  ни печально, но на этом этапе нам придется  распрощаться. Я  надеюсь, что 

чтение не прошло для вас даром, что вы многому научились и открыли для себя 

увлекательный   мир  мобильного   программирования. Реализация  приведенных 

примеров, их понимание и совершенствование — вот золотой ключик от дверей 

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

Опыт приходит с практикой! 



                                         * * * 



Эта книга не является учебником, документацией  или серьезным методическим 

пособием, это   именно самоучитель       — практические  советы,  минимум  теории 

и максимум практики, личные наблюдения автора, начиная с самого нуля. Если 

эта книжка уже  вся исчиркана разными пометками, изрядно потрепана и залита 

кофе, значит потраченное мной и вами время не прошло даром. 

Если вы действительно  серьезно увлеклись   мобильным программированием, то 

у  вас неизбежно появится масса  вопросов, благо на сайте разработчиков языка 

java.sun.com/j2me/docs/ имеется исчерпывающая документация, масса книг и ста- 

тей (правда, на английском языке). Также за советом можно сходить на специа- 

лизированные форумы, например, forum.juga.ru/, раздел которого так и называет- 

 ся: J2ME   — форум   о Java  в мобильнике. Пользуйтесь   поиском, не стесняйтесь 

 спрашивать: программисты любят помогать друг другу. 

 Количество обладателей мобильных телефонов уже приблизилось к двум милли- 

 ардам  (!)   и неуклонно растет, и каждый   из них — потенциальный пользователь 

 ваших программ. Простота в использовании и всеобщая доступность в любое вре- 

 мя — вот залог успеха. Будущее именно за мобильными, портативными техноло- 

 гиями, так что запрыгивайте в поезд, пока не поздно. В качестве бонус-трека вас 

 ожидают практические советы по продаже мобильных приложений. 

 Удачи вам! 


----------------------- Page 170-----------------------

 Bonus 



 Распространение мобильных 

приложений.  Практические советы 

по продажам мобильного контента 



Теперь у нас есть все необходимое для разработки игр и приложений для мобиль- 

ных телефонов: аппарат с поддержкой J2ME, кабель для соединения с компьюте- 

ром, необходимое  программное обеспечение, а главное — знания и практические 

навыки. 

Если вы уже  начинили свой телефон самодельными играми и приложениями, по- 

делились ими с друзьями, похвастали  перед коллегами, то можно сделать и следу- 

ющий шаг: попытаться продать свои труды. И пусть вас не смущает, что програм- 

мка вышла коротенькая, а разработка заняла не более нескольких дней: специфика 

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

Игры для мобильных телефонов существенно  отличаются   от обычных компью- 

терных игр, которые пишут целые команды разработчиков, крупные фирмы с боль- 

шим штатом сотрудников, а потому написать что-то конкурентоспособное на этом 

рынке одному человеку   просто не под силу. 

Ограниченная функциональность платформы, жесткие требования к размеру при- 

ложения не позволяют писать зрелищные, интерактивные побоища типа Quake. 

Маленькое 64-килобайтное тельце программы не способно вместить логику серь- 

езной военно-экономической стратегии, да и не стоит велосипеду   тягаться   с го- 

ночной машиной. Игра для мобильника и не должна быть сложной и наворочен- 

ной.  Обычно она заполняет небольшую  паузу ввиду  доступности  в любое время 

и в любом месте. 

Красивая, элегантная, аккуратно реализованная идея имеет хорошие шансы при- 

нести создателю   некоторую прибыль. Остановимся на процедуре  получения де- 

нег за свой программный продукт, схеме движения финансовых потоков и, вооб- 

ще, на том, как это работает. 

Допустим,  что вы реализовали  какое-то нехитрое, но оригинальное приложение, 

например шуточный мобильный тест на беременность, с предложением приложить 

палец к обведенному месту на экране и с какой-то вероятностью получить один из 

заготовленных  ответов. Если вы считаете, что данная программа может принести 

вам несколько долларов, то можно смело попытаться ее продать. В любом  случае, 

хуже не будет, ведь денег за эту попытку с вас никто не возьмет.  Главный вопрос: 

кто и каким образом будет заниматься продажей? Для этого и существуют фирмы, 

которые специализируются   на продажах   мобильного контента. За определенный 


----------------------- Page 171-----------------------

172        Распространение мобильных приложений 



процент они размещают ваш продукт  на своей платформе, рекламируют   его, осу- 

ществляют всю техническую  поддержку, связанную с процедурой продажи. 

Для начала нужно связаться с представителем такой фирмы и заключить договор 

о передаче  прав на продажу,  где и будет оговорена сумма, которую  вы  получите 

с каждой проданной копии программы. 

Обычно договор не передает фирме все права на продукт, а только предоставляет 

право распространения. То есть  договор   не должен  препятствовать   вам заклю- 

чать соглашения параллельно с несколькими фирмами. 

Устоявшаяся   цена в  России на момент  написания этой книги, которую   платит 

конечный пользователь  за скачанную игру  или программу,  — 2 доллара. Теперь 

посмотрим, какая часть от этой суммы окажется у вас в кармане. Деньги за прило- 

жение снимаются со счета  пользователя   и поступают   оператору   сотовой связи. 

Оператор, как правило, половину этой суммы забирает себе, а половину перево- 

дит на счет фирмы-распространителя.                           * 

На этом этапе фирма имеет свой интерес и делит с вами прибыль согласно дого- 

вору. При заключении договора не стесняйтесь торговаться  в пределах разумно- 

го.  Здесь ставка  по России колеблется   в пределах   30-50 центов в пользу  разра- 

ботчика за каждую  проданную копию программы. 

Очевидно, что повышение вашего благосостояния напрямую зависит от числа лю- 

дей, скачавших программный продукт. Дело за малым: заинтересовать пользовате- 

ля   полетом  своей фантазии, оригинальностью, новизной, практической пользой. 

Здесь уже все в ваших руках. 

Особое внимание следует уделить соблюдению авторских прав, ответственность 

за нарушение которых лежит  целиком на разработчике. Когда речь идет о ком- 

мерческом использовании, последствия нарушений могут быть очень серьезны- 

ми, и наступают   они куда с большей вероятностью, чем при установке операци- 

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

программного кода не должно идти вразрез с чьими бы то ни было интересами. 

Чтобы не быть голословными, приведем конкретные примеры и адреса наиболее 

крупных в России фирм-распространителей контента для мобильных телефонов: 

www.playmobile.ru, www.pLayfon.ru, www.infon.ru, www.javagames.ru. В любом   случае, 

такие фирмы совсем не трудно  найти в поисковиках по любому запросу с ключе- 

выми словами данной тематики. Серьезные фирмы отличаются  качественно вы- 

полненным сайтом, ежедневно обновляемыми новостяги, наличием менеджеров, 

готовых  оперативно выйти на связь. 



Если вы попробовали свои силы на российском рынке мо» ильного программиро- 

вания и чувствуете высокий коммерческий потенциал ваш   й программы, то мож- 

но шагнуть  и за границу. Здесь также существуют давно на 1аженные схемы рас- 

пространения программ, например через сайт forum.nokia.со  n/business. 

В современном мире сотовый телефон стал неотъемлемым атрибутом нашей жиз- 

ни. Человек без мобильника вызывает сейчас не меньшее удивление, чем лет   10 

назад  человек   с мобильником. Именно поэтому   технология   чрезвычайно перс- 

пективна. Каждый третий человек на планете —" обладатель  мобильного телефо- 

на, а значит, и потенциальный потребитель программного продукта. Всех благ! 


----------------------- Page 172-----------------------

 Приложение                                      1 



 Полный  листинг 

 программы                                   «Змейка» 

//               -        -       — 

                                                               ч 

// Пример игры        "Змейка " для телефона       Nokia 

// 

// Реализован в рамках книги 

// "Программируем        игры для мобильных телефонов " 

// 

// (с ) Voolkan 

//         ----           -               - 



import    javax.mi croedi ti on.mi dlet.MIDIet : 



import     javax.microedition.lcdui.Canvas ; 

import     javax.microedition.lcdui.ChoiceGroup ; 

import     javax.microedition.lcdui.Command : 

import     javax.microedition.lcdui.CommandListener ; 

import     javax.microedition.lcdui.Display ; 

import     javax.microedition.lcdui.Displayable ; 

import     javax.microedition.lcdui.Form; 

import     javax.microedition.lcdui.Gauge : 

import     javax.microedition.lcdui.Graphics : 

import     javax.microedition.lcdui.Image ; 

import     javax.microedition.lcdui.Imageltem : 

import     javax.microedition.lcdui.Item ; 

import     javax.microedition.lcdui.List ; 

import     javax.microedition.lcdui.Stringltem ; 



import     javax.microedition.rms.RecordStore ; 

 i mport javax.mi croedi ti on.rms.RecordEnumerati on : 

 import javax.mi croedi ti on.rms.RecordStoreExcepti on ; 



 import    java.util.Vector : 

 import    java.util.Random ; 



 import    java.io.IOException : 



 import    com.nokia.mid.ui.FullCanvas : 


----------------------- Page 173-----------------------

1 74        Приложение   1 . Полный   листинг  программы          «Змейка» 



import com.nokia.mid.ui.DeviceControl : 

import com.nokia.mid.sound.Sound : 



// основной   класс SnakeGame . реализующий сценарий    смены экранов 

public class SnakeGame extends MIDlet      implements CommandListener { 



   private Display display :             // менеджер дисплея 

   private Snake curSnake ;              // объект змейки 

   private List menu :                   // стартовое меню 

   private Command ok :                  II команда  выбора пункта  меню 

   private ChoiceGroup    levelChoice : // меню выбора уровня сложности    игры 

   private Command set :                 // команда  выбора уровня сложности   игры 



   public void destroyAppCboolean destroy ) { 

       curSnake=null : 

       notifyDestroyed() : 



   public void    pauseAppO { } 



    // стартовый   метод мидлета 

    public void   startAppO { 

       // массив строк с названиями     пунктов меню 

       String menuOptions[ ] = {"New   Game"."Set Level"."High    Score"} : 

       // создать стартовое меню 

       menu = new ListC",List.IMPLICIT,menuOptions.null) : 

       // создать команду    выбора пункта меню 

       ok = new Command("Ok" , Command.OK , 1) : 

       // добавить команду в меню 

       menu.addCommand(ok) : 

       // создать команду установки уровня игры 

       set = new Command("Set" , Command.BACK , 1) : 

       // установить блок прослушивания     команд для меню 

       menu.setCommandLi stener(thi s) ; 

       // получить ссылку на менеджер дисплея 

       display «• Display.getDisplay(this) : 



       // отобразить на экране стартовую     заставку 

       Image title = null ; 

       try { 

          // создать объект заставки 


----------------------- Page 174-----------------------

                                              Полный   листинг  программы  «Змейка»                   1 7 5 



       title » Image.createlmage("/title.png") ; 

    }  catch  (IOException    ioe ) { } 



    // создать    новый  элемент формы 

    Imageltem item = new ImageltemC".title.ImageItem.LAYOUT_CENTER."") : 

    // отобразить    заставку 

    showNewScreent'"SNAKE".ok.item): 



// блок прослушивания      команд 

public void commandAction(Command с          Dispiayable d ) { 

    // если была    команда Ok из главного меню игры 

    if (с ~ ok & & d —      menu ) { 

       // получить     выбранный  пункт   меню 

       int selIndex • menu.getSelectedlndexO ; 

       switch(selIndex) { 

           // первый    пункт меню : новая игра 

           case 0 : 

               // создать объект     змейки 

               curSnake - new Snake() : 

               // создать объект треда автоматического         передвижения 

              Thread moveThread • new Thread(curSnake) : 

               // начать   выполнение треда 

              moveThread . startO : 

               // отобразить    змейку на экране 

              display.setCurrent(curSnake); 

              break ; 

           // второй    пункт меню : установить уровень сложности 

           case 1 : 

               // массив строк с названиями       пунктов   меню 

               String   levelOptions[ ] = {"Level 1"."Level 2" , 

                                             "Level   3"."Level 4"} ; 

               // создать    меню  выбора уровня   игры 

               level Choi се = new Choi ceGroupC" . List . EXCLUSIVE . 

                                                   levelOptions.null); 

               // выделить пункт меню,      соответствующий 

               // текущему уровню     сложности 

               levelChoice.setSelectedIndex(5-getParameter(l).true); 

               // отобразить    новую форму 

               showNewScreen("Set Level".set.1evelChoice) : 

               break : 

        •    // третий   пункт меню : посмотреть текущий     рекорд 

           case 2 : 

               // получить строку с текущим       рекордом 


----------------------- Page 175-----------------------

1 76         Приложение   1 . Полный   листинг  программы «Змейка» 



                 String    strScore  -  (new  Integer(getParameter(0))).toStringO ; 

                 //   создать  объект   элемента-строки 



                 Stringltem    highScoreltem    »  new  StringltemC"".strScore) ; 



                 //   отобразить   новую  форму 



                 showNewScreenC'HIGH       SCORE".ok.highScoreltem): 

                 break; 



       // если была    команда Ok . вызванная не из меню 

       if(C"-ok & & d!=menu ) 

           // отобразить на экране стартовое меню 

          display.setCurrent(menu); 



       // если была    команда Set из меню установки уровня       сложности 

       if(с —    set ) { 

           try { 

              // массив    параметров   игры 

              byte buff[ ] = {0.5} : 

               // открыть хранилище записей с именем       "SNAKE" 

              RecordStore    recordStore = RecordStore.openRecordStoreC"SNAKE" . 

                                                                              true) ; 

               // получить список     записей  хранилища 

              RecordEnumeration re = recordStore.enumerateRecords(null ,           null . 

                                                                           false); 

               // если хранилище не пусто 

              if (re.numRecords()!=0 ) { 

                  // получить id записи параметров       игры 

                  int id = re.nextRecordldO : 

                  // считать запись с параметрами       игры 

                  buff = recordStore.getRecord(id) ; 

                  // вычислить    текущий уровень игры 

                  buff[l ] = (byte)(5 - levelChoice.getSelectedlndexO) : 

                  // записать    параметры   игры 

                  recordStore.setRecord(id.buff.0.2); 

               }  else { 

                  // добавить    новую запись параметров     игры 

                  recordStore.addRecord(buff . 0 .   2 ) ; 



           } catch(RecordStoreException       rse ) { 

           } 

           // отобразить на экране стартовое меню 

           di splay.setCurrent(menu) ; 


----------------------- Page 176-----------------------

                                             Полный   листинг  программы  «Змейка»                  1 7 7 



// метод showNewScreen создает и отображает форму с элементом            item 

private void showNewScreentString        title.Command с Item item ) { 

    // создать форму для нового экрана 

    Form newScreenForm - new Form(title) : 

    // добавить команду для возврата из формы 

    newScreenForm.addCommand(c); 

    // добавить элемент в форму 

    newScreenForm.append(item); 

    // установить блок прослушивания       команд 

    newScreenForm.setCommandListener(this); 

    // отобразить форму на экране 

    display.setCurrent(newScreenForm); 



// метод getParameter      считывает из хранилища     записей  параметры   игры 

// и возвращает один из них , определенный        входным  параметром   index 

private byte getParameter(int       index ) { 

    // массив параметров     игры 

    byte buff[ ] = {0.0} ; 

    try { 

       // открыть хранилище записей с именем        "SNAKE" 

       RecordStore    recordStore = RecordStore.openRecordStoreC'SNAKE" . 

                                                                       true) ; 

        // получить список    записей   хранилища 

       RecordEnumeration re = recordStore.enumerateRecordstnull ,           null . 

                                                                    false); 

        // если хранилище не пусто 

       if (re.numRecords()!=0 ) { 

           // получить id и считать запись параметров         игры 

           buff = recordStore . getRecord ( re . nextRecordldO) : 

        } 

    }  catch(RecordStoreException      rse ) { 

    } 

    // вернуть параметр 

    return buff[index] ; 



 // класс SnakePart представляет       каждый  элемент  змейки 

private class SnakePart extends Object { 



    private int x :         // координата х 


----------------------- Page 177-----------------------

1 78          Приложение   1 . Полный   листинг  программы              «Змейка» 



       private int у ;          // координата у 

       private int part ;       // код части тела змеи 

       private int dir :       // код направления     движения 



       // конструктор ; принимает       все прля  как аргументы 

       public SnakePartCint _x . int _y . int _part , int _dir ) { 

           x=_x : 

           У=_У : 

           part=_part : 

           di r=_di г; 

        } 

        // получить    код части   тела  змеи 

       private int getPartO { return part ; } 

        // получить код направления       движения 

       private int getDirO { return dir ; } 

        // получить    координату х 

       private int getX O { return x; } 

        // получить координату у 

       private int getY O { return y ; } 

        // установить    тип части и направление 

       private void setPartDir(int _part , int jiir ) { 

           // если    второй аргумент -1 . 

           // то направление остается        неизменным 

           part=_part ; 

           1f(_d1r!--l) dir=_dir ; 



    // класс    змейки 

    private class Snake extends Full Canvas         implements Runnable { 



    // константы , определяющие       направление   движения 

    private int UP = 0;               // вверх 

    private    int DOWN = 3 ;         // вниз 

    private    int LEFT • 1;          // влево           '                      . 

    private in t RIGHT - 2 ;          // вправо 



    private    int HEAD = 0  ;        // голова   змеи 

    private int TAIL - 1;             // хвост змеи 

    private int BODY = 2 :            // тело змеи 

    private int ACLOCKWISE_TURN = 3 ;          // сгиб тела против часовой       стрелки 

    private int CLOCKWISE_TURN = 4 :           // сгиб тела по часовой      стрелке 



    private Vector snake :            // хранилище   элементов   змейки 


----------------------- Page 178-----------------------

                                           Полный   листинг  программы  «Змейка»              1 7 9 



private int width ;            // ширина экрана 

private int height :           // высота экрана 

private int xHead ;            // координата х головы змейки 

private int yHead ;            // координата у головы змейки 

private Image heart ;          // картинка сердца 

private int xHeart ;           // координата х сердца 

private int yHeart ;           // координата у сердца 

private   Image[ ] images ;    // массив картинок   элементов змейки 

private int imageSize :        // размер картинки   элемента змейки 



private int direction :        II текущее направление змейки 

private boolean eatFlag ;      // флаг удлинения   змейки 

private boolean gameOverFlag ;       // флаг  конца  игры 



private RecordStore recordStore ; // хранилище данных 

private int recordID ;         // ID записи  параметров 

private byte level ;           // уровень сложности   игры 

private byte speed ;           // коэффициент  вычисления  задержки  треда 

private byte highScore ;       // текущий рекорд 



 // конструктор класса Snake 

public SnakeO { 

    // конструктор родительского класса 

    superO ;                                             Г 

    // создать массив для картинок-частей     змейки 

    images = new   Image[20] ; 

    // создать объекты    картинок для каждой части 

    try { 

       // голова во всех направлениях 

       images[0] = Image.createlmageC'/HeadUp.png") : 

       images[l] = Image.createlmageC'/HeadLeft.png") ; 

       images[2] = Image.createlmageC'/HeadRight.png") : 

       images[3] = Image.createlmageC'/HeadDown.png") ; 

       // хвост во всех направлениях 

       images[4] - Image.createlmageC'/TailUp.png") : 

       images[5] = Image.createlmageC'/TailLeft.png") : 

       images[6] - Image.createlmageCVTailRight.png") ; 

       images[7] = Image.createlmageC'/TailDown.png") : 

        // тело 

       images[8] = Image.createlmageC'/BodyUp.png") ; 

       images[9] = Image.createlmageC'/BodyLeft.png") ; 

       images[10] = images[9] ; 

       images[ll] = images[8] ; 

        // сгиб тела против часовой стрелки 


----------------------- Page 179-----------------------

1 80          Приложение  1 . Полный  листинг  программы «Змейка» 



           images[12] • Image.createlmageC'/TurnDownLeft.png") ; 

           images[13] = Image . createlmageCVTurnUpLeft.png") ; 

           images[14] » Image.createlmageC'/TurnDownRight.png") : 

           images[15] = Image . createlmageC'/TurnllpRight.png") : 

           // сгиб тела по часовой        стрелке 

           images[16] = images[14] : 

           images[17] • images[12] : 

           images[18] - 1mages[15] : 

           images[19] = images[13] : 

           // сердце 



           heart    »  Image.createlmageC'/heart.png"); 



        } 

        catch   (IOException    ioe ) { } 



        imageSize = images[O].getWidth() ; 

        // создать    вектор , хранящий    элементы   змейки 

        snake = new VectorO ; 

        // получить ширину экрана 

       width = getWidthO : 

        // получить     высоту экрана 

        height - getHeightO ; 

        // установить     координаты    головы в центре экрана 

        // для транспортабельности        координаты    кратны imageSize 

        xHead = ((width/2)/imageSize)*imageSize : 

       yHead =     ((height/2)/imageSize)*imageSize ; 

        // создать элемент      головы змейки 

        SnakePart parts • new SnakePartUHead.yHead.HEAD.UP) : 

        // добавить     элемент в вектор 

        snake.addElement(partS) : 

        // создать    элемент тела    змейки 

        parts - new SnakeParttxHead.yHead+imageSize.BODY.UP) ; 

        snake.addElement(partS) : 

        // создать    элемент хвоста     змейки 

        parts - new SnakePart(xHead,yHead+2*imageSize.TAIL.UP) : 

        snake.addElement(partS) : 

        // установить     текущее направление      движения 

        direction • UP; 

        // получить новые координаты для сердца 

        setNewHeartO : 

        // сбросить флаг окончания игры 

        gameOverFlag = false : 



        // параметры     для долговременного     хранения 

        // первый байт - текущий        рекорд 

        // второй байт - уровень сложности игры 


----------------------- Page 180-----------------------

                                          Полный  листинг  программы «Змейка»                 1 8 1 



   byte buff[ ] - {0,5} ; 

   try { 

       // открыть хранилище записей с именем     "SNAKE" 

       recordStore • RecordStore.openRecordStore("SNAKE" ,     true) ; 

       // получить список записей    хранилища 

      RecordEnumeration re - recordStore.enumerateRecords(null ,       null . 

                                                                false); 

       // если хранилище пусто 

       if  (re.numRecords()==0 ) 

          // добавить новую запись параметров игры 

          recordID - recordStore.addRecord(buff , 0 .   2 ) ; 

      else 

          // получить id записи параметров игры 

          recordID = re.nextRecordldO : 

       // считать запись параметров игры 

      buff = recordStore.getRecord(recordID) ; 

       // первый байт : текущий рекорд 

      highScore - buff[0] : 

       // второй байт : уровень сложности игры 

       level • buff[l] ; 

       // установить   коэффициент  вычисления  задержки 

       speed = 100 ; 

    }  catchCRecordStoreException   rse ) { 



// метод перерисовки    экрана 

public void paint(Graphics g ) { 



    // очистить экран 

   g.setColor(OxFFFFFF) : 

   g.fillRect(0.0,width.height); 

   g.setColor(OxOOOOOO); 

    // нарисовать рамку 

   g.drawRect(O.O.width-l.height-l); 

    // получить длину змейки 

    int snakeLen = snake.size() : 

    // если  поднят флаг  конца игры 

    if ( gameOverFlag ) { 

       // включить вибрацию на 100 мс 

       DeviceControl.startVibra(lOO.lOO) : 

       // вывести сообщение конца игры 

       g.drawStringCGAME    OVER",width/2,height/2-10.g.HCENTER|g.TOP) ; 

       // строка   сообщения счета игры 


----------------------- Page 181-----------------------

1 82         Приложение   1 . Полный   листинг  программы  «Змейка» 



           String scoreStr ; 

           // массив долговременных       данных 

          byte b u f f О = new byte[2] ; 

           // если установлен     новый   рекорд 

           if(highScore < snakeLen ) { 

              highScore • (byte)snakeLen ; 

              // инициализировать      массив новыми    параметрами 

              buff[0 ] = highScore : 

              buff['l] - level : 

              try { 

                  // записать новый      рекорд в хранилище 

                  recordStore.setRecord(recordID.buff . 0.2) ; 

               } catch(RecordStoreException       rse ) { 

               } 

               // установить    цвет текста красным 

              g.setColor(OxffOOOO); 

               // сформировать строку с новым        рекордом 

               scoreStr = new StringC'HIGH SCORE :   "+snakeLen) : 

           // если   рекорд не установлен 

           }   else 

               // сформировать строку со счетом 

               scoreStr = new StringC'YOUR       SCORE : "+snakeLen) : 

           // вывести    счет 

           g.drawString(scoreStr.width/2.height/2+10.g.HCENTER|g.TOP) : 

           // если флаг     конца игры не поднят 

        } else { 

           // для каждого элемента       змейки 

           for ( int   1=0 : i<snakeLen : i++ ){ 

               // вывести    картинку , индекс которой     вычисляется 

               // с помощью комбинации       кода части   тела и кода   направления 

               g.drawImage(images[4*((SnakePart)snake.elementAt(i)).getPart()+ 

                   ((SnakePa rt)snake.elementAt(i)).getDi r()] . 

                   ((SnakePart)snake.elementAt(i)).getX(). 

                   ((SnakePart)snake.elementAt(i)).getY(). 

                  g.HCENTER | g.VCENTER) ; 

            } 

           // вывести    картинку сердца 

           g.drawImage(heart.xHeart.yHeart.g.HCENTER           | g.VCENTER) : 



    // метод setNewHeart ; получает новые координаты          для сердца 

    public void    setNewHeart 0 { 

        // получить    количество   возможных позиций по координате х 


----------------------- Page 182-----------------------

                                            Полный   листинг  программы  «Змейка»                1 8 3 



   int xPartMaxNum = (width-5)/imageSize ; 

   // получить количество     возможных позиций по координате у 

   int yPartMaxNum = (height-5)/imageSize ; 

   // получить длину змеи 

   int len = snake.sizeO ; 

   // создать объект     генератора случайных чисел 

   Random   rnd • new Random() ; 

   // флаг   корректной   генерации 

   boolean setFlag - false ; 

    // повторяем , пока не сгенерируем     корректные координаты    сердца 

   while ( !setFlag ) { 

       // поднять флаг    корректной   генерации 

       setFlag - true ; 

       // сгенерировать случайную     координату х 

       xHeart « imageSize*(Math.abs(rnd.nextInt(mxPartMaxNum+l) ; 

       // сгенерировать случайную     координату у 

       yHeart = imageSize*(Math.abs(rnd.nextInt())PyPartMaxNum+l); 

       // проверить совпадение координат сердца 

       // с координатами    элементов   змеи 

       for ( int i-0; i<len-l : i++ ) 

          if ( xHeart —     ((SnakePart)(snake.elementAt(i))).getX( ) & & 

              yHeart —    ((SnakePart ) (snake . elementAtd ))).getYO ) 

       // опустить флаг    корректной   генерации 

       setFlag = false ; 



// метод keyPressed обработки      нажатий  клавиш 

public synchronized     void keyPressed(int    keyCode ) { 

    // если флаг   конца игры не поднят 

    if ( IgameOverFlag ) { 

       switch   (keyCode ) { 

           // проверить , можно ли передвигаться в заданном       направлении 

           // и вызвать соответствующую функцию       передвижения 

           case KEYJUM2 : 

              checkMove(xHead . yHead-imageSize) : 

              moveUpt) ; 

              break : 

          case KEY_NUM4 : 

              checkMove(xHead-imageSize .    yHead) : 

              moveLeftO ; 

              break; 

           case KEY_NUM6 : 

              checkMove(xHead+imageSize,     yHead) : 


----------------------- Page 183-----------------------

1 84         Приложение   1 . Полный   листинг  программы   «Змейка» 



                 moveRightO : 

                 break; 

             case KEYJUM8 : 

                 checkMove(xHead, yHead+imageSize) ; 

                 moveDownO ; 

                 break; 



          // вызвать перерисовку     экрана 

          repaintO : 

       }  else { 

          // GameOver ! отобразить стартовое меню 

          display.setCurrent(menu) : 



    // метод автоматического     продвижения  змейки 

   public void run() { 

       // пока не поднят флаг конца игры 

       while   (IgameOverFlag ) { 

           // синхронизировать с управлением 

          synchronized    (this) { 

              // продвинуть    змейку в текущем   направлении 

              ifCdirection—UP)     {checkMove(xHead,   yHead-7) :  moveUpO; } 

              if(direction==LEFT)     {checkMove(xHead-7 . yHead) :  moveLeftO: } 

              if(direction==RIGHT)     {checkMove(xHead+7 , yHead) ;  moveRightO: } 

              if(direction=-DOWN)     {checkMove(xHead, yHead+7) :   moveDownO; } 

           } 

           // вызвать перерисовку     экрана 

           repaintO; 

          try { 

              // приостановить    тред на sleep*level    миллисекунд 

              Thread.sieep(1evel*speed); 

           } catch   (InterruptedException    e ) { 



    // проверить    возможность  передвижения 

    // в точку с координатами     (хН.уН ) 

    public void checkMoveOnt      xH , int yH ) { 

       // для каждого    элемента  змеи 

       for ( int 1-3 : i<snake.size()-l : i++ ) { 


----------------------- Page 184-----------------------

                                            Полный  листинг  программы «Змейка»                  1 8 5 



       // если проверяемая точка совпадает с координатами элемента 

       if ( хН —    ((SnakePart)(snake.elementAt(i))).getX( ) && 

       .   уН —    ((SnakePart)(snake.elementAt(i))).getYO ) 

          // поднять флаг конца игры 

          gameOverFlag=true; 



    // проверить совпадение координат передвижения 

    // с координатами сердца 

   if ( хН == xHeart & & уН —      yHeart ) { 

       // поднять флаг удлинения змейки 

       eatFlag - true ; 

       // пройти в цикле частоты от 100 до 1000 герц с шагом 50 

       for(int 1=100 : i<=1000 ; i+=50 ) { 

          // создать звуковой объект 

          Sound beep = new Sound(i.50) ; 

          // воспроизвести     звуковой объект 

          beep.play(l) ; 

       } 

       // увеличить скорость 

       speed--; 

    } 

   else 

       // опустить флаг удлинения змейки 

       eatFlag - false ; 



// передвижение влево 

private void moveLeftO { 

    // если текущее направление направо , то возврат 

    if ( direction==RIGHT ) return ; 

    // сдвинуть координату     головы влево 

    xHead-=imageSize; 

    // проверить выход за левую      границу экрана 

    if ( xHead <=• imageSize/2+1 ) { gameOverFlag=true ; return ; } 

    // получить первый элемент змеи 

    SnakePart head = (SnakePartKsnake.firstElementO) ; 

    // если текущее направление вверх 

    if ( direction==UP ) 

       // заменить голову на сгиб против часовой стрелки 

       head.setPartDi r(ACLOCKWISE_TURN.LEFT) ; 

    else { 

       // если текущее направление вниз 

       if ( direction-=DOWN ) 


----------------------- Page 185-----------------------

1 86         Приложение   1 . Полный  листинг  программы   «Змейка» 



              // заменить   голову на сгиб по часовой     стрелке 

             head.setPartDi r(CLOCKWISE_TURN,LEFT) ; 

          else // если текущее направление       влево 

              // заменить   голову на тело 

              head.setPartDi rCBODY,LEFT) ; 



       // создать новый элемент для      головы 

       SnakePart sPart • new SnakePart(xHead,yHead,HEAD,LEFT) ; 

       // добавить    голову первым  элементом   вектора 

       snake.i nsertElementAt(sPa rt,0) ; 



       // если флаг удлинения     змеи поднят 

       if (eatFlag ) { 

          // получить новые координаты      для сердца 

          setNewHeartC); 



       else { 

           // продвинуть хвост , 

           // удалить последний    элемент 

          snake.reirioveElement(snake.lastElement()); 

           // назначить хвостом    предпоследний   элемент 

        - ((SnakePart)snake.1astElement()).setPartDir(TAIL.-1) ; 



       // установить    текущее направление 

       direction = LEFT ; 

    } 



    // передвижение    вправо 

    private void moveRightO { 

       // если   текущее направление налево , то возврат 

       if ( direction==LEFT ) return : 

       // сдвинуть    координату  головы  вправо 

       xHead+=imageSize; 

       // проверить выход за правую      границу экрана 

       if ( xHead >- width-imageSize/2 ) { gameOverFlag=true ;        return ; ) 



       // если   текущее направление змейки     вверх . 

       // заменить    голову на сгиб по часовой    стрелке 

       if ( direction —      UP ) ((SnakePartKsnake.firstElementO)) . 

                     setPartDi r(CLOCKWISEJ"URN , RIGHT) ; 

       else { 

           // если текущее направление змейки вниз , 

           // заменить   голову на сгиб против часовой     стрелки 

           if ( direction == DOWN ) ((SnakePartKsnake.firstElementO)) . 


----------------------- Page 186-----------------------

                                             Полный   листинг  программы  «Змейка»                  18 7 



                     setPartDi r(ACLOCKWISE_TURN.RIGHT) ; 

       // продвижение по лрямой ,     заменить   голову на тело 

       else    ((SnakePart)(snake.firstElement())).setPartDir(BODY.RIGHT) : 



    // добавить новый    элемент   головы  змейки 

   SnakePart    sPart - new SnakePart(xHead.yHead.HEAD.RIGHT) ; 

    snake.insertElementAt(sPart.O); 

    // если   поднят флаг   удлинения 

    if  (eatFlag ) 

       // получить новые координаты       для сердца 

       setNewHeartO; 

   else { 

       // продвинуть    хвост 

       snake . removeEl ement (snake . lastElementO) ; 

        ((SnakePart Jsnake.lastElementO) . setPartDi r(TAIL.-l) : 

    } 

    // установить    текущее направление    движения 

   direction = RIGHT : 



// передвижение     вверх 

private void movellpO { 

    // если   текущее направление    вниз , то возврат 

    if ( direction ~      DOWN ) return : 



    // сдвинуть    координату   головы  вверх 

   yHead-=imageSize; 

    // проверить    выход за верхнюю    границу  экрана 

    if ( yHead <= imageSize/2+1 ) { gameOverFlag=true ;        return : } 



    // если текущее направление       змейки  влево . 

    // заменить    голову на сгиб по часовой     стрелке 

    if ( direction==LEFT )     ((SnakePartKsnake.firstElementO)) . 

                  setPartDi r(CLOCKWISE_TURN.UP) ; 

    else { 

        // если   текущее  направление   змейки  направо , 

        // заменить   голову на сгиб против часовой      стрелки 

       if ( direction==RIGHT )      ((SnakePartMsnake.firstElementO)) . 

                      setPartDi r(ACLOCKWISEJURN.UP) ; 

        // продвижение по прямой, заменить       голову на тело 

       else    ((SnakePart)(snake.firstElementO)).setPartDir(B0DY.UP) ; 



    // добавить новый     элемент  головы   змейки 


----------------------- Page 187-----------------------

1 88          Приложение 1 . Полный листинг программы «Змейка» 



       SnakePart     sPart  -  new   SnakePart(xHead.yHead.HEAD.UP); 



       snake.insertElementAt(sPart.O); 



       //   если   поднят   флаг   удлинения 



        i f  (eatFlag) 



           //   получить   новые   координаты   для   сердца 



           setNewHeartO; 



       else    { 



           //   продвинуть    хвост 



           snake.removeElement(snake.lastElementO); 



            ((SnakePart)snake.lastElementO).setPartDir(TAIL.-l); 



        // установить текущее направление движения 

        direction = UP ; 

    } 



    // передвижение вниз 

    private void moveDownO { 

        // если текущее направление вверх, то возврат 

        if ( direct1on=4JP ) return ; 

        // сдвинуть координату        головы вниз 

        yHead+=imageSize; 

        // проверить выход за нижнюю         границу экрана 

        if ( yHead >= height-imageSize/2 ) { gameOverFlag-true,- return ; } 



        // если текущее направление змейки влево . 

        // заменить     голову на сгиб против часовой стрелки 

        if ( direction—LEFT ) ((SnakePartKsnake.firstElementO)) . 

                       setPartDi r(ACLOCKWISE_TURN.DOWN) : 

        else { 

            // если текущее направление змейки направо , 

            // заменить     голову на сгиб по часовой стрелке 

            if ( direction==RIGHT ) ((SnakePartXsnake.firstElementO)) . 

                           setPartOi r(CLOCKWISE_TURN.DOWN) : 

            // продвижение по прямой, заменить голову на тело 

            else ((SnakePart)(snake.firstElement())).setPartDir(BODY.DOWN) : 



        // добавить новый элемент         головы змейки 

        SnakePart sPart = new SnakeParUxHead.yHead.HEAD.DOWN) ; 

        snake.insertElementAt(sPart.O); 



        // если поднят флаг удлинения 

        if (eatFlag ) 


----------------------- Page 188-----------------------

                                              Полный   листинг  программы  «Змейка»                1 8 9 



          // получить новые координаты для сердца                 • • 

          setNewHeartO : 

      else { 

          // продвинуть хвост 

          snake.removeElement(snake.1astElement()): 

          ((SnakePart)snake.lastElementO).setPartDir(TAIL.-l); 

       } 

       // установить текущее направление движения 

      direction - DOWN : 

   } 

} // class Snake 

} // class SnakeGame 


----------------------- Page 189-----------------------

Приложение 2 



 Полный листинг 

 программы «BookReader» 



 // Пример программы     "BookReader" 

 // 

 // Реализован в рамках книги 

 // "Программируем    игры для мобильных телефонов" 

 // 

 // (с ) Voolkan 



 import   javax.microedition.midlet.MIDlet ; 

 import javax.microedition.lcdui.* ; 

 import Java.util.Stack ; 



 import   java.io.InputStream : 

 import java.io.EOFException : 

 import java.io.IOException ; 



public class BookReader extends MIDlet { 



 private Display display :            // менеджер дисплея 

 private BookCanvas     fCanvas :     // экранный фрагмент     книги 



,public void destroyApp(boolean       flag ) { 

 } 



 public void pauseAppO { 



 // стартовый    метод  мидлета 

 public void startAppO { 

      // получить ссылку на менеджер дисплея 

     display = Display.getDisplay(this) : 


----------------------- Page 190-----------------------

                                       Полный  листинг  программы «BookReader»               1 9 1 



    // создать новую форму с именем    основного класса 

    Form form = new Form(getClass( ) .getNameO) ; 

    // создать объект экранного фрагмента     книги 

    fCanvas • new BookCanvasO ; 

    // отобразить экранный фрагмент книги 

    display.setCurrenttfCanvas); 



// класс экранного фрагмента книги 

public class BookCanvas extends Canvas { 



    private Stack   Pagelndex ; // стек смещений страниц от начала текста 

    public BookCanvasO { 

         // создать новый объект стека    номеров страниц 

         Pagelndex - new StackO : 



     // метод перерисовки объекта экранного фрагмента 

    protected   void paint(Graphics g ) { 

         // получить ширину и высоту рабочей области     экрана 

         int gw = g.getClipWidthO ; 

         int gh - g.getClipHeightO : 



         // создать шрифт 

         Font font = Font.getFont(Font.FACE_M0N0SPACE . 

                                    Font.STYLE_PLAIN . 

                                    Font.SIZE_SMALL) : 

         // установить шрифт 

         g.setFont(font) : 



         // инициализировать   поток 

         InputStream is - getClass0.getResourceAsStreamC7story.txt") ; 

         MyDatalnputStream mdis = new MyDatalnputStream(is) ; 



         int x=0,y=0 :     // текущее положение вывода на экране 

         int offset-0 ;    // смещение по исходному   тексту 



         // очистка экрана 

         g.setColor(255,255.255); 

         g.fillRect(O,O.gw.gh) : 

         g.setColor(O.O.O); 



         // получить смещение для очередной    страницы 

         if(!Pagelndex.emptyO) 


----------------------- Page 191-----------------------

1 92         Приложение   2 . Полный  листинг  программы «BookReader» 



         offset-C(Integer)PageIndex.peek()).intValuet) : 



         try { 

              // сместиться по тексту до необходимой страницы 

              mdis.skipBytes(offset) ; 

          } catchUOException      ioe ) { } 



         String sWord : 

         do { 

              // прочитать очередное слово из потока 

              sWord - mdis.readWordO : 

              // продвинуть смещение по файлу на одно слово 

              offset+=sWord.1ength(); 

              // если текущая позиция и пиксельная ширина слова 

              // не выходят за экран 

              if(x+font.stringWidth(sWord)<=gw) { 

              // отобразить слово , начиная с текущей позиции 

              g.drawStringtsWord .     x.y . g.TOPjg.LEFT) : 

              // сместить текущую позицию на пиксельную ширину слова 

              x+=font.stringWidth(sWord); 

              II если слово последнее в строке , перевести         текущую позицию 

              // в начало новой строки на дисплее 

              if (mdis.b_endline ) { y+=font.getHeight() ; x-0 : } 

          } 

          // слово не входит по ширине с текущей позиции 

         else { 

              // перейти на новую строку 

              y+-font.getHei ght() ; 

               // если на экране есть место для новой строки . 

              // отобразить слово с начала строки 

              1f(y+font.getHei ght()<gh ) 

                   g.drawString(sWord .    O.y . g.TOP|g.LEFT) : 

               // перевести    текущую позицию на ширину слова 

              x-font.stringWidth(sWord); 

          } 

          // повторять до тех пор , пока есть место 

          // для новой строки или не кончится файл 

     } while(y+font.getHeight()<gh & &   !mdis.b_endFile) : 



     int index ;      // смещение начала     страницы 



     // если цикл закончился чтением слова , которое не было 

     // отображено на экране , сместить начало новой страницы 

     // назад на одно слово 

     if(mdis.b_endLine) index-offset ; else index=offset-sWord.length() : 


----------------------- Page 192-----------------------

                                               Полный   листинг  программы  «BookReader»                         1 9 3 



     //   поместить    в   стек  начало   следующей   страницы 

     if(!mdis.b_endFile)        Pagelndex.push(new       Integer(index)) : 

     //   закрыть   поток 

     t r y { 

          mdis.closet); 

     }  catch(IOException       ioe)   {} 



     //   метод   обработки   нажатий   клавиш 

     public    void   keyPressedCint     keyCode){ 

          //   обработка    нажатий   клавиш 

          switch(keyCode)       { 

               case    KEYJUM1: 

                     //  удалить    из  стека   начала 

                     //  следующей   и  текущей   страницы 

                     Pagelndex.popO; 

                     ifdPagelndex.emptyO)           Pagelndex.popO; 

                     repaintO: 

                     break : 

                case KEYJUM2 : 

                     // перерисовать экран 

                     // (начало следующей страницы уже в стеке ) 

                     repaintO : 

                     break : 

                case KEY_NUM0 : 

                     // очистить стек и перерисовать экран 

                     Pagelndex.removeAl1 Elements() ; 

                     repaintO; 

                     break; 



}   //  class   BookCanvas 

}   / / c l a s s  BookReader 



import java.io.InputStream ; 

import java.io.DatalnputStream ; 

import java.io.EOFException : 

import java.io.IOException : 



public class MyDatalnputStream extends OatalnputStream { 



// кодовая строка преобразования символов 

private String WIN1251_TO_UNICODE - 


----------------------- Page 193-----------------------

1 94         Приложение   2 . Полный   листинг  программы «BookReader» 



"\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\u00bl\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"; 



public   boolean   b_endLine » false ; // флаг    конца  строки 

public   boolean   b_endFile = false ; // флаг    конца  файла 



public   MyDatalnputStreamdnputStream       is ) { 

     super(is); 



 // метод   чтения  слова из потока 

public   String   readWordO { 

     int   i=0 ;    // счетчик   символов в текущем    слове 

     b_endFile = false ; 

     char[] word = new char[100] : // массив для чтения         слова 

     try { 

          // читать слово посимвольно до пробела        или перевода   строки 

          do   word[i++]=(char)convert(readl)nsignedByte()) : 

          while   (word[i-l]!=32 && word[i-l]!=10) : 

     }   catch(EOFException    ioe ) { 

          // конец   файла 

          i--:   b_endFile - true ; 

     } 

     catchdOException      ioe ) { } 



     // если   слово   заканчивается   переводом  строки ,  поднять  флаг 

     if(word[i-l]==10)     b_endl_ine=true :  else  b_endLine=false : 

     // возвратить    строку с прочитанным     словом 

     return   (new String(word.O.i)) ; 



 // метод   конвертации   симолов в Unicode 

 char  convert(int   ch ) { 

     return   (ch < 128 ) ? (char)ch : WIN1251_T0_UNIC0DE.charAt(ch-128) : 


----------------------- Page 194-----------------------

Приложение 3 



Полный  листинг 

программы   «AdressBook» 



// Пример программы      "AdressBook" 

// 

// Реализован в рамках книги 

// "Программируем     игры для мобильных телефонов" 

// 

// (с ) Voolkan 



import   javax.microedition.midlet.MIDlet: 

import   javax.microedition.lcdui.Command; 

import   javax.microedition.lcdui.CommandListener; 

import    javax.microedition.lcdui.Display; 

import   javax.microedition.lcdui.Displayable; 

import   javax.microedition.lcdui.Form; 

import   javax.microedition.lcdui.List ; 

import    javax.microedition.lcdui.TextField; 

import   javax.microedition.lcdui.DateField; 

import    javax.microedition.lcdui.TextBox; 

import java.util.Date : 

import java.util.Calendar ; 

import   java.io.ByteArrayOutputStream ; 

import    java.io.ByteArraylnputStream ; 

import   java.io.DataOutputStream : 

import    java.io.DatalnputStream : 

import java.io.IOException ; 

import    javax.microedition.rms.RecordStore ; 

import javax.mi croediti on.rms.RecordEnumerati on : 

import javax.microediti on.rms.RecordComparator : 

i mport java x . mi croedi ti on . rms . RecordFi H e r  : 

import javax.mi croedi ti on.rms.RecordStoreExcepti on ; 



public class AddressBook       extends MIDlet   implements   CommandListener 


----------------------- Page 195-----------------------

1 96         Приложение   3 . Полный   листинг  программы «AdressBook» 



private Display    display ;                  // менеджер дисплея 

private RecordStore     recordStore ;         // хранилище    записей 

private List nameList ;                       // список   имен 

private int redndexes[] :                     // массив ID записей . 

                                              // соответствующий    списку имен 

private Command    add,ok,next,back ;         // команды   переходов  между  экранами 

private TextBox    tbName.tbPhone.tbEMail :   // экраны   ввода параметров   записи 

private Form dateForm :                       // форма   ввода дня рождения 

private DateField    dateField ;              // поле ввода дня рождения 



public void destroyApp(boolean      destroy ) 

    notifyOestroyedO ; 



public void pauseAppO { } 



public void    startAppO { 

    try { 

       // открыть   хранилище   записей с именем   "Address-Book" 

       recordStore = RecordStore.openRecordStoreC"Address-Book" ,        true) : 

    } catch   (RecordStoreException    rse ) { } 



    // получить ссылку на менеджер дисплея 

    display - Display.getDisplay(this) ; 



    // создание объектов    команд 

    ok = new Command("Ok" . Command.OK . 1) ; 

    add " new CommandC'Add" .    Command.BACK , 1) ; 

    next • new Command("Next" . Command.OK . 1) ; 

    back - new CommandC'Back" ,    Command.BACK . 1) : 



    // поле ввода    имени 

    tbName » new TextBox("Name:"."".15,TextField.ANY) ; 

    tbName.addCommand(next); 

    tbName.addCommand(back) : 

    tbName.setCommandListener(this); 



    // поле ввода номера     телефона 

    tbPhone - new TextBox ( "Number:11 ."". 15 . TextField.PHONENUMBER) : 



    tbPhone.addCommand(next) : 

    tbPhone.addCommand(back) : 

    tbPhone.setCommandLi stener(this) : 



    // поле ввода    электронной  почты 

    tbEMail - new TextBox("E-Mail:"."".35,TextField.EMAILADDR) : 


----------------------- Page 196-----------------------

                                              Полный листинг программы «AdressBook»                        1 9 7 



    tbEMa i1.addCommand(next) : 

    tbEMail.addCommand(back); 

    tbEMail.setCommandLiPtener(this): 



    // экран    ввода  дня   рождения 

    dateField * new DateField("Birthday",DateField.DATE) ; 

    dateForm = new      FormC"') ; 

    dateForm.append(dateField); 

    dateForm.addCommand(next) : 

    dateForm.addCommand(back); 

    dateForm.setCommandListener(this) : 



    // создать список      имен                  , 

    BuildNameListO ; 

    // поиск    именинника 



    String   name   •  SearchBirthdayO : 



    //  если   имя   найдено 



    if(name!=null)      { 



        // создать форму      напоминания 

        Form   remindForm = new Form("Reminder") ; 

        // добавить     напоминание в форму 

        remindForm.append Сname + " has a birthday           today!") ; 

        // добавить     команду   возврата 

        remindForm.addCommand(ok); 

        remi ndForm.setCommandLi stener (this) ; 

        // отобразить     форму 

        display.setCurrent(remindForm);            . 

    }  else 

        // отобразить     список имен на экране 

        di splay.setCurrent(nameLi st) : 



// нетод SearchBirthday .        возвращает строку с именем        именинника 

// если в этот день именинников          нет , то возвращает     null 

private String SearchBirthday(j { 

    String name » null ; 

    try { 

        // создать объект фильтра по дню рождения 

        BirthdayFilter      filter = new    BirthdayFilterO ; 

        // получить список       записей с подходящим      днем  рождения 

        RecordEnumeratiop . re - recordStore.enumerateRecords(fiHer .               null . 

                                                                         false) : 

        // получить ID записи 

        int id - re.nextRecordldO : 

        // получить     запись по ID 


----------------------- Page 197-----------------------

1 98         Приложение   3 . Полный   листинг  программы «AdressBook» 



       byte[ ] record • recordStore.getRecord(id) : 

       // преобразовать    запись в байтовый    поток 

       ByteArraylnputStream     bais = new     ByteArraylnputStream(record) ; 

       // создать   поток , поддерживающий    чтение по типу 

       DatalnputStream    dis • new DataInputStream(bais) ; 

       // считать из потока строку с именем 

       name =-dis,readUTFC) : 

    } 

   catch(RecordStoreException       rse ) { } 

 •  catchdOException      ioe ) { } 

    // вернуть имя    именинника 

    return name : 



// метод BuildNameList ; создает список       имен из адресной    книги 

private void BuildNameListО { 

    // создать объект    списка 

   nameList = new ListC'Address-Book" ,       List.IMPLICIT) : 

    nameLi st.setCommandLi stener(this) : 

    // команда   добавления   записи 

    nameList.addCommand(add) : 

    // команда   получения   параметров   записи 

    nameList.addCommand(ok) : 

    try { 

       // получить    количество   записей 

       int size = recordStore.getNumRecordsO ; 

       // создать    массив  для хранения ID записей 

       reclndexes - new     int[size] ; 

       // создать объект компаратора в алфавитном         порядке 

       Alphabetical Ordering    comparator = new AlphabeticalOrderingO : 

       // получить список записей      хранилища 

       RecordEnumeration re - recordStore.enumerateRecords(null ,          comparator . 

                                                                   false),: 

        // индекс   массива  хранения ID записей 

       int i=0 : 

        // бесконечный    цикл ; выход из цикла   происходит по 

        // формированию    исключения  получения'ID   следующей   записи . 

        // после того как было получено ID последней        записи 

       while(true ) { 

           // получить ID следующей      записи 

           int id = re.nextRecordldO ;            , 

           // записать ID в массив 

           reclndexes[i++]=id; 

           // получить    запись по Ш 

           byte[ ] record - recordStore.getRecord(id) ; 


----------------------- Page 198-----------------------

                                          Полный   листинг  программы  «AdressBook»               19 9 



           // преобразовать    запись в байтовый   поток 

          ByteArraylnputStream     bais=new ByteArraylnputStream(record) ; 

           // создать поток , поддерживающий    чтение по типу 

          DatalnputStream    dis - new DatalnputStream(bais) : 

           // считать из потока    первую строку и добавить в список 

          nameList.append(dis.readUTF().null) : 



   catch(RecordStoreException      rse ) 

   catch(IOException     ioe ) { } 



// блок   прослушивания   команд 

public void    commandAction (Command с    Displayable d ) { 

    // команда Add   отображает   экран  ввода  имени 

    if(c==add) 

       display.setCurrent(tbName); 

       // команда Ok 

    if(c—ok) { 

       // если   команда  вызвана из экрана со списком     имен 

       if(d==namel_ist) { 

       try { 

           // получить в массиве reclndexes      ID,необходимой  записи 

           int id - recIndexes[nameList.getSelectedIndex()] : 

           // считать необходимую запись 

          byte[ ] record - recordStore.getRecord(id) ; 

           // преобразовать    запись в байтовый   поток 

          ByteArraylnputStream     bais=new ByteArrayInputStream(record) ; 

           // создать поток , поддерживающий     чтение по типу 

          DatalnputStream    dis = new DatalnputStream(bais) ; 

           // создать форму для отображения      параметров 

           Form infoForm = new    Form("") : 

           // считать строки с параметрами и добавить в форму 

           infoForm.append(dis.readUTF()+"\n") : 

           infoForm.append(dis.readUTF()+"\n"); 

           infoForm.append(dis.readUTFO); 

           // создать объект даты рождения 

          Date birthday = new Date(dis.readLongO) : 

           // создать объект    поля  ввода  даты 

          DateField df = new DateField("".DateField.DATE) ; 

           // установить   дату рождения в поле ввода     даты 

          df.setDate(birthday) : 

           // добавить даты    рождения в форму отображения    параметров 

           infoForm.append(df); 

           // добавить команду    возврата к списку имен 


----------------------- Page 199-----------------------

2 0 0         Приложение 3 . Полный  листинг программы «AdressBook» 



           i nfoForm.addCommand(ok) ; 

           i nfoForm.setCommandLi stener(thi s ) : 

           // отобразить форму на экране 

           display.setCurrent(infoForm); 

        } 

       catch(RecordStoreException        rse ) { } 

       catch(IOException      ioe ) { } 

        }  else {          // если   команда  вызвана из формы     параметров   записи 

           // вернуться к списку имен 

           display.setCurrent(nameList); 



    // команда    Next : перейти к следующему      полю ввода 

    if(c==next) { 

        // из поля    ввода имени - к полю ввода номера        телефона 

        if(d==tbName) display.setCurrent(tbPhone) ; 

        // из поля    ввода  номера телефона - к полю       ввода e-mail 

        if(d==tbPhone) display.setCurrentCtbEMail) : 

        // из поля    ввода e-mail - к дню рождения 

        i f(d==tbEMai1 ) display.setCurrent(dateForm) ; 

        // из поля    ввода дня рождения 

        if(d==dateForm) { 

     -       // создать байтовый     поток  вывода 

           ByteArrayOutputStream       baos - new ByteArrayOutputStream() ; 

            // создать    поток  вывода , поддерживающий     запись по типу 

           DataOutputStream      dos = new DataOutputStream(baos) ; 

           try { 

               // записать     введенные параметры в поток вывода 

               dos.writeUTF(tbName.getStri ng()) : 

               dos.writeUTF(tbPhone.getStringO); 

               dos.writeUTF(tbEMail.getStringO); 

               dos.wri teLong(dateFi eld.getDate().getTi me()) ; 

               // добавить запись в хранилище 

               recordStore.addRecord(baos.toByteArray() . 0 . baos.sizeO) ; 

            } 



            catchdOException      ioe)   {} 

            catch   (RecordStoreException      rse)  {} 

            //  создать   список   имен 

            BuildNameListO : 

            // отобразить список      имен на экране 

           display.setCurrent(nameList); 



     // команда    Back : возврат к предыдущему      полю ввода 


----------------------- Page 200-----------------------

                                           Полный листинг   программы  «AdressBook»                  2 0 1 



    if(c==back)   { 

       // из поля    ввода имени - к списку имен 

       if(d==tbName) display.setCurrent(nameList) ; 

       // из поля ввода     номера  телефона - к полю ввода     имени 

       if(d==tbPhone) display.setCurrent(tbName) ; 

       // из поля ввода e-mail - к полю ввода номера         телефона 

       if(d--tbEMail) display.setCurrent(tbPhone) : 

      -II из поля в-вода даты рождения - к полю ввода e-mail 

       if(d==dateForm) display.setCurrent(tbEMai1) : 



// класс фильтра     записей по дням   рождения 

private class BirthdayFilter       implements RecordFilter { 



    // метод   сравнения   записей 

    public boolean matches(byte[ ] candidate ) { 

       // преобразовать     запись в байтовый    поток 

       ByteArraylnputStream     bais = new ByteArraylnputStream(candidate) ; 

       // создать поток поддерживающие чтение по типу 

       DatalnputStream dis - new DatalnputStream(bais) : 

       // день рождения 

       Date birthDate « new Date'O ; 

       try { 

           // считать строковые параметры       записи 

           dis.readUTFO ; 

           dis.readUTFO ; 

           dis.readUTFO : 

           // считать дату рождения 

           birthDate.setTi me(di s.readLong()) ; 

        } 

       catch   (IOExcepti.on ioe ) { return false : } 



        // получить два календаря с текущей       датой 

       Calendar    rightNow • Calendar.getlnstanceO : 

       Calendar birthday = Calendar.getlnstanceO : 

        // установить дату рождения      именинника 

       bi rthday.setTi me(bi rthDate) ; 

        // сравнить день и месяц рождения с текущей        датой 

        i f(rightNow.get(Calendar.DAY_OF_MONTH)== 

                  birthday.get(Calendar.DAY_OF_MONTH) && 

           ri ghtNow.get(Ca1enda r.MONTH)— 

                  bi rthday.get(Ca1enda r.MONTH) ) 

           return true ; 

        else 


----------------------- Page 201-----------------------

2 0 2        Приложение   3 . Полный   листинг  программы «AdressBook» 



           return   false ; 



// класс компаратора      записей по алфавиту 

private class Alphabetical Ordering        implements RecordComparator { 

    // метод сравнения     записей 

    public int compare(byte[ ] reel . byte[ ] rec2 ) { 

        // преобразовать     записи в байтовый    поток 

       ByteArraylnputStream      baisl = new ByteArraylnputStream(recl) ; 

       ByteArraylnputStream      bais2 = new ByteArrayInputStream(rec2) : 

        // создать   потоки , поддерживающие чтение по типу 

       DatalnputStream     disl = new DataInputStream(baisl) ; 

       DatalnputStream     dis2 = new DataInputStream(bais2) ; 

        // строки   имен 

       String    namel = null : 

       String    name2 » null : 

       try { 

           // считать строки с именами 

           namel = disl.readUTF О  : 

           name2 - dis2.readUTF () : 

        } 

       catch    (IOException   ioe ) { } 

        // лексикографическое сравнение       строк 

        int result = namel.compareTo(name2) : 

        if (result < 0 ) 

           // первая    запись предшествует     второй 

           return    RecordComparator.PRECEDES ; 

       else 

           if (result == 0 ) 

               // записи    содержат  идентичные   имена 

               return   RecordComparator.EQUIVALENT ; 

           else 

               // вторая    запись предшествует    первой 

               return   RecordComparator.FOLLOWS ; 



 } // class AddressBook 


----------------------- Page 202-----------------------

Литература 



•    Брюс Эккель. Философия Java. 3-е  изд. СПб.: Питер, 2003. 

•    Жерздев С. B.Java 2 Micro Edition. ИТЛаб, ННГУ, ВМК, 2003. 

•    Вартан Пирумян. Платформа программирования J2ME для портативных   уст- 

   ройств. КУДИЦ-Образ, 2002. 

•    Кен Арнольд, Джеймс Гослинг.   Язык   программирования   JAVA.  СПб.: Питер, 

    1997. 

Использованы  материалы   порталов   и форумов: 



•   java.sun.com/j2me/docs 



•    www.juga.ru 



•    www.javagu.ru 


----------------------- Page 203-----------------------

                           Евгений Леонидович Буткевич 

      Пишем программы и игры для сотовых телефонов 



              Главный редактор                                                   Е. Строганова 

              Заведующий редакцией                                                   А. Кривцов 

              Руководитель  проекта                    .                       А. Крузенштерн 

              Литературный редактор                                               Е. Бурнашова 

              Иллюстрации                                     .       В. Демидова, С. Романов 

              Художник                                                              С. Мапикова 

              Корректоры                                       И. Смирнова, Н. Шелковникова 

              Верстка                                                                С. Романов 



                                       Лицензия ИД № 05784 от 07.09.01. 

         Подписано в печать  18.11.05. Формат 70X100/16. Усл. п. л. 16,77. Тираж 3000 экз. Заказ№6647 . 

                  ООО «Питер Принт». 194044, Санкт-Петербург, Б. Сампсониевский пр., 29а. 

Налоговая льгота — общероссийский классификатор продукции ОК 005-93, том 2; 953005 — литература   учебная. 

               Отпечатано с готовых диапозитивов в ФГУП «Печатный двор» им. A.M .  Горького 

                        Федерального агентства  по печати и массовым коммуникациям. 

                                  197110, Санкт-Петербург, Чкаловский пр., 15. 


----------------------- Page 204-----------------------

К Л У Б                  П Н О/ Ш С                      С И О НА Л 



В1997 году по инициативе генерального директора Издательского дома «Питер» 

Валерия Степанова и при поддержке деловых кругов города в Санкт-Петербурге 

был основан «Книжный  клуб Профессионал». Он собрал под флагом клуба про- 

фессионалов своего дела, которых объединяет  постоянная тяга к знаниям и любовь 

к  книгам.  Членами клуба  являются   лучшие студенты  и известные  практики из раз- 

ных сфер деятельности, которые хотят стать или уже стали профессионалами в той 

или иной области. 

Как  и все развивающиеся   проекты, с течением   времени   книжный   клуб   вырос 

в  «Клуб Профессионал».  Идею клуба сегодня формируют три основные «клубные» 

функции: 



      •  неформальное  общение и совместный досуг интересных людей; 

      •   участие в подготовке специалистов высокого класса 

        (семинары, пакеты книг по специальной литературе); 

      •   формирование и высказывание мнений современного профессионала 

        (при встречах и на страницах  журнала). 



КАК ВСТУПИТЬ   В КЛУБ? 



Для вступления в «Клуб Профессионал»   вам необходимо: 

      •  ознакомиться с правилами вступления в «Клуб   Профессионал» 

        на страницах журнала  или на сайте www.piter.com; 

      •   выразить свое желание  вступить в «Клуб   Профессионал» 

        по электронной почте postbook@piter.com  или по тел. (812) 103-73-74 ; 

      •   заказать книги на сумму не менее 500 рублей в течение любого времени 

        или приобрести комплект «Библиотека   профессионала». 



«БИБЛИОТЕКА         ПРОФЕССИОНАЛА» 

Мы предлагаем вам получить все необходимые знания, подписавшись на «Библио- 

теку  профессионала».  Она для тех,  кто экономит не только  время, но и деньги. 

 Покупая комплект - книжную полку ((Библиотека профессионала», вы получаете: 



      •   скидку 1 5 % от розничной цены издания, без учета почтовых  расходов; 

      •   при покупке двух или более комплектов - дополнительную  скидку 3% ; 

      •   членство   в «Клубе   Профессионал»; 

      •   подарок -  журнал  «Клуб  Профессионал». 

                                                       пзалтепьскип а ом 

Закажите  бесплатный  журнал 

                                                            0 

 «Клуб Профессионал» .                                "^SP  www!  р\гв " 


----------------------- Page 205-----------------------

ЗАКАЗАТЬ  КНИГИ ИЗДАТЕЛЬСКОГО ДОМА «ПИТЕР» 

МОЖНО ЛЮБЫМ УДОБНЫМ ДЛЯ ВАС СПОСОБОМ: 



           •   по телефону: (812) 103-73-74; 

           •   по электронному адресу: postbook@piter.com; 

           •   на нашем сервере: www.piter.com; 

           •   по почте: 197198,  Санкт-Петербург, а/я 619, 

              ЗАО «Питер Пост». 



ВЫ МОЖЕТЕ ВЫБРАТЬ ОДИН ИЗ ДВУХ СПОСОБОВ  ДОСТАВКИ 

И   ОПЛАТЫ ИЗДАНИЙ: 



$g?    Наложенным    платежом   с  оплатой   заказа   при получении  посылки на 

      ближайшем почтовом отделении. Цены на издания приведены ориентиро- 

      вочно и включают   в себя стоимость пересылки по почте (но без учета 

      авиатарифа).  Книги будут   высланы   нашей  службой    «Книга-почтой» 

      в течение двух недель после получения заказа или выхода книги из печати. 

Щ      Оплата  наличными   при курьерской доставке   (для жителе й   Москвы 

      и   Санкт-Петербурга) . Курьер  доставит   заказ   по указанному   адресу 

      в удобное для вас время в течение трех дней. 



ПРИ   ОФОРМЛЕНИ И ЗАКАЗА   УКАЖИТЕ : 



           •   фамилию,  имя, отчество, телефон,  факс, e-mail; 

           •   почтовый   индекс,  регион,  район,  населенный   пункт, 

              улицу,  дом, корпус, квартиру; 

           •   название книги, автора, код, количество  заказываемых 

              экземпляров. 



                                                                         ПОМ 

Вы можете заказать бесплатный 

журнал «Клуб Профессионал» 

                                                           www.         COM 


----------------------- Page 206-----------------------

                                             ©                       СПЕЦИАЛИСТАМ 

                                                                КНИЖНОГО  БИЗНЕСА! 

           WWW.PITER.COM 



       ПРЕДСТАВИТЕЛЬСТВА  ИЗДАТЕЛЬСКОГО ДОМА «ПИТЕР» 

      предлагают эксклюзивный  ассортимент компьютерной, медицинской, 

             психологической, экономической и популярной  литературы 



                                           РОССИЯ 

 Москва      м. «Павелецкая», 1 -й Кожевнический переулок, д. 10; тел./факс (095) 234-38-15, 

                        255-70-67, 255-70-68; e-mail:  sales@piter.msk.ru 



            Санкт-Петербург         м. «Выборгская»,  Б. Сампсониевский пр., д. 29а; 

                тел./факс (812) 703-73-73, 703-73-72; e-mail:  sales@piter.com 



             Воронеж       ул. 25 января, д. 4; тел./факс (0732) 39-43-62, 39-61 -70; 

                                    e-mail:  pitervrn@comch.ru 



Екатеринбург        ул. 8 Марта, д. 2676, офис 203, 205; тел./факс (343) 225-39-94, 225-40-20; 

                                    e-mail:  piter-ural@isnet.ru 



               Нижний Новгород          ул. Совхозная, д.  13; тел. (8312) 41 -27-31; 

                                  e-mail:  piter@infonet.nnov.ru 



                Новосибирск         ул. Немировича-Данченко, д.  104, офис 502; 

                тел./факс (383) 354-13-09, 211-27-18; e-mail:  piter-sib@risp.ru 



         Ростов-на-Дону         ул. Ульяновская, д. 26; тел. (863) 269-91-22, 269-91 -30; 

                                      e-mail: jupiter@rost.ru 



      Самара       ул. Молодогвардейская, д. 33, литер А2, ком. 225; тел. (846) 77-89-79; 

                                    e-mail:   pitvolga@samtel.ru 



                                           УКРАИНА 

  Харьков       ул. Суздальские ряды, д. 12, офис 10-11 ; тел./факс (10-38-057) 712-27-05, 

                  751-10-02, (0572) 58-41-45; e-mail:  piter@kharkov.piter.com 



 Киев     пр. Московский, д. 6, кор. 1, офис 33; тел./факс (10-38-044) 490-35-68, 490-35-69; 

                                e-mail:  office@piter-press.kiev.ua 



                                          БЕЛАРУСЬ 

          Минск       ул. Бобруйская, д. 21 , офис 3; тел./факс (10-375-17) 226-19-53; 

                                  e-mail: office@minsk.piter.com 



    Ищем зарубежных партнеров или посредников, имеющих выход на зарубежный рынок. 

    Телефон для связи: (812)      703-73-73 . 

     E-mail:  grigorjan@piter.com 



     Издательский дом «Питер» приглашает к сотрудничеству авторов. 

    Обращайтесь по телефонам: Санкт-Петербург — (812)               703-73-72, 

     Москва -   (095) 974-34-50 . 



    Заказ книг для вузов и библиотек: (812) 703-73-73. 

            Специальное  предложение - e-mail:  kozin@piter.com 


----------------------- Page 207-----------------------

   пзпАтшаьскпй                               дом 

                                                                        УВАЖАЕМЫЕ   ГОСПОДА! 

                                                             КНИГИ ИЗДАТЕЛЬСКОГО ДОМА «ПИТЕР» 

                                         .COM                         ВЫ  МОЖЕТЕ ПРИОБРЕСТИ 

                                                                         ОПТОМ И В РОЗНИЦУ 

                                                             У НАШИХ РЕГИОНАЛЬНЫХ                  ПАРТНЕРОВ. 



 Башкортостан 

Уфа, «Азия», ул. Гоголя, д. 36, офис   5, 

тел./факс (3472) 50-39-00,51-85-44. 

                                                            Красноярск, «Книжный мир», 

 E-mail: asiaufa@ufanet.ru 

                                                            тел./факс (3912) 27-39-71. 

                                                            E-mail: book-world@public.krasnet.ru 

Дальний Восток 

 Владивосток, «Приморский торговый дом книги»               Нижневартовск, «Дом книги», 

тел./факс (4232) 23-82-12. 

                                                            тел. (3466) 23-27-14, факс 23-59-50. 

 E-mail: bookbase@mail.primorye.ru 

                                                            E-mail: book@nvartovsk.wsnet.ru 



Хабаровск, «Мире»,                                          Новосибирск, «Топ-книга», 

тел. (4212) 30-54-47, факс 22-73-30.                        тел. (3832) 36-10-26, факс 36-10-27. 

E-mail: salebook@bookmirs.khv.ru                            E-mail: office@top-kniga.ru 



                                                            http://www.top-kniga.ru 

Хабаровск, «Книжный мир», 

тел. (4212) 32-85-51, факс 32-82-50. 

                                                            Тюмень, «Друг», 

E-mail: postmaster@worldbooks.kht.ru 

                                                            тел./факс  (3452) 21-34-82. 

                                                            E-mail: drug@tyumen.ru 

 Европейские регионы России 

Архангельск, «Дом книги»,                                   Тюмень, «Фолиант», 

тел. (8182) 65-41-34, факс 65-41-34.                        тел. (3452) 27-36-06, факс 27-36-11. 

 E-mail: book@atnet.ru                                      E-mail: foliant@tyumen.ru 



Калининград, «Вестер»,                                      Челябинск, ТД «Эврика», ул. Барбюса, д.  61 , 

тел./факс  (0112) 21-56-28,21-62-07.                        тел./факс (3512) 52-49-23. 

E-mail:  nshibkova@vester.ru                                E-mail:evrika@chel.sumet.ru 

http://www.vester.ru 



                                                            Татарстан 

Северный Кавказ                                             Казань, «Таис», 

Ессентуки, «Россы», ул. Октябрьская, 424,                   тел. (8432) 72-34-55, факс 72-27-82. 

тел./факс (87934) 6-93-09.                                  E-mail: tais@bancorp.ru 

E-mail:  rossy@kmw.ru 



                                                            Урал 

Сибирь                                                      Екатеринбург, магазин № 14, 

Иркутск,  «ПродаЛитЪ»,                                      ул. Челюскинцев, д . 23, 

тел. (3952) 59-13-70, факс 51-30-70.                        тел./факс  (3432) 53-24-90. 

E-mail: prodalit@irk.ru                                     E-mail: gvardia@mail.ur.ru 

http://www.prodalit.irk.ru 



                                                            Екатеринбург, «Валео-книга», 

Иркутск, «Антей-книга»,                                     ул. Ключевская, д. 5, 

тел./факс (3952) 33-42-47.                                  тел./факс (3432) 42-56-00. 

E-mail: antey@irk.ru                                        E-mail: valeo@etel.ru