Java 2 Micro Edition (J2ME)

         

Acпекты интернационализации


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

Работа с сообщениями - представление всего видимого текста (текст сообщения, текст ошибки, заголовки компонентов пользовательского интерфейса, приглашения и так далее) на языке, соответствующем контексту региональной настройки исполнения.

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

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

Политика строкового сравнения - использование соответствующей политики строкового сравнения на основе языка региональной настройки.

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

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

Работа с сообщениями. Работа с сообщениями - это представление всех текстовых данных пользователю на языке, соответствующем контексту региональной настройки выполнения приложения. Это наиболее заметная область интернационализации. Работа с сообщениями включает выполнение следующих этапов:


определение среды региональной настройки устройства;

загрузка локализованных ресурсов приложения;

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

отображение локализованных ресурсов.

Работа с сообщениями - это область, которая лучше всего подчеркивает близкое родство между интернационализацией и локализацией. Чтобы сделать интернационализированную реализацию используемой, приложение должно быть локализовано. Для каждой поддерживаемой региональной настройки процесс локализации должен создавать набор переведенных строк сообщений, к которому приложение может получить доступ во время работы.

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

Различные языки определяют различные правила сортировки. Элемент строковой сортировки должен использовать механизм, который понимает правила сортировки по языковому контексту строк. На практике, это включает понимание подробностей базовой символьной кодировки.

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

Форматирование даты, времени, числовых и денежных значений. В различных ре гионах используют различные форматы написания дат, времени и чисел. Например, в Европе люди пишут даты и числа не так, как жители Соединенных Штатов. Француз ский пользователь пишет дату, время и числовые величины с помощью следующих форм:

25 decembre 2002

2002/12/25



25/12/2002

08.30

14.45

20.000,45 (двадцать тысяч и сорок пять сотых)



В Соединенных Штатах, однако, те же самые значения обычно пишутся следующим образом:

December 25, 2002

12/25/2002

8:30 am

2:45 pm

20,000.45 (двадцать тысяч и сорок пять сотых)

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

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


Apxитeктypa приложения


Построение архитектуры приложения - это искусство и наука. По существу, имеется много определений архитектуры приложения, каждое из них ожесточенно отстаивается своими сторонниками. Разумным определением кажется то, что предоставлено институтом разработки программного обеспечения Университета Carnegie-Mellon University (http://www.sei.cmu.edu):

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

Буч, Рамбаут и Якобсен приводят классическое определение архитектуры в своей книге «Руководство пользователя по языку моделирования UML» («UML Modeling Language User Guidz»), которое приведено ниже:

Архитектура является набором важных решений об организации системы программного обеспечения, выборе структурных элементов и их взаимосвязей, из которых составляется система, наряду с их поведением, указываемым во взаимодействиях этих элементов, составе этих структурных и поведенческих элементов в прогрессивно увеличивающихся подсистемах, и архитектурном стиле, который управляет этой организацией - этими элементами и их интерфейсами, их взаимодействиями и их структурой.

Архитектура создает артефакты, которые описывают систему. Важным аспектом архитектуры является то, что она включает использование процессов, которые выражаются в создании этих артефактов. Архитектурная методология (architectural methodology (AM)) - это набор действий, которые управляют использованием набора процессов. Сообщество разработчиков программного обеспечения определяет много методологий, каждая из которых отражает свою собственную философию, процессы и артефакты. Одна из таких архитектурных методологий - SunTone AM, которая была разработана в корпорации «§un Microsystems» и является дополнением к Rational Unified Process (RUP).


В этом разделе, конечно, не представлена полная трактовка всего объема того, чем является архитектура, любая архитектурная методология, такая, как SunTone AM, как осуществляется ее построение или происходит архитектурный процесс. Описание деятельности по созданию архитектуры, ее артефактов, процессов и связанных с ней методологий лежит далеко за пределами темы или цели данной книги.

Скорее цель данного раздела заключается в том, чтобы познакомить вас с понятиями, которые окружают архитектуру приложений, и объяснить важность выполнения построения архитектуры как первого этапа в создании коммерчески качественных приложений. Теперь, когда вы знакомы с набором определений, необходимых для разработки приложений J2ME, нам необходимо более полно осветить вопросы, связанные с созданием надежных, коммерчески качественных приложений, соответствующих требованиям реальной среды беспроводной связи. Внимание к архитектуре, бесспорно, повысит способность разработчика приложений J2ME проектировать надежные приложения, которые отвечают требованиям сред беспроводной связи.

Приложения MIDP используют службы, формирующие портал беспроводного Интернета. Хотя, возможно, разработчики приложений на MIDP не принимают участие в проектировании и создании служб портала, важно то, что они представляют себе архитектурную конструкцию платформы беспроводного Интернета для того, чтобы определять возможности, характеристики и количество этих служб. Разработчики MIDP приложений должны рассматривать приложения MIDP как одну из частей системы, которая состоит из мобильного устройства и всех остальных компонентов беспроводного Интернета.

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

Архитектура - это комплексная тема и о ней написано множество отдельных книг. Цель данного раздела по архитектуре заключается лишь в знакомстве с понятием архитектуры, если вы с ним еще не знакомы. Для тех из вас, кто уже знаком с архитектурой, цель - побудить вас взглянуть на среду беспроводного Интернета с точки зрения архитектуры и мотивировать вас к размышлению об архитектурных проблемах для создания надежных коммерчески выгодных MIDP приложений для беспроводного Интернета. Я призываю вас оценить важность выполнения построения архитектуры и воспитать в себе привычку выполнять архитектурное построение в качестве первого этапа при разработке любого приложения.


Apxитeктypныe решения беспроводного Интернета


Полный объем работ по созданию архитектуры должен учитывать каждый аспект системы. С точки зрения разработчика приложений на J2ME системный контекст является не просто платформой J2ME, но также целой средой беспроводного Интернета, включая интернет-портал и среды беспроводных сетей.

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

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

Проблемы, на которые мы обращаем здесь особое внимание, сконцентрированы на влиянии, которое характеристики сред беспроводного Интернета оказывают на системные качества архитектуры. Хотя небезопасно уделять первостепенное внимание системным качествам без определения конкретных требований, вероятно, можно с уверенностью сказать, что, в общем, производительность, расширяемость, доступность и безопасность являются важнейшими из понятий разработки, если не больше - из всех системных качеств. Эти системные качества, в частности, подчеркивают некоторые из различий между средами беспроводного и проводного Интернета.


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

Двумя основными категориями беспроводных сетей являются сети с коммутацией каналов и сети с коммутацией пакетов. Сети с коммутацией каналов тратят больше времени на установление соединения, чем сети с коммутацией пакетов. Более длительное время установления соединения вызывает задержку начала обмена данными, что влияет на время ожидания и пропускную способность. Теоретически приложения MIDP должны, вероятно, проектироваться с учетом запроса большего объема данных на каждое соединение и ограничения количества соединений, устанавливаемых с удаленными серверами, особенно в сетях с коммутацией каналов. Действительные же измерения производительности, доступные на время написания этой книги, однако, показывали, что относительно новые сети, основанные на коммутации пакетов, еще пока недостаточно хорошо налажены, чтобы снизить время ожидания и увеличить пропускную способность настолько, насколько ожидалось первоначально. Поэтому неплохим решением может стать ограничение общего количества запросов соединения.

Повышение производительности может быть также достигнуто посредством использования дейтаграмм для определенных сетевых коммуникаций. Если приложения могут быть к нему приспособлены, использование UDP вместо HTTP или сокетов (то есть, даже если реализация MIDP поддерживает сокеты) может привести к резкому повышению сетевой производительности, поскольку реализации UDP не создают соединений на транспортном уровне.

Другой проблемой является стоимость соединений передачи данных в беспроводных сетях. В сетях с коммутацией каналов оплачивается время соединения. В пакетных сетях оплачиваются отправленные и принятые байты. Тип сети, на основе которого работает ваше приложение MIDP, и разработка, которую вы выбрали для осуществления коммуникаций вашим приложением, могут повлиять на стоимость для конечного пользователя. Кроме того, тип сети может повлиять на расширяемость, нагрузку и пропускную способность сети.



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

Извлечение данных является другой проблемой, которая влияет на производительность. Вы не можете ничего сделать с производительностью уровня данных, лежащим глубоко в архитектуре портала. Однако вы можете уменьшить влияние частых запросов данных. Получение большего объема данных при каждом запросе и кэширование их локально на устройстве либо в памяти, либо в RMS может повысить производительность. И, как мы уже говорили, эта стратегия может также повысить производительность в беспроводной сети.

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

но иллюстрирует выгодность создания прототипов приложения до загрузки полной реализации. Вы можете обнаружить, что при кэшировании RMS-записей в памяти у вас повышается производительность по сравнению с ситуацией, когда RMS получает доступ к каждому прочтению или записи. В некоторых реализациях MIDP производительность повышается при удалении всего хранилища записей, его повторном создании, а затем внесении всех записей за один раз вместо создания отдельных записей.

Расширяемость близко связана с производительностью. Вы должны учитывать, поддерживает ли разработка, поддерживающая высокую производительность, также массовую расширяемость. Приложения MIDP должны быть прототипированы и протестированы для большого масштаба, поскольку приложения беспроводного Интернета могут подвергаться одновременному доступу большого количества пользователей. В зависимости от вашего приложения и среды Интернета, к которой ваше приложение получает доступ, иногда возможно получить доступ к децентрализованным службам Интернета, таким образом уменьшая влияние узких мест, появление которых вызвано доступом отдельного сервера.



Поддержка определяющих местонахождение служб является другой областью, которая может повлиять на разработку приложений для телефонов. Как вы уже узнали ранее, среды беспроводного Интернета могут поддерживать один из трех типов технологий местоопределения, на основе которых строятся службы, определяющие местонахождение. Системы, базирующиеся на GPS, еще пока не очень доступны в реальных сетях. Во время написания данной книги службы, базирующиеся на сети, были превалирующими. Полуавтоматические системы GPS все еще находятся на стадии эксперимента, но подают надежды. На разработку вашего приложения влияет в большей степени доступная системная поддержка. Однако независимо от технологии вы можете выбирать альтернативные варианты разработки, повышающие производительность и расширяемость. Суть в том, что вы должны оценивать всю систему - а не только программное обеспечение, неотъемлемое от устройства - в соответствии с критериями, определяемыми системными качествами.

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

Другой причиной реализации этих схем является ограниченность адресного пространства сети. Беспроводные сети часто преобразуют IP-адреса в собственные адресные схемы, чтобы получить возможность работать с большим количеством телефонных трубок. Для того чтобы поддерживать обмен данными между равноправными узлами - телефонными трубками, беспроводной сети приходится предоставлять схему пересылки телефонных адресов, основываясь на некоторой форме внутренней адресации. Модель централизованной службы может повлиять на производительность и расширяемость.

Это некоторые из причин, почему беспроводные сети имеют более ограниченную среду сетевой передачи данных, чем среды проводных сетевых комплексов. Разработчики MIDP должны учитывать ограничения сети при разработке приложений. С принятием IPv6 будет достаточно адресов для того, чтобы присвоить каждой телефонной трубке статический IP-адрес. Тем не менее, безопасность, производительность и расширяемость останутся важными вопросами.


Аудитория


Эта книга предназначена для разработчиков на Java. Она подходит для профессиональных разработчиков программного обеспечения, а также для продвинутых студентов, аспирантов. Если быть более точным, создатели этой книга ожидают, что читатель хорошо знает язык программирования Java, но не приобрел опыта в программировании с отдельными API сверх основных API Java. Несмотря на возможное отсутствие опыта в какой-либо отдельной области программирования на Java, кроме хорошего знания языка, будет полезно, если читатели имеют, по крайней мере, представление о понятиях и профессиональных выражениях, которые окружают различные технологии Java, как, например, термины, которые связаны с виртуальными машинами, сборкой мусора, загрузкой классов, верификацией классов, интерфейсами родного кода, синхронной компиляцией, AWT, RMI, JDK, JRE и так далее.

Я также предполагаю, однако, что читатель имеет порядочный опыт в объектно-ориентированном программировании. По этой причине я не объясняю объектно-ориентированных понятий, когда они появляются в процессе описания J2ME API, классов, идиом программирования и тому подобного.

Конечно, чем больше у читателя опыта, тем лучше. Во всей книге появляются различные ссылки на AWT и Swing. Значительная часть MIDP-программирования включает манипулирование компонентами интерфейса пользователя. Читатель, знакомый с понятиями инструментария AWT или Swing, может быстро понять сущность организации и понятий модели программирования UI в MIDP. Несмотря на преимущества знания AWT и Swing, вам не нужно иметь опыта в разработке UI Java.



Аутентификация пользователей


Аутентификация пользователей - это просто процесс подтверждения через агента пользователя того, что пользователь является тем, кем себя заявляет. Аутентификация пользователя требует, чтобы агент пользователя передавал информацию о пользователе диспетчеру инициализации. Обычно агент пользователя передает эту информацию диспетчеру инициализации в форме HTTP-заголовков HTTP-запроса браузера устройства. Запрос скорее всего также использует поле заголовка HTTP-запроса агента пользователя для идентификации агента пользователя.

Существуют некоторые беспроводные системы, которые не хранят много пользовательской информации на мобильных устройствах. В таких случаях сервер должен связывать пользовательскую информацию с информацией устройства - MSN устройства, например. Как описывалось ранее, системы инициализации могут взаимодействовать с другими системами транспортировщика, такими, как серверы аутентификации, серверами облегченного протокола службы каталогов (Lightweight Directory Access Protocol (LDAP)) или серверами шлюза беспроводного Интернета (WIG) для получения остальной необходимой пользовательской информации.



Базовое геометрическое рисование


Класс Graphics предоставляет операции по рисованию и заливке следующих типов геометрических фигур:

линии;

прямоугольники;

дуги;

текстовые символы.

Для всех операций по рисованию геометрических фигур класс Graphics использует графическую ручку, которая рисует линии шириной в один пиксель. Графическая ручка рисует слева направо и сверху вниз со своего координатного местоположения, как показано на рисунке 6.3. Взглянув на несколько примеров, вы лучше поймете принципы ее действия.

Линии. На рисунке 6.4 показаны линии, нарисованные в Canvas.

Рисунок 6.4. Вы можете рисовать линии в Canvas. Вы можете сделать линии толще одного пикселя, нарисовав прилегающие линии ручкой одного цвета

В листинге 6.3 показан исходный код, который создает рисунок 6.4, но я опустил код MID-лета, который отображает это. Вы можете найти полный исходный код по адресу http://www.phptr.com/. Для остальных примеров, приведенных в этой главе, предполагается, что отображаемые классы, которые вы видите здесь, созданы и отображаются MID-летом в стиле, сходном с примерами, которые вы видели в предыдущих главах. Исходя из этого, я покажу вам только новый код.

Листинг 6.3. Демонстрационная программа описывает метод paint (), который гарантирует, что некоторые визуальные представления появляются на дисплее устройства

import javax.microedition.lcdui.Canvas;

import javax.microedition.lcdui.Command;

import javax.microedition.lcdui.CommandListener;

import javax.microedition.lcdui.Display;

import javax.microedition.lcdui.Displayable;

import javax.microedition.lcdui.Graphics;

import javax.raicroedition.lcdui.Command;

/*'

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

которые могут быть нарисованы с помощью класса Graphics.

@смотри javax.microedition.Icdui.Graphics

*/

public class LineDemo extends Canvas .

implements CommandListener

}

// Константа, которая представляет белый цвет.

private static final int WHITE = OxFF « 16 | OxFF « 8 I OxFF;

private Command back = new Command("Back", Command.BACK, 1);


private GraphicsDemo gDemo = GraphicsDemo.getlnstance(};

private Display display = Display.getDisplay(gDemo);

/**

Конструктор No-arg.

*/

public LineDemo()

{

super ();

addCommand(back);

setCommandListener(this) ;

display.setCurrent(this);

}

/*'*

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

что было изображено в Canvas перед этим. "/

protected void paintdipRect (Graphics g)

}

int clipX = g.getClipX ();

int clipY = g.getClipY() ;

int clipH = g.getdipHeight () ;

int clipW = g.getClipWidth();

int color = g.getColor ();

g.setColor(WHITE);

g.fillRect(clipX, clipY, clipW, clipH);

g.setColor (color);

}

/ **

Отображает внешний вид этого подкласса Canvas.

* /

public void paint (Graphics g)

{

paintdipRect (g) ;

int width = getWidth();

int height = getHeight ();

g.drawLine (20, 10, width - 20, height - 34);

g.drawLine(20, 11, width - 20, height - 33);

g.drawLine(20, 12, width - 20, height - 32);

g.drawLine(20, 13, width - 20, height - 31);

g.drawLine(20, 14, width - 20, height - 30);

g.setStrokeStyle(Graphics.DOTTED);

g.drawLine(20, 24, width - 20, height - 20);

g.drawLine(20, 25, width - 20, height - 19);

g.drawLine(20, 26, width - 20, height - 18);

g. setStrokeStyle (Graphics.SOLID);

g.drawLine(20, 36, width - 20, height - 8);

}

public void commandAction(Command c, Displayable d)

{

if (c == back)

{

GraphicsDemo.getlnstanceO.display() ;

}

}

}

Метод paint (Graphics g) является основным в этом примере. Поскольку Canvas описывает этот метод как абстрактный, подклассы должны предоставлять конкретное описание. На экране, созданном программой в листинге 6.2, ничего не появляется, поскольку ее метод paint (Graphics g) не описывает никаких операций по рисованию.

Ваша программа должна выполнять все свои операции по рисованию в методе paint (Graphics g) на объекте Graphics, переданном ей. Вы запускаете стандартные операции по рисованию, предназначенные для класса Graphics, в этом экземпляре, который передан вашему Canvas.



Чтобы нарисовать линию, вы должны указать координаты (х, у) начальной и конечной точек. Координаты (х, у) определяются относительно точки (0, 0), которая, во время создания графического контекста, представляет пиксель, лежащий в верхнем левом углу дисплея, как показано на рисунке 6.3. Координата х определяет горизонтальное расстояние направо от колонки 0 (левый край дисплея), а координата у определяет вертикальное расстояние от строки 0, которая является верхним краем дисплея.

Ширина линий составляет один пиксель. Чтобы нарисовать более толстую линию, вы должны рисовать прилегающие линии, как демонстрируется в листинге 6.3. Три линии, показанные на рисунке 6.4, созданные с помощью листинга 6.3, имеют ширину в пять, три и один пиксель соответственно.

Кроме того, средняя линия отображена штрихпунктиром. Вы можете установить стиль штриховки для любого рисунка с помощью метода setStrokeStyle (), как демонстрируется в программе. Конкретное отображение линий, которые используют стиль штрихования Graphics.DOTTED, зависит от реализации.

Прямоугольники. Вы можете рисовать два вида прямоугольников: обычный и закругленный. На рисунке 6.5 показаны несколько прилегающих прямоугольников.



Рисунок 6.5. Прямоугольники, как и все геометрические фигуры, могут быть изображены различными цветами с помощью указания цвета графической ручки. Средний прямоугольник красный, хотя он и кажется опенком серого на рисунке

В листинге 6.4 показан исходный код paint (Graphics g) для этого примера.

Листинг 6.4. Демонстрационная программа RectangleDemo демонстрирует графические вызовы рисования прямоугольников. Обратите внимание, что это вызов на заполнение прямоугольников

import javax.microedition.lcdui.Canvas;

import javax.microedition.Icdui.Command;

import javax.microedition.Icdui.CommandListener;

import javax.microedition.lcdui.Display;

import javax.microedition.Icdui.Displayable;

import javax.microedition.Icdui.Graphics;

import javax.microedition.Icdui.Command;

/**

Рисует прямоугольники на Canvas с помощью графических методов



в классе javax.microedition.Icdui.Graphics.

@смотри javax.microedition.Icdui.Graphics

*/

public class RectangleDemo extends Canvas

implements CommandListener

{

// Константа, представляющая белый цвет.

private static final int WHITE = OxFF « 16 | OxFF « 8 I OxFF;

private Command back = new Command("Back", Command.BACK, 1);

private Display display =

Display.getDisplay(GraphicsDemo.get!nstance()) ;

/**

Конструктор No-arg. Вызывает конструктор no-arg Canvas.

*/

public RectangleDemo()

}

super () ;

addCommand(back); setCommandListener(this);

display.setCurrent (this) ;

}

/**

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

все, что было отображено на Canvas перед этим.

*/

protected void paintClipRect(Graphics g)

{

int clipX = g.getClipX () ;

int clipY = g.getClipY();

int clipH = g.getClipHeight();

int clipW = g.getClipWidth ();

int color = g.getColor();

g.setColor (WHITE);

g.fillRect(clipX, clipY, clipW, clipH);

g.setColor (color);

}

/**

Отображает внешний вид этого подкласса Canvas.

*/

public void paint(Graphics g)

{

paintClipRect(g);

int width = getWidthO; int height = getHeightf);

int xO = 5;

int yO = 5;

int barW = 10;

int initHeight = height - 10;

int deltaH = 10;

g.drawRect(xO, yO, barW, initHeight);

g.fillRect(xO + barW, yO + deltaH, barW, initHeight - deltaH + 1);

g.drawRect(xO + barW " 2, yO + deltaH * 2,

barW, initHeight - deltaH * 2);

g.setColor (255, 00, 00); g.fillRect(xO + bar» * 3, yO + deltaH * 3,

barW, initHeight - deltaH * 3 + 1) ; g. setColor (0," 0, 0);

g.drawRect(xO + barW * 4, yO + deltaH * 4,

barW, initHeight - deltaH * 4);

g.fillRect(xO + barW * 5, yO + deltaH * 5,

barW, initHeight - deltaH * 5 + 1);

g.drawRect(xO + barW * 6, yO + deltaH * 6,

barW, initHeight - deltaH * 6); g.fillRect(xO + barW * 1, yO + deltaH * 1,

barW, initHeight - deltaH * 7 + 1);

}

public void commandAction(Command c, Displayable d)

{

if (c == back)

{

GraphicsDemo.getlnstanceO.display!) ;



}

}

}

Дуги. Класс Graphics также поддерживает рисование дуг. Чтобы нарисовать дугу, вы должны указать шесть параметров. Эти параметры включают четыре размера, которые определяют ограничивающий дугу прямоугольник, ее начальный угол и ее конечный угол. Ограничивающий прямоугольник определяется теми же четырьмя параметрами, которые требуются для прямоугольников.

Процедура рисования отслеживает дугу вдоль ее пути от начального угла к конечному углу в направлении против часовой стрелки. Угол в 0 градусов располагается вдоль положительной оси X координатной плоскости. На рисунке 6.6 показаны две дуги, нарисованные методом paint (Graphics g) в листинге 6.5.



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

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

import javax.microedition.lcdui.*;

/**

Демонстрирует рисование дуг с помощью класса Graphics.

Усмотри javax.microedition.lcdui.Graphics

*/

public class ArcDemo extends Canvas

implements ComraandListener

{

public void paint(Graphics g)

{

paintClipRect(g);

}

int width = getWidth();

int height = getHeight ();

g.drawArc(5, 5, 80, 40, 90, 300);

g.fillArc(5, 60, 80, 40, 0, 250);

}

. . . .

}

Обратите внимание, что вторая дуга заполнена и что она была создана с помощью метода f illArc () вместо метода drawArc ().

Текст. Класс Graphics также поддерживает «рисование» текстовых символов в Canvas. Три метода, перечисленные в таблице 6.4, являются методами класса Canvas, поддерживающими размещение текста в Canvas.

Таблица 6.4. Методы класса Canvas, которые поддерживают изображение текста на Canvas

Название метода отображения текста в Canvas Описание
public void drawString(String str, int x, int y, int anchor) Рисует символы, которые формируют строковую переменную с указанной точкой привязки в позиции, определяемой координатами (х, у]
public void drawSubstring(String str, int offset, int len, int x, int y, int anchor) Рисует символы, которые формируют переменную подстроки, определяемую начальной точкой и сдвигом, с указанной точкой привязки в позиции, определяемой координатами (х, у)
public void drawChar (Char char, int x, int y, int anchor) Рисует символ с указанной точкой привязки в позиции, определяемой координатами (х, у)
<


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

Параметры (х, у) в только что показанных методах представляют расположение ограничивающего прямоугольника. Параметр привязки определяет точку привязки ограничивающего прямоугольника. Точка привязки определяет, которая из шести возможных точек по периметру текста ограничивающего прямоугольника должна быть размещена в позицию (х, у).

На рисунке 6.7 показаны шесть точек привязки для регулирования расположения прямоугольника, ограничивающего текстовую строку. Значение точки привязки на самом деле является выбором нагрузки на точку ограничивающего прямоугольника. Два атрибута составляют нагрузку точки привязки: горизонтальная и вертикальная политики нагрузки. В таблице 6.5 описаны представляющие их константы класса Graphics. Они описывают public static final int.



Рисунок 6.7. Текст «рисуется» в границах воображаемого ограничивающего прямоугольника, который вычисляется стандартными текстовыми процедурами

Некоторый отображаемый текст

Таблица 6.5. Графические константы для определения политики привязки-нагрузки

Константа привязки Описание
static int LEFT Размещает левый край у координаты х
static int HCENTER Размещает горизонтальный центр у координаты х
static int RIGHT Размещает правый край у координаты х
static int TOP Размещает верх у координаты у
static int BASELINE Размещает нижнюю строку текста у координаты у
static int BOTTOM Размещает низ ограничивающего прямоугольника у координаты у
static int VCENTER Только для изображений, размещает вертикальный центр изображения у координаты у
Класс Graphics описывает эти константы для текущих значений горизонтальной нагрузки, а также определяет значения для текущих значений вертикальной нагрузки.



На рисунке 6. 8 показан некоторый текст, отображаемый на Canvas, а в листинге 6.6 показан метод paint (Graphics g) исходного кода, который его отображает.



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

Листинг 6.6. Чтобы создать текст, укажите точку привязки и нагрузку точки привязки. Вы также можете указать шрифт текста, который будет отображен

import javax.microedition.lcdui.Canvas;

import javax.microedition.lcdui.Command;

import javax.rnicroedition.lcdui.CornmandListener;

import javax.microedition.lcdui.Display;

import javax.microedition.lcdui.Displayable;

import javax.microedition.lcdui.Font;

import javax.microedition.lcdui.Graphics;

/**

Отображает некоторый текст, «нарисованный» в Canvas.

Демонстрирует использование процедур рисования текста в Graphics.

Усмотри javax.microedition.lcdui.Graphics

*/

public class TextDemo extends Canvas

implements CommandListener

}

public void paint(Graphics g)

}

paintClipRect(g) ;

int width = getWidth (); int height = "getHeight () ;

g.setFont(Font.getDefault Font());

g.drawStriny("Default", 5, 30, Graphics.LEFT I Graphics.BOTTOM);

g. setFont (Font.get Font (Font.FACE_SYSTEM, Font.STYLE_PLAIN,

Font.SIZE_LARGE)) ; g.drawstring("Large", 5, 53, Graphics.LEFT | Graphics.BOTTOM);

g.set Font(Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_ITALIC,

Font.SIZE_MEDIUM));

g.drawString("Medium", 5, 71, Graphics.LEFT I Graphics.BOTTOM);

g.set Font(Font.get Font(Font.FACE_PROPORTIONAL, Font.STYLE_UNDERLINED,

Font.SIZE_SMALL));

g.drawString("Small", 5, 90, Graphics.LEFT I Graphics.BOTTOM);

g.setFont(Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_BOLD,

Font.SIZE_MEDIUM));

g.drawString ("V", width - 10, 20, Graphics.RIGHT I Graphics.BOTTOM)

g.drawStringC'E", width - 10, 32, Graphics.RIGHT I Graphics.BOTTOM)

g.drawString("R", width - 10, 44, Graphics.RIGHT I Graphics.BOTTOM)



g.drawStringC'T", width - 10, 56, Graphics.RIGHT I Graphics.BOTTOM)

g.drawString("I", width - 10, 68, Graphics.RIGHT I Graphics.BOTTOM)

g.drawString ("C", width - 10, 80, Graphics.RIGHT | Graphics.BOTTOM)

g.drawStringC'A", width - 10, 92, Graphics.RIGHT I Graphics.BOTTOM) g.drawString ("L", width - 10, 104, Graphics.RIGHT I Graphics.BOTTOM);

g.drawChar('B', width - 25, 20, Graphics.RIGHT | Graphics.BOTTOM);

g.drawChar('0', width - 25, 32, Graphics.RIGHT I Graphics.BOTTOM) ;:

g.drawChar('L', width - 25, 44, Graphics.RIGHT I Graphics.BOTTOM) ;:

g.drawChar ( 'D', width - 25, 56, Graphics.RIGHT I Graphics.BOTTOM);

}

. . .

}

Эта демонстрационная программа выбирает, где разместить текстовые строки «Default», «Large», «Medium» и «Small», располагая основные линии ограничивающих прямоугольников. Текст также выравнивается по левому краю. Обратите внимание, что логический OR горизонтальной и вертикальной политик привязки (LEFT | BOTTOM) указывают позицию привязки.

Две строки «BOLD» и «VERTICAL» нарисованы вертикально простым размещением отдельных символов с помощью метода drawChar(). Они определяются сдвигом от правого края дисплея. С помощью политики привязки RIGHT код вычисляет положение правого края ограничивающего прямоугольника, вычитая некоторое количество пикселей из координаты крайнего справа пикселя дисплея.

API Graphics также определяет другую константу, VCENTER, которая действительна только для указания вертикальной политики привязки при размещении изображений. Она недействительна для текста. VCENTER обуславливает то, что вертикальный центр изображения должен быть размещен в координатной точке (х, у). Вы узнаете о работе с изображениями далее в этой главе.

Шрифты. Вы можете выбрать шрифт для любого текста, который вы изобразили в Canvas, как демонстрируется в листинге 6.6. Вы выбираете шрифт, указывая три атрибута шрифта: гарнитура, стиль и размер. Класс javax.microedition.lcdui.Font определяет для каждой из трех категорий константы, показанные в таблице 6.6.



Таблица 6.6. Графические константы, которые определяют атрибуты шрифтов

Константа атрибута Описание
static int FACE MONOSPACE Значение атрибута гарнитуры
static int FACE_PROPORTIONAL Значение атрибута гарнитуры
static int FACE SYSTEM Значение атрибута гарнитуры
static int STYLE BOLD Значение атрибута стиля
static int STYLE ITALIC Значение атрибута стиля
static int STYLE PLAIN Значение атрибута стиля
static int STYLE UNDERLINED Значение атрибута стиля
static int SIZE SMALL Значение атрибута размера
static int SIZE MEDIUM Значение атрибута размера
static int SIZE LARGE Значение атрибута размера
Вы должны были заметить, что спецификация MIDP не требует от реализаций предоставления всех этих размеров, стилей и гарнитур. Выводимый шрифт, которым реализация может управлять, будет как можно ближе соответствовать требуемому шрифту.

В отличие от AWT и Swing, вам не придется иметь огромный набор шрифтов и несметное число размеров шрифтов. Более того, поскольку класс Font объявлен final и не имеет конструкторов public, вы не можете организовать его подклассы для определения новых шрифтов. Создатели MIDP решили ограничить число доступных шрифтов с учетом ограничений устройства.

Вам необходимо получить ссылку на текущий объект Font для того, чтобы переслать его в метод Graphics.setFontf). Вы можете получить объект Font, только вызвав один из двух методов static:

Font.getFont(int face, int style, int size)

Font.get Default Font ()

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


Беспроводные приложения


В этом разделе кратко описываются типы приложений и служб приложений, которые обычно доступны на порталах беспроводного Интернета. Мы не будем обсуждать здесь разработку этих служб, потому что они могут включать комплексные архитектуры, которые требуют Web-серверов, серверов приложений и других компонентов системного уровня. Цель данного раздела - дать вступительное теоретическое описание среды беспроводного приложения и побудить разработчиков начать думать о том, как разрабатывать приложения для данной среды.

Обратите внимание, что на рисунке 11.2 не представлен вид, который структурирован настолько, чтобы показать системы внутри сети intranet транспортировщика, которая предоставляет индивидуальные программные службы. Причина этого кроется в том, что системы компонентов, которые беспроводные транспортировщики используют для предоставления обмена сообщениями, персонализации, служб личной информационной системы и так далее, часто являются комплексными собственными программными системами сторонних разработчиков. Интерфейсы и программные интерфейсы приложений для этих систем являются собственными, а описание коммерческих продуктов лежит за пределами темы данной главы или книги.



Блоки прослушивания записей


Приложения имеют способность получать уведомления при добавлении записи, ее удалении или изменении в хранилище записей. Класс RecordStore позволяет вам добавлять и удалять блоки прослушивания записей из определенного хранилища данных с помощью методов, перечисленных в таблице 7.2. Блок прослушивания записей является любым классом, реализующим интерфейс RecordListener, определенный в пакете javax.microedition.rms. Он объявляет три метода, показанных в таблице 7.3.

Таблица 7.2. Методы поддержки блока прослушивания событий RecordStore

Название метода RecordStore Описание
Void addRecordListener (RecordListener listener) Делает указанный объект блоком прослушивания для данного хранилища записей
Void removeRecordListener (RecordListener listener) Удаляет указанный блок прослушивания как блок прослушивания данного хранилища записей

Таблица 7.3. Методы интерфейса RecordListener

Название метода RecordListener Описание
void recordAdded (RecordStore recordStore, int recordld)

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

void recordChanged (RecordStore recordStore, int recordld) Уведомляет блок прослушивания записей о том, что запись с указанным ID была изменена в хранилище записей
void recordDeleted(RecordStore recordStore, int recordld) Уведомляет блок прослушивания записей о том, что запись с указанным ID была удалена из хранилища записей

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



Блоки соединения и соединения


На рисунке 8.1 представлено схематичное изображение этапов, входящих в процесс создания и использования соединения. Эти этапы, которые мы перечислим позже, соотносятся с условным обозначением, показанным на рисунке 8.1.

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

Рисунок 8.1. Производящий соединения блок создает соединения с сетевыми ресурсами, анализируя поле схемы URI и привлекая помощь определенных сетевых классов для создания соответствующего типа транспортного механизма

Приложение запрашивает класс Connector для открытия соединения с сетевым ресурсом.

Фабричный метод Connector.open() анализирует URI и возвращает объект Connection. Полученный объект Connection содержит ссылки на входной и выходной потоки к сетевому ресурсу.

Приложение получает объект InputStream или OutputStream из объекта Connection.

Приложение считывает данные из InputStream или записывает их в OutputStream в процессе своей обработки.

Приложение закрывает Connection при завершении работы

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

анализ сообщения протокола;

анализ полезной нагрузки сообщения - содержимого сообщения.

Например, если клиент устанавливает HTTP-соединение, он должен проанализировать синтаксис и семантику ответного сообщения протокола HTTP, возвращенного сервером. Сообщение HTTP передает некоторого рода содержимое, и клиент должен быть способен проанализировать содержимое соответствующим образом. Если, например, содержимое сообщения является данными HTML, клиент должен соответственно анализировать HTML содержимое. Если приложение не знает формата данных, переданных входящим потоком, оно не сможет правильно интерпретировать либо синтаксис, либо семантику содержимого потока.

Структура общих соединений MIDP определяет иерархию типов соединений, объединяющую природу различных видов потоковых соединений. То есть различные типы представляют различные протоколы, используемые соединениями. При использовании соответствующего типа соединения анализ и управление различными типами содержимого соединений становится проще. Например, HTTP-соединения являются основой сетевых коммуникаций в MIDP. Структура общих соединений определяет тип соединения, чей интерфейс поддерживает создание HTTP-запросов и анализ HTTP-откликов.



Cпиcки


Существует на самом деле два способа извлечения записей из хранилища данных:

Извлечение отдельной записи с помощью ее уникального ID;

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

Чтобы извлечь определенную запись, вы можете использовать следующий метод класса RecordStore:

byte [] getRecord(int recordld)

Этот метод, очевидно, требует, чтобы вы знали уникальный ID записи, которую вы хотите извлечь. К сожалению, это означает, что вам, возможно, придется хранить ID где-нибудь в легкодоступном месте после того, как он будет выдан вам методом addRecord (). Это не всегда удобно или практично при большом количестве записей.

Самый легкий способ найти записи, которые вам нужны, - это использовать списки, которые поддерживаются классом RecordStore. Список весьма удобен при извлечении записей, если вы не знаете ID записей, которые вам нужны. Вы можете создать список записей, хранящихся в хранилище записей, а затем исследовать его, выбрав одну или несколько записей, которые вам нужны.

Класс RecordStore определяет метод

RecordEnumeration

enumerateRecords(RecordFilter filter,

RecordComparator comparator,

boolean keepUpdated)

который выдает список записей в хранилище записей. В листинге 7.2 показан исходный код RecordList.Java. Этот класс создает и отображает список всех записей адресной книги. Обратите внимание, что для того, чтобы извлекать записи, ID записей указывать не нужно.

Листинг 7.2. Списки дают вам возможность получать доступ к записям, не зная их идентификационных номеров (ID)

import javax.microedition.midlet.MIDlet;

import javax.microedition.lcdui.Alert;

import javax.microedition.lcdui.AlertType;

import javax.microedition.lcdui.Command;

import javax.microedition.lcdui.CommandListener;

import javax.microedition.lcdui.Display;

import javax.microedition.lcdui.Displayable;

import javax.microedition.lcdui.List;

import javax.microedition.rms.RecordEnumeration;

import javax.microedition.rms.RecordStore;

import javax.microedition.rms.RecordStoreException;


import java.io.ByteArraylnputStream;

import Java.io.DatalnputStream; import Java.io.lOException;

/**

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

отображающим список записей, находящихся в хранилище записей.

Он использует объект AddressBook, определенный классом MID-лета

для данного приложения MID-лета.

@смотри AddressBook

@смотри AddressBookMain

*/

public class RecordList extends List

implements CommandListener

{

private static Command go =

new command("Go", Command.SCREEN, 1);

private static Command back =

new Command("Back", Command.BACK, 1);

private Display display;

private static RecordList instance;

/**

Конструктор.

@param title название экрана пользовательского интерфейса,

который является List.

*/

public RecordList (String title)

superltitle, List.IMPLICIT);

instance = this;

PersistenceDemo pDemo = PersistenceDemo.getlnstance ();

display = Display .get-Display (pDemo) ;

addCommand(back); setCommandListener (this);

if (buildRecordList() <= 0) setTitle("No records found");

}

/""

Возвращает один экземпляр данного класса.

Вызов этого метода перед созданием объекта возвращает нулевой указатель.

@возвращает экземпляр данного класса.

*/

public static RecordList getlnstance()

}

return instance;

}

void display ()

{

display.setCurrent (this);

{

/**

Создает список записей, хранящихся в хранилище записей. Выдает число найденных записей. Этот метод извлекает все записи из хранилища записей, то есть он не использует фильтров для извлечения записей. Он также не использует компараторов записей, так что не упорядочивает выводимые записи.

<р>Этот метод не сбрасывает исключений, но находит исключения,

которые происходят при доступе к хранилищу записей.

(@возвращает число записей, найденных в хранилище записей,

или 0, если записей не найдено.

*/

int buildRecordList ()

{

AddressBook addressBook =

AddressBookMain.get Instance!).getAddressBook();

RecordStore recordStore = addressBook.getRecordStore();



int numRecords = 0; try

RecordEnuraeration re;

re = recordStore.enumerateRecords(null,

null, false);

if (re.numRecords() > 0)

{

ByteArraylnputStream bais = null;

DatalnputStreara dis = null;

String name = null;

while (re.hasNextElement())

byte [] record = re.nextRecord();

bais = new ByteArraylnputStream(record); dis = new DatalnputStrearn (bais ) ;

String strRec = new String(record);

name = dis . readUTFO ;

appendfname, null ;

numRecords++;

)

)

else

}

Alert a = new Alert("No records",

" No records found in record store", null,

AlertType.CONFIRMATION); a.setTimeout(Alert.FOREVER);

display.setCurrent (a, AddressBookMain.get Instance ());

} )

catch (RecordStoreException re)

re.printStackTrace(); Alert a = new Alert("Error retrieving record",

"Error retrieving record.", AlertType.CONFIRMATION);

a.setTimeout(Alert.FOREVER); display.setCurrent (a, this);

catch (lOException ioe)

}

ioe.printStackTrace();

}

finally

{

return numRecords;

{

public void coramandAction(Command c, Displayable d)

if (c == back)

AddressBookMain.getlnstancel).display ();

}

}

}

Метод buildRecordList() использует составление списка для получения всех записей, хранящихся в хранилище записей, а затем извлекает поле имени каждой из них, чтобы создать список всех имен. Вызов enumerateRecords () выдает RecordEnumeration, содержащий все записи. С помощью методов hasNextRecord() и nextRecord() цикл while просто извлекает имена из каждой записи и добавляет их в объект List для отображения.

Для каждой записи вы должны расшифровать байтовый массив обратно тому процессу, согласно которому вы создали запись ранее. Вы знаете, что первый элемент, имя, является string, так что вы можете преобразовать его из байтов в String. Обратите внимание, что та же самая идиома потока ввода-вывода Java используется здесь для создания DatalnputStream, который поддерживает API для легкого преобразования встроенных типов Java.


Cтpyктypa общих соединений MIDP


Структура общих соединений MIDP определяет инфраструктуру, которая обобщает детали определенных сетевых механизмов, протоколов и их реализаций приложения. В модели структуры общих соединений приложение делает запрос блоку соединения (connector) на создание соединения с указанным ресурсом. Чтобы создать соединение, вы используете адрес общего вида для указания сетевого ресурса. Форма адреса одинакова, независимо от типа соединения.

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

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

В модели общего соединения MIDP вы определяете ресурс и получаете подключение к нему за один этап. Это отличает ее от модели J2SE, где приложение должно привлечь два объекта: один, представляющий сам указанный ресурс, и второй, являющийся потоком или соединением с ним.

Например, чтобы получить доступ к URL в J2SE, приложение создает объект Java,net.URL, который представляет собой ресурс действующего URL. Используя этот объект, приложение затем явно открывает соединение с ресурсом URL, который вырабатывает объект URL-соединения. Этот объект представляет собой текущее соединение между приложением и ресурсом и предоставляет механизм, с помощью которого приложение получает доступ к содержимому ресурса. Теперь приложение может получать входящий поток соединения, которое поставляет содержимое ресурса.


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

В общем, модель J2SE требует от программиста создания потока, который совместим с типом ресурса, к которому получен доступ - URL, файл, сетевой канал, дейтаграмма и так далее. Модель J2SE не извлекает этих подробностей из приложения.

В модели MIDP потоки ведут себя так же, как и в модели J2SE, они все еще не знают ничего о текущем физическом сетевом ресурсе. Они просто знают, как управлять содержимым, предоставленным им, при создании их экземпляра. Блок соединения, однако, скрывает от приложения подробности установления связывания потока с текущим сетевым ресурсом.

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

Чтобы использовать структуру общих соединений, приложения MIDP указывают сетевой ресурс, к которому они хотят получить доступ, используя универсальный идентификатор ресурса (universal resource identifier (URI)), за которым следует синтаксис стандартного URI Интернета, определяемого RFC 2396. URI поддерживает классический синтаксис для идентификации ресурсов в Интернете. Общая форма URI следующая

<схема>://<адрес;<параметры>

Частью URI является его поле схемы, которое представляет собой протокол, используемый для соединения. RFC 2396 поддерживает множество действующих схем, таких, как as file, datagram, socket, serversocket, http, ftp и так далее.



CLDC не определяет поддержку каких- либо из них. Причина этого заключается в том, что спецификация CLDC не позволяет ручной настройки. Поэтому все реализации CLDC должны поддерживать одни и те же свойства. Реализации MIDP, однако, могут реализовать так много настроек, сколько пожелаете. Однако спецификация MIDP требует, чтобы реализации поддерживали, по крайней мере, протокол HTTP 1.1. Несколько факторов влияют на доступность поддержки протоколов в реализациях MIDP:

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

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

Порталы беспроводного Интернета делают использование HTTP своим основным механизмом взаимодействий на уровне приложений.

Реализация платформы MIDP предоставляет действующую реализацию поддержки протоколов. Эти реализации протоколов не являются частью определений MIDP или CLDC. Они представляют собой зависящие от реализации компоненты, указанные в главе 1.


Cтроковая сортировка


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



Cтруктуры интернационализации


В основном проблема всех разработок интернационализации заключается в поиске механизма, который дает возможность приложениям извлекать правильную версию локализованных ресурсов при работе. В отличие от J2SE, MIDP не имеет реального API или классов, которые поддерживают общее извлечение локализованных ресурсов. Здесь нет класса ResourceBundle или каких-либо его подклассов. Приложения MIDP должны создавать свои собственные механизмы для определения и извлечения локализованного ресурса. В реальности, наиболее жизнеспособными подходами являются:

извлечение локализованных ресурсов из файла JAD;

извлечение локализованных ресурсов из текстового файла, который является частью файла JAR приложения;

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

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



DateField


На главном экране демонстрационного приложения UlComponent (смотри http://www.phptr.com/) вторым элементом списка является демонстрационная версия класса DateField. На рисунке 5.1 показано, что DateField является разновидностью Item; как таковой, он должен быть частью Form для того, чтобы быть отображаемым. В листинге 5.5 показан исходный код файла DateFieldDemo.java.

Листинг 5.5. Поскольку экраны являются отображаемыми, метод getlnstanceO должен возвращать экранный объект некоторого вида. Этот возвращает экземпляр Form

import Java.util.Date;

import Java.util.Calendar;

import Java.util.TimeZone;

import javax.microedition.lcdui.Command;

import javax.microedition.lcdui.CommandListener;

import javax.microedition.lcdui.DateField;

import javax.microedition.lcdui.Displayable;

import javax.

microedition.lcdui.Form;

/**

Демонстрирует использование класса DateField пользовательского интерфейса MIDP.

@смотри javax.microedition.Icdui.DateField

public class DateFieldDemo extends Form implements CommandListener

private Command back = new Command("Back", Command.BACK, 1);

private static Displayable instance;

private DateField date = new DateField("Date/Time in GMT",

DateField.DATE_TIME, TimeZone.getDefault () ) ;

/**

Конструктор.

*/

public DateFieldDemo()

}

super ("DateField Demo");

Calendar cal = Calendar.getlnstance();

date.setDate(cal.getTime()) ;

append(date); addCommand (back); setCcmmar.dListener (this) ;

instance = this;

}

/**

Возвращает один экземпляр этого класса. Вызов этого

метода перед созданием объекта возвращает пустой.указатель.

@Возвращает экземпляр этого класса.

*/

public static Displayable getlnstance ()

{

return instance;

}

public void commandAction(Command c, Displayable d)

{

if (c == back)

{

UI ComponentDemo.get Instance().display() ;

}

}

}

Прежде всего, обратите внимание, что DateFieldDemo дополняет класс Form. Конструктор просто добавляет объект DateField к форме и необходимая структура сформирована. Другие методы класса DateFieldDemo сходны с предыдущими примерами, так что я не буду их описывать здесь еще раз.


DateField является простым текстовым элементом, который отображает дату и время. На рисунке 5.4 показан экран дата/время, отображаемый DateFieldDemo.

Первая строка на рисунке 5.4 «Date/Time in GMT» («Дата/время в GMT») является меткой и определяется в первом аргументе конструктора. Вторая строчка является датой, а третья - временем. Конструктор no-arg DateFieldDemo в листинге 5.5 демонстрирует, как устанавливать дату в объекте DateField с помощью объекта Java.util .Calendar.

В этом примере указываются дата и время, потому что вызов конструктора устанавливает отображение обоих значений. Класс DateField определяет три константы (перечисленные в таблице 5.4), которые позволяют вам контролировать то, какая информация отображается.

Таблица 5.4. Константы DateField для управления отображением информации о дате и времени

Константа DateField Описание
public static int DATE Отображает только дату
public static int DATE TIME Отображает дату и время
public static int TIME Отображает только время
Третьим аргументом конструктора DateField в листинге 5.5 является определение временных зон, объект Java.util.TiraeZone. Остерегайтесь того, что спецификация MIDP потребует от реализации поддержки только одной временной зоны. Вы должны знать, какие временные зоны поддерживает ваша реализация. Очень вероятно, что большинство реализаций MIDP поддерживает только одну временную зону.

Вызов конструктора DateField может определять временную зону, которая не поддерживается вашей реализацией MIDP. Если временная зона, которую вы указали в конструкторе, не поддерживается вашей реализацией MIDP, ваша программа все равно будет выполняться без ошибки или предупреждения, но временная зона объекта DateField будет представлять собой какую-либо зону, поддерживаемую реализацией, но не ту, которую вы запрашивали. И время, отображаемое на экране, будет отражать временную зону, используемую объектом DateField, вместо временной зоны, которую вы указали в вызове конструктора.



Объекты DateField являются редактируемыми. Чтобы отредактировать их,

Во-первых, выберите поле даты, показанное на рисунке 5.4.

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

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

Обратите внимание, что реализация размещает экранные клавиши Back (Назад) и Save (Сохранить) на экране. Такое представление интерфейса типично для всех редактируемых компонентов. Когда вы закончите редактирование и вернетесь к предыдущему экрану, время и дата, показываемые на дисплее, изменятся.

На главном экране DateFieldDemo, показанном на рисунке 5.4, вы можете прокрутить до поля времени и, нажав, выбрать его. Дисплей затем покажет экран, изображенный на рисунке 5.6.



Рисунок 5.4. Объект DateField состоит из двух частей: метки и значения, которые отображают количество как текст



Рисунок 5.5. Объект DateField реализует интерфейс, с помощью которого вы можете редактировать значения даты и времени



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


Дейтаграммные соединения и дейтаграммы


Интерфейс javax.microedition.io.DatagramConnecti.on дополняет Connection. Его положение в диаграмме иерархии наследования, показанной на рисунке 8.2, а также его название, предполагают, что дейтаграммные соединения являются на самом деле соединениями, хотя и отличными от других соединений потоков и содержимого соединений. В действительности интерфейс DatagramConnection описывает соединения, которые посылают и получают дейтаграммы через протокол дейтаграмм.

В мире сетевых технологий термин протокол дейтаграмм подразумевает облегченный протокол - протокол без установления состояний. Но само это отличие на самом деле не помогает объяснить его позицию в иерархии структуры общих соединений. Более правильно, вероятно, различать протоколы уровня приложений и низкоуровневые протоколы.

Термин протокол дейтаграмм обозначает протокол, который находится на более низком уровне в модели OSI, чем протоколы уровня приложений. Протоколы дейтаграмм переносят дейтаграммы, которые иногда называются пакетами. Эти протоколы обычно переносят сообщения дейтаграмм с одной машины на другую, основываясь исключительно на информации, содержащейся в этой дейтаграмме. Несколько пакетов, посланных с одной машины на другую, могут быть переданы по различным маршрутам и приходить на назначенный компьютер в любом порядке. Доставка пакетов в общем и целом не гарантирована.

Универсальный интернет-протокол передачи дейтаграмм (Internet Universal Datagram Protocol (UDP)) является одним конкретным примером протокола передачи дейтаграмм. В действительности это протокол, поддерживаемый некоторыми реализациями MIDP. Он встроен непосредственно поверх интернет-протокола (Internet Protocol (IP)) сетевого уровня. Помните, что в соответствии со спецификацией MIDP, HTTP 1.1 является единственным протоколом, который должны поддерживать реализации, все остальные - необязательно. Разработчики должны помнить об этом при учете портативности приложений.

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


В UDP отсутствуют многие свойства, которые имеются в транспортных протоколах, как, например, в TCP, такие, как согласование вариантов соединений, повторная сборка пакетов, сквозной контроль потока, управление окнами, устранение ошибок, разбиение на части и гарантированная доставка. Он отказывается от этих свойств в пользу очень эффективной быстрой пересылки. Приложения MIDP могут использовать дейтаграммные соединения, когда им нужны быстрые соединения без перехода из состояния в состояние и когда не требуется гарантированная пересылка.

В таблице 8.9 перечислены методы интерфейса DatagramConnection. Вы можете видеть, что это относительно простой интерфейс. Эта простота отражает низкоуровневую природу базового протокола реализации. Сравните это с интерфейсом HttpConnection, чьи методы отражают относительно более сложную природу сообщений протокола HTTP и используют поля сообщений типа MIME для определения семантики сообщения. В отличие от протоколов уровня приложений, таких как, HTTP, протоколы дейтаграмм не определяют атрибуты, которые отражают природу полезной нагрузки, которую они транспортируют.

Таблица 8.9. Методы интерфейса DatagramConnection

Название метода DatagramConnection Описание
int getMaximumLength ( ) Выдает максимально возможную длину дейтаграммы, определен базовым протоколом реализации
int getNominalLength ( ) Выдает номинальную длину дейтаграммы
Datagram newDatagram(byte [] buf, int size) Создает новый объект дейтаграммы, получая данные из указанного массива
Datagram newDatagram(byte [ ] buf, int size, String addr) Создает новый обьект дейтаграммы с указанными массивом данных и с указанным адресом назначения
Datagram newDatagramfint size) Создает новый обьект дейтаграммы
Datagram newDatagram (int size, String addr) Создает новый обьект дейтаграммы с указанным адресом
void receive (Datagram dgram) Получает дейтаграмму и забирает ее данные для заполнения данного аргумента дейтаграммы
void send (Datagram dgram) Посылает указанную дейтаграмму
<


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

Оно создает объект DatagramConnection.

Получает объект Datagram из объекта DatagramConnection.

Затем оно заполняет объект Datagram данными, составляющими полезную нагрузку, которая будет послана принимающему объекту.

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

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

Чтобы создать дейтаграммное соединение, вам все равно нужно использовать класс Connector. Вы указываете, что желаете получить дейтаграммное соединение, поставляя строковую дейтаграмму в поле схемы URI, который вы передаете одной или трем формам метода Connector.open(). Полный синтаксис дейтаграммных адресов следующий:

address := <протокол>://<адресат>

protocol := "datagram"

target := [<хост>]:<порт>

host := Значимое DNS-имя хоста или его номер>

port := Значимуй системный номер порта>

Указание полей хоста необязательно. Если вы пропускаете поле хоста, соединение представляет соединение сервера - реализация допускает, что объект, запрашивающий соединение, является сервером. Серверы не инициируют передачу сообщений, так что для указания места назначения имя хоста не требуется. Соединение сервера ожидает клиента для посылки ему дейтаграммы. Сервер извлекает адрес посылающего из дейтаграммы, полученной им, и использует его для ответа. Пример указания соединения сервера:

datagram:/7:513

Если поле хоста указано, соединение открывается как соединение клиента. Реализация предполагает, что запрашивающий является клиентом, который инициирует соединение, поскольку он желает послать дейтаграмму адресованному узлу. Пример соединения клиента, указывающего известный компьютер:

datagram://server.foo.com:513

Когда соединение установлено, ваше приложение может использовать его для отправки и получения дейтаграмм. Интерфейс javax.microedition.io.Datagram определяет дейтаграммы, которые являются частями сообщения, посланными и полученными протоколами передачи дейтаграмм. Объект DatagramConnection посылает и получает объекты Datagram. Обратите внимание, что методы, указанные в таблице 8.9, содержат несколько ссылок на тип Datagram.



В таблице 8. 10 перечислены методы интерфейса Datagram. Обратите внимание, что они отражают только следующие понятия:

адрес - представляет адрес посылающего или принимающего объекта;

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

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

В интерфейсе Datagram отсутствует информация о синтаксисе или семантике полезной нагрузки. Причина этого заключается всего лишь в том, что дейтаграммы не определяют синтаксиса или семантики данных, которые они переносят. Дейтаграммы просто рассматривают свою полезную нагрузку как последовательность байтов. Полезная нагрузка дейтаграммы определяется просто как byte [].

Дейтаграмма может содержать любую информацию. Дейтаграммная служба определяет формат и содержимое ее дейтаграмм. Посылающее и получающее устройства должны создавать дейтаграммы таким образом, чтобы они придерживались этих определений. То есть byte [] должен быть правильно написан посылающим и правильно проанализирован принимающим устройством.

Интерфейс Datagram происходит из интерфейсов Datalnput и DataOutput в пакете java.io. Такое происхождение гарантирует наличие удобного интерфейса для чтения двоичных данных из дейтаграммы и записи в нее. На рисунке 8.4 показана иерархия происхождения интерфейса Datagram. В таблице 8.11 перечислены методы интерфейса Datalnput, а в таблице 8.12 перечислены методы интерфейса DataOutput. Эти интерфейсы идентичны интерфейсам пакета java.io J2SE.



Рисунок 8.4. Дейтаграмма определяет общие данные. Методы в этой иерархии интерфейсов поддерживают только низшую абстракцию, которая дает возможность манипулировать встроенными типами данных. Для полей, определяемых протоколом, абстракции не существует

Таблица 8.10. Методы интерфейса Datagram

Название метода интерфейса Datagram Описание
String getAddress () Выдает адрес в данной дейтаграмме
byte [] getData() Выдает буфер, содержащий полезную нагрузку дейтаграмм
int getLength() Выдает длину полезной нагрузки дейтаграммы
int getOffsetO Выдает смещение указателя для чтения/записи в буфере полезной нагрузки
void reset() Восстанавливает позицию указателя для чтения/записи в буфере полезной нагрузки
void setAddress (Datagram reference) Устанавливает, что адрес данной дейтаграммы является адресом указанной дейтаграммы
void setAddress (String addr) Устанавливает адрес, указываемый строкой
void setData (byte[] buffer, int offset, int len) Устанавливает полезную нагрузку данной дейтаграммы
void setLength (int len) Устанавливает длину полезной нагрузки дейтаграммы
<


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

Таблица 8.11. Методы интерфейса Datalnput

Название метода Datalnput Описание
boolean readBoolean ( ) Считывает только значение Boolean из входного потока
byte readByte() Считывает один байт из входного потока
char readCharf) Считывает символ из входного потока
void readFully (byte [] b) Считывает байты из входного потока, пока указанный массив не наполнится
void readFully(byte[] b, int off, int len) Считывает указанное число байт в указанный буфер, начиная с указанного сдвига
int readlnt() Считывает значение int из входного потока
long readLong() Считывает значение long из входного потока
short readShort() Считывает два входных байта и выдает значение short
int readUnsignedByte() Считывает один байт, дополненный нулями, из потока
int readUnsignedShort () Считывает два входных байта и выдает значение int
String readUTF() Считывает в UTF-8 шифрованную строку символов
int skipBytes (int n) Просматривает п байтов из входного потока
Таблица 8.12. Методы интерфейса DataOutput

Название метода DataOutput Описание
void writeByte (byte [ ] b) Записывает все байты в выходной поток
void write (byte[] b, int off, int len) Записывает указанное число байтов в выходной поток, начиная от смещения
void write (int b) Записывает младший байт в выходной поток
void writeBoolean (boolean v) Записывает значение boolean
void writeByte (int v) Записывает младший байт int
void writeChar (int c) Записывает два младших байта в выходной поток
void writeChars (String s) Записывает каждый символ в уникоде в выходной поток
void writelnt(int v) Записывает int (четыре байта) в выходной поток
void writeLong (long v) Записывает значение long (четыре байта) в выходной поток
void writeShort (int v)

Записывает int как два байта в выходной поток
void writeUTF(String s) Записывает каждый символ в формате Java LJTF, которому предшествуют два байта, показывающие длину в байтах
<


Например, если приложение MIDP хочет взаимодействовать со стандартным демоном синхронизирующего сетевого протокола Unix (Unix Network Time Protocol (NTP)), оно должно создать соединение, которое использует стандартный номер порта демона NTP, то есть 123. Приложение-клиент MIDP должно задать формат полезной нагрузки ответных дейтаграмм, придерживаясь определения NTP. Оно также должно быть способно анализировать ответ, возвращенный сервером.

MIDP кое в чем отличается от платформы J2SE в своей поддержке дейтаграммных соединений. J2SE имеет пакет java.net. Например, ее класс, DatagramPacket определяет дейтаграмму. Класс DatagramSocket реализует протокол передачи дейтаграмм с помощью соединений сокета.

Эти классы не существуют в CLDC/MIDP. В действительности пакет java.net недоступен в CLDC/MIDP. С другой стороны, CDC содержит пакет java.net, который содержит эти классы.

В листинге 8.5 демонстрируются вышеописанные понятия. Код, описанный в этом листинге, является дейтаграммным клиентом, который соединяется с определенной дейтаграммной службой. Важными шагами, выполняемыми программой, являются следующие:

Она получает новый объект DatagramConnection.

Получает объект Datagram из DatagramConnection.

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

Получает ответную Datagram от DatagramConnection. Этот вызов блокирует обработку до тех пор, пока дейтаграмма не будет получена или время вызова не истечет.

Обрабатывает данные в дейтаграмме.

Повторяет цикл для следующих взаимодействий.

Программа, описанная в листинге 8.5, на самом деле не осуществляет этап 3. Его выполнение требует создания должным образом отформатированного сообщения, как ожидается службой, с которой соединяется клиент. Также «обработка», указанная в шаге 5, включает лишь вывод ответа сервера в стандартный результат. В настоящих приложениях клиент использовал бы дейтаграммную информацию для локальной обработки.



Листинг 8.5. Дейтаграммы посылаются и получаются дейтаграммным соединением. Эта программа анализирует полезную нагрузку полученной дейтаграммы и отображает ее на экране

import javax.microedition.midlet.MIDlet;

import javax.microedition.Icdui.Display;

import javax.microedition.Icdui.Command;

import javax.microedition.Icdui.CommandListenerj;

import javax.microedition.Icdui.Displayable;

import javax.microedition.Icdui.TextBox;

import javax.microedition.Icdui.TextFie Id;

import javax.microedition.io.Connector;

import javax.microedition.io.Datagram;

import javax.microedition.io.DatagramConnection;

import Java.io.lOException; ft,

Этот класс реализует дейтаграммкого клиента,

который соединяется с сервером синхронизирующего

сетевого протокола (NTP) через стандартный порт NTP 123.

Для контроля клиента назначается отдельная нить,

поэтому он реализует Runnable. Приложение может

осуществлять коммуникации асинхронно из управления

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

Обратите внимание, что данный файл представляет только «скелет клиента».

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

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

*/

public class DatagramTest extends MIDlet,

implements CommandListener, Runnable

}

private static final int BUF_SIZE = 1024;

private static Command exit =

new Command ("Exit", Command.EXIT, 1);

private static DatagramTest instance; private Display display;

private TextBox dgramText;

// Дейтаграммное соединение. private DatagramConnection conn;

// Дейтаграмма, которая поддерживает посылку

и получение данных, private Datagram dgram;

// Адрес демона синхронизирующего сетевого протокола (NTP) на

// определенном сервере. NTP использует

протокол UDP. private String address = "datagram://srl-usca28-07:123";

/"*

Конструктор No-arg.

*/

public DatagramTest()

{

super (); instance = this;

}

/**

Конструктор.

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



не осуществляется. Если он деформирован, при попытке соединения

будет сброшено исключение.

@param service URI дейтаграммной службы, с которой соединяемся.

*/

public DatagramTest(String service)

(

this ();

address = service;

}

/**

Выдает один экземпляр данного класса. Вызов данного метода

до создания объекта возвращает нулевой указатель.

@выдает экземпляр данного класса.

*/

public static DatagramTest getlnstance()

}

return instance;

{

public void startApp()

}

display = Display.getDisplay (this);

dgramText = new TextBox("Datagram contents", null, 2048,

TextField.ANY); dgramText.setCommandListener (this);

display.setCurrent(dgramText); run ();

}

/*

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

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

Заполняет объект дейтаграммы и посылает его. Получает

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

демонстрации. Бесшумно перехватывает исключения, связанные

с любым соединением.

*/

public void run ()

}

try int maxLength;

// Откройте клиентское соединение,

conn = (DatagramConnection) Connector.open(address);

maxLength = conn.getMaximumLength();

dgram = conn.newDatagram(maxLength);

// Убедитесь, что указатель для чтения/записи находится в

// начале буфера, так что данные будут переписывать

// буферную память, dgram.reset();

// Заполните дейтаграмму перед посылкой сообщением,

// которое служба дейтаграммы поймет.

// Создайте запрос в дейтаграмме.

**/

// Пошлите только что заполненную дейтаграмму. conn.send(dgram);

// Получите дейтаграмму; ее содержимое помещается в

// указанный объект дейтаграммы. conn.receive(dgram);

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

// ответа сервера более управляемому объекту Java,

// как, например, String. Вы можете затем использовать

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

byte [] data = dgram.getData ();

// Извлеките строку ответа. String str = new String (data);

// Проделайте обработку ответа. Здесь он



// просто распечатывается для демонстрации. System.out.println(str);

// Переустановите указатель для чтения/записи для объекта

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

// в буфер дейтаграммы, он перепишет данные последней операции

// по пересылке или получению.

//Это гарантирует, что предыдущие и последующие данные не

// перемешаются в одном буфере и что не будет создано

// испорченных засоренных сообщений.

dgram.reset();

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

// сообщения сервера.

// ....

}

catch (lOException ioe)

(

System.out.println(ioe.getMessage() ) ;

loe.printStackTrace();

quit();

}

return;

}

public void pauseApp()

{

}

void quit()

destroyApp(true); notifyDestroyedf); }

public void destroyApp(boolean destroy) }

try }

conn.close () ;

}

catch (lOException ioe) ioe.printStackTracef) ;

public void display!)

Display.getDisplay(this).setCurrent(dgramText); )

public void commandAction(Command c, Displayable d)

{

if (c == exit)

}

quit();

}

}

}

Обратите внимание, что любой из объектов Datagram по умолчанию содержит тот же адрес, что и создающий их объект DatagramConnection. Вы можете изменять адрес дейтаграммы с помощью методов интерфейса Datagram.

Приложение должно поставлять объект Datagram для посылки или получения дейтаграммы. Чтобы послать дейтаграмму, приложение заполняет объект дейтаграммы данными, составляющими сообщение, которое должно быть послано на сервер. Когда приложение получает дейтаграмму, его объект соединения заполняет объект дейтаграммы данными, которые оно получает от посылающего устройства.

Вы можете использовать тот же объект дейтаграммы для посылки и получения нескольких сообщений. Чтобы сделать это, вы должны убедиться, что вы не перепутали данные нескольких сообщений. Перед повторным использованием объекта дейтаграммы для посылки или приема нового сообщения используйте метод Datagram.reset() для переустановки указателя чтения/записи буфера.

Объект Datagram имеет буфер, в котором хранятся байты, составляющие сообщение, которое будет послано или получено. Если вы повторно используете объект Datagram, байты, которые были помещены в буфер предыдущей операцией посылки или получения, все еще будут находиться там. Вызов reset () устанавливает сдвиг указателя чтения/записи на начало данного буфера и устанавливает длину на 0. Таким образом, вы эффективно переписываете данные любой предыдущей операции, гарантируя, что вы не смешаете байты двух отдельных сообщений.


Другие экранные типы


Вы видели все компоненты MIDP за исключением одного: TextBox. В отличие от TextField TextBox является многострочной редактируемой текстовой областью. Взгляните еще раз на наглядную иерархию наследования, показанную на рисунке 5.1, и вы увидите, что TextBox является видом Screen, а не Item.

Поскольку TextBox является Displayable, вы должны создать объект MID-лета для демонстрации его использования, вы не можете разместить его в другом Screen или Form, как вы могли поступить с компонентами, происходящими от Item. На рисунке 5.11 показан экран TextBoxDemo.

Рисунок 5.11. Экран TextBoxDemo

Ha рисунке 5.11 показан сам экземпляр TextBox, который является Screen. В листинге 5.10 показан частичный исходный код класса TextBoxDemo. Части, которые опущены, являются структурно очень сходными с кодом UIComponentDemo и имеют отношение к атрибутам МШ-лета.

Листинг 5.10. Текстовые окна являются экранами и не нуждаются в форме, в которой можно существовать

import jav,ax.micro etiition.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.TextBox;

import javax.microedition.lcdui.TextField;

import javax.microedition.midlet.MIDlet;

/**

Этот MID-лет демонстрирует использование отображаемого

элемента TextBox пользовательского интерфейса MIDP.

Усмотри javax.microedition.Icdui.TextBox

* /

public class TextBoxDemo extends MIDlet implements CommandListener

private Command quit = new Command("Exit", Command.EXIT, 1);

private static TextBoxDemo instance;

// Компонент пользовательского интерфейса TextBox. private TextBox textBox;

// Максимальное число знаков, которое TextBox может

// поддерживать. private int MAX_SIZE = 100;

// Первоначальный текст в TextBox. private String initialText =

"You can edit the contents of this TextBox";

/**

Конструктор.

*/

public TextBoxDemo()

super () ; instance = this;


}

public void pauseApp()

{

. . .

}

public void destroyApp(boolean destroy)

}

textBox = null; initialText = null; instance = null;

}

void quit()

}

destroyApp (true);

notifyDestroyedf);

public void startApp()

{

texcBox = new TextBoxC'A TextBox", initialText, MAX_SIZE,

TextField.ANY); сextBox.addCommand(quit); textBox.setCommandListener(this);

display();

}

/**

Возвращает единственный экземпляр этого класса.

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

@возращает экземпляр класса.

*/

public static TextBoxDemo getlnstance()

return instance;

}

public void display!)

{

Display. getDisplay(this).setCurrent(textBox);

}

public void commandAction(Command c, Displayable d)

if (c == quit)

{

quit();

}

}

}

Вы можете видеть из конструктора, что TextBox сходен с TextField, за исключением того, что он является многострочной текстовой областью. Аргументами являются заголовок, первоначальный текст, максимальное количество знаков, которое он может поддерживать, и ограничения ввода. Ограничения являются абсолютно теми же, что и в классе TextField.

На рисунке 5.11 изображен первоначальный текст, используемый для создания экземпляра TextBox. Как и в случаях с другими редактируемыми объектами, вы просто выбираете TextBox с помощью кнопки выбора Select эмулятора и затем редактируете содержимое. Вы можете переходить с помощью четырех клавиш стрелок, стирать знаки с помощью клавиши Clear (Очистить) и вводить их с помощью кнопочной панели, либо компьютерной клавиатуры при использовании эмулятора. Конечно, программа может также манипулировать содержимым с помощью API, который поддерживает вставку, удаление, установку максимального размера, установку ограничений и так далее. На рисунке 5.12 показан экран после выбора текстового окна для редактирования.



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


Другие компоненты Item


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



Двойная буферизация


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

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

Смысл этой технологии заключается в ограниченности производительности. Операции по рисованию могут в результате привести к быстрым обновлениям дисплея, заставляя пользователя воспринимать мерцание. Чтобы избежать мерцания, вы должны сначала выполнить ваше рисование во внеэкранной графической среде, а затем скопировать весь внеэкранный графический контекст в оригинальную графику устройства. Операция копирования обычно быстрее, чем множество операций по рисованию, требуемых даже относительно сложными Canvas, так что это будет сделано практически без заметного мерцания.

В листинге 6.9 демонстрируется использование двойной буферизации. Код выполняет несколько простых функций рисования во внеэкранном буфере, затем копирует содержимое этого буфера в саму графическую среду, которая представляет дисплей устройства. Хотя процедуры рисования в этом примере относительно просты, реальное приложение может выполнять намного более сложное рисование, действительно подтверждая необходимость двойной буферизации.

Листинг 6.9. Двойная буферизация использует два графических контекста. Единственный способ получить второй графический контекст в МЮР - через класс Image

import javax.microedition.lcdui.Canvas;

import javax.microedition.lcdui.Command;

import javax.microedition.lcdui.CommandListener;

import javax.microedition.lcdui.Display;

import javax.microedition.lcdui.Displayable;

import javax.microedition.lcdui.Graphics;

import javax.microedition.lcdui.Image;

import Java.io.lOException;

Демонстрирует двойную буферизацию графического контекста


для отображения в Canvas.

public class DoubleBufferDerao extends Canvas

implements CommandListener

{

// Константа, которая представляет белый цвет.

private static final int WHITE = OxFF « 16 I OxFF « 8 | OxFF;

private static Command back = new Command("Back", Command.BACK, 1);

GraphicsDemo gDemo = GraphicsDemo.getlnstance();

private Display display = Display.getDisplay(gDemo);

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

// внеэкранного объекта Graphics, private Iraage offscreen;

// Переменная, используемая для определения того, осуществляет

// ли реализация автоматическую двойную буферизацию.

// Сохраняет значение true, если реализация автоматически

// осуществляет двойную буферизацию, иначе становится

false. private boolean autoDoubleBuffered = true;

/**

Конструктор No-arg.

* /

public DoubleBufferDemo()

super();

addCoramand(back); setCommandListener(this); display.setCurrent(this);

if ( ! isDoubleBufferedO )

{

// Если реализация не использует двойную буферизацию

// автоматически, извлеките Image для того, чтобы мы могли

// получить из него внеэкранный Graphics. Этот Image изменяемый!

// Его размерами являются высота и ширина данного Canvas.

offscreen = Image.createlmage(getWidth (),

getHeight () ) ;

autoDoubleBuffered = false;

}

)

protected void paintdipRect (Graphics g)

int clipX = g.getClipX() ;

ir.t clipY = g.getClipY() ;

int clipH = g.getClipHeight();

int clipW = g.getClipWidth();

int color = g.getColor ();

g.setColor (WHITE);

g.fillRect(clipX, clipY, clipW, clipH);

g,setColor(color);

}

public void paint(Graphics g)

}

Graphics originalG = null;

int width = getWidthf); int height = getHeight ();

if (!autoDoubleBuffered)

}

// Сохраняем первоначальный графический контекст и получаем

// новый внеэкранный Graphics из утилиты Image.

originalG = g;

g = offscreen.getGraphics ();

// Очищаем отсекаемый прямоугольник с помощью нового объекта

// Graphics. Таким образом, мы используем двойную буферизацию



// для очистки Canvas, следовательно, избегая мерцания.

// Очистка Canvas является рисованием, как и все другие

// операции по рисованию. paintdipRect (g) ;

}

else

{

// Очищаем Canvas с первоначальной графикой, поскольку

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

paintdipRect (g) ;

}

for (int x = 0, у = 0; (x < width /2); x = x + 2)

{

g.drawRect(x, y, (width - x) - x, (height - y) - y) ;

у +1; у +1 ;

}

// При рисовании изображения содержимое внеэкранного

// контекста Graphics изображения на самом деле копируется

// в контекст Graphics устройства. if (!autoDoubleBuffered)

{

originalG.drawlmage(offscreen, 0, 0,

Graphics.TOP | Graphics.LEFT);

{{

public void commandAction(Command c, Displayable d)

}

if (c == back)

GraphicsDemo.getInstance().display!);

}

}

}

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

if (!isDoubleEuffered())

{

offscreen = Image.createlmage(getWidth(), getHeight());

autoDoubleBuffered = false;

}

Если реализация не поддерживает двойную буферизацию, приложению не нужно выполнять ее. Метод Canvas.IsDoubleBuffered() сообщает вам, не выполняет ли реализация двойную буферизацию неявно. Обратите внимание на конструкцию объекта Image. Этот вызов Image, create Image () создает изменяемый объект Image. Приложение нуждается в изменяемом Image, потому что оно выполняет рисование в контексте Graphics объекта Image, являющемся нужным вам внеэкранным буфером. Это единственный способ получения дополнительного Graphics в MIDP.

Метод paint () содержит остальной код двойной буферизации. Если автоматическая двойная буферизация не осуществляется, приложение должно выполнить ее. Это требует второго графического контекста. Следующий фрагмент метода paint () демонстрирует эту идиому

public void paint(Graphics g)

if (!autoDoubleBuffered)

originalG = g;



g = offscreen.getGraphics();

else

{

paintClipRect(g);

}

. . .

}

Временная переменная хранит ссылку на первоначальный объект Graphics, который представляет графический контекст устройства. Новый графический контекст получается через объект Image, созданный ранее. Этот Graphics связан с Image. Последовательность событий представлена схематично на рисунке 6.11.

Теперь метод paint (Graphics g) выполняет свои операции по рисованию во внеэкранном контексте Graphics. При выполнении он копирует содержимое внеэкранного Graphics в первоначальный контекст Graphics, что в результате формирует изображение на дисплее. Операция копирования совершается с помощью вызова метода Graphics.drawlmage(). Этот метод говорит по сути: «Копируй содержимое графического контекста этого аргумента изображения в меня».

Механизм двойной буферизации в MIDP отличается от двойной буферизации Swing. В Swing вы можете выполнять двойную буферизацию операций по рисованию в любом Component, не только в объектах Canvas. Приложения Swing вызывают Java. awt. Component .getGraphics () для получения внеэкранного графического контекста. Приложение может рисовать в этом контексте. Оно затем связывает этот внеэкранный графический контекст с самим устройством.



Рисунок 6.11. Левая половина предаавляет состояние во время первого ввода метода paint. Правая сторона представляет состояние после получения внеэкранного контекста Graphics. Ссылка сохраняет первоначальный контекст Graphics. Кодирование цвета показывает, что внеэкранный контекст Graphics связан с объектом изображения

MIDP не имеет такого вызова. Единственной ссылкой на объект Graphics, которая связана с реальным устройством, является та, что передается методу paint (Graphics g) Canvas.


Фильтры записей


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

Первый аргумент в методе enuraerateRecords() указывает фильтр записей. Фильтр является объектом, определяющим семантику соответствия записи набору критериев, которые определяют, должна ли запись включаться в набор списка.

Фильтр записей является классом, реализующим интерфейс RecordFilter, который определяется в пакете javax.microedition.rms. Этот интерфейс определяет единственный метод boolean matches (byte [] candidate). Ваш подкласс RecordFilter задает этот метод и устанавливает критерии фильтрации записей, указанных в списке всех записей хранилища записей. Метод enumerateRecords() активизирует вашу реализацию на каждой записи, извлеченной из хранилища записей.

В листинге 7.3 показан код класса SearchScreen. Java. Он ищет записи, которые начинаются с подстроки, введенной пользователем, или эквивалентные указанной пользователем строке.

Листинг 7.3. Поиск имен, которые начинаются с подстроки, введенной пользователем, использует API в классе AddressBook, определяющем семантику поиска

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.TextField;

import javax.microedition.rms.RecordEnumeration;

import javax.microedition.rms.RecordStoreException;

import Java.util.Enumeration;

import Java.util.Vector;

/**

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

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

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

в адресной книге.

*/

public class SearchScreen extends Form


implements CommandListener

{

private static Command go =

new Command("Go", Command.SCREEN, 1);

private static Command back = new Command("Back", Command.BACK, 1);

private static SearchScreen instance; private Display display;

private AddressBookMain addressBook; private TextField keyEntry;

/**

Конструктор.

*/

public SearchScreen(}

(

super("Search for entry");

instance = this;

PersistenceDerao pDemo = PersistenceDemo.getlnstance () ;

display = Display .getDisplay (pDerno) ;

addressBook = AddressBookMain.getlnstance ();

keyEntry = new TextField("Enter name",

null, 20, TextFieid.ANY); append(keyEntry);

addCommand(go); addCommand(back);

setCoramandListener(this);

}

/**

Возвращает один экземпляр данного класса.

Вызов данного метода до создания объекта возвращает нулевой указатель.

/**

возвращает экземпляр данного класса.

**/

public static SearchScreen getlnstance ()

return instance; ) void display!)

( display.setCurrentlthis) ;

}

/**

Отображает данные, переданные на экран.

На самом деле этот метод передает обязанности по отображению

данных экземпляру SearchResultScreen. Этот метод,

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

Затрата выражается в Vector записей из хранилища записей адресной книги.

*/

void displaySearchResults(Vector results)

SearchResultScreen screen =

new SearchResultScreen (results);

display. setCurrenJ: (screen) ;

)

Создает конечный набор записей, соответствующих указанному имени.

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

соответствовать имени, введенному

пользователем в TextField "keyEntry". Этот метод задействует метод

AddressBook.getMatchesByName() для применения специального фильтра,

определяющего соответствие этого имени.

*/

Vector buildSearchResults()

{

AddressBook addressBook =

AddressBookMain.getInstance().getAddressBookf);

String matchKey = keyEntry.getString(); Vector results = new Vectorf);

try



{

RecordEnuraeration re =

addressBook.getMatchesByName(matchKey);

byte [] record = null;

while (re.hasNextElement())

record = re.nextRecord () ; results.addElement(record);

}

}

catch (RecordStoreException rse)

}

rse.printStackTracet) ;

)

return results;

)

/**

Создает результаты поиска и отображает их на экране.

class BuildSearchResultsAction implements Runnable

{

public void run ()

Vector results = buildSearchResults ();

displaySearchResults(results) ;

}

}

public void commandAction(Command c, Displayable d) ;

if (c == go)

Runnable action = new BuildSearchResultsAction();

action.run () ;

)

else if (c == beck)

}

AddressBookMain.getInstanced.display!);

}

}

}

Метод buildSearchResults() в классе SearchScreen получает список записей, вызывая метод getMatchesByName (String matchKey) в классе AddressBook. Этот метод фильтрует записи для вывода лишь тех, в которых поле имени начинается с matchKey.

Метод getMatchesByName () выполняет эту фильтрацию, пересылая фильтр записей как первый аргумент в метод enumerateRecords (). Экземпляр MatchAllNamesFilter определяет семантику фильтра для нахождения всех записей, которые начинаются с подстроки matchKey.

Метод enumerateRecords () обращается к следующему методу объекта фильтра для каждой записи в хранилище:

boolean matches(byte [] candidate)

Если в результате выводится true, он включает эту запись в набор списка. Теоретически это сходно с определением запроса SQL в системе родственных баз данных. Объект RecordFilter определяет критерии поиска.

Обратите внимание, что в листинге 7.2 аргумент RecordFilter был равен нулю. Таким образом класс RecordList может вывести все записи в списке, фильтр не применяется.

Вы можете описать несколько фильтров для поддержки поиска по различным критериям. Следуя программе листинга 7.4, вы можете определить несколько внутренних классов, которые реализуют RecordFilter и используют внутренний класс, соответствующий осуществляемому поиску.


Форматирование дат, времени и чисел


MIDP не предоставляет поддержки форматирования дат, времени, числовых или денежных значений. В MIDP нет классов платформы J2SE, которые поддерживают это форматирование: здесь нет классов DateFormat, NumberFormat и DecimalFormat. Однако производители могут предоставлять определяемые реализацией классы для поддержки этих возможностей форматирования.

MIDP определяет классы Date и TimeZone в своем пакете java.util, но эти классы на самом деле не интернационализированы. То есть их интерфейсы не определяют каких-либо возможностей, которые связаны с чувствительной к региональным настройкам обработкой.

Класс Date просто представляет определенный экземпляр формата времени в Универсальном синхронизированном времени (UTC). В MIDP нет поддержки изменения значения Date на представление временного значения в любой другой временной зоне или для форматирования временных значений при отображении пользователям. Платформа J2SE, однако, имеет связанные классы (такие, как DateFormat), которые могут форматировать значения даты в манере, принятой в данном регионе. В MIDP нет таких классов.

MIDP поддерживает временные зоны с помощью своего класса java.util.TimeZone. Этот класс абстрактен. Ваша реализация MIDP предоставит, по крайней мере, один конкретный подкласс, который представляет временную зону GMT. Спецификация MIDP требует поддержки только временной зоны GMT, однако ваша реализация может также поддерживать другие зоны.

Метод TimeZone.getDefault() выдает объект TimeZone, который представляет временную зону, устанавливаемую по умолчанию, для сервера, на котором ваше приложение запущено. Убедитесь, что он может определить временную зону GMT, даже если это не временная зона, в которой работает приложение вашего компьютера.

Метод TimeZone.getTimeZone(String id) выдает объект TimeZone для трехбуквенного обозначения аргумента временной зоны, указанного в вызове. Имейте в виду, что выдаваемый объект может не представлять временную зону, которую вы запрашивали, потому что реализация не поддерживает ее. Очевидно, для вас как для разработчика приложения важно знать, в каких временных зонах поддерживается ваша платформа.



Java 2 Micro Edition (J2ME)


Класс Gauge также является производным от Item. Запуск GaugeDemo из основного экрана создает дисплей, показанный на рисунке 5.8.

Рисунок 5.8. Существуют интерактивные и неинтерактивные измерители. Вы можете изменять значение интерактивного измерителя

Пример, показанный на рисунке 5.8, размещает в Form четыре элемента: два измерителя (gauge) и метку String для каждого. Метки идентифицируют два различных типа измерителей, определяемых классом Gauge: интерактивный (interactive) и неинтерактивный (noninteractive). Реализация формирует изображение двух типов измерителей по-разному, так что пользователь может различать их тип.

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

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

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

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


Сокращенный исходный код, приведенный в листинге 5.7, показывает, как настроить максимальное и первоначальное значение Gauge в конструкторе.

Листинг 5.7. Четырьмя параметрами, требуемыми для указания измерителя, являются его состояние, удобное для прочтения название, первоначальное значение и максимальное значение

import javax.microedition.Icdui.Command;

import javax.microedition.lcdui.CommandListener;

import javax.microedition.Icdui.Displayable;

import javax.microedition.Icdui.Form;

import javax.microedition.Icdui.Gauge;

/**

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

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

Усмотри javax.microedition.Icdui.Gauge

*/

public class GaugeDemo extends Form

implements CommandListener

}

private String gaugelLabel = new String("Interactive gauge");

private Gauge interactiveGauge = new Gauge("Interactive", true, 50, 15);

private String gauge2Label = new String("Non-interactive");

private Gauge staticGauge = new Gauge ("Static", false, 50, 25);

/**

Конструктор.

*/

public GaugeDemol)

}

super("Gauge Demo");

append(gaugelLabel); append(interacciveGauge);

append(gauge2Label); append(staticGauge);

}

addCommand(back); setCoramandListener(this);

instance = this;

}

...

}

В отличие от демонстрационной версии, настоящее приложение, вероятно, также изменяет значение измерителя в течение своего жизненного цикла, используя следующие методы в классе Gauge:

public void setValue(int value) public int getValuel)



Где найти примеры кogoв из этой книги


Все примеры кодов, с которыми вы столкнетесь в этой книге, могут быть найдены на Web-сайте издательства «Prentice Hall» по адресу http://www.phptr.com/piroumian.



Где сКачать J2ME


Вы можете скачать J2ME Wireless Toolkit и полную API-документацию, следуя ссылкам на Java Developer Connection с адреса http://java.sun.com/. Там вы найдете полную API-документацию для CDC, профиля Foundation Profile и CLDC/MIDP, а также инструментарий для всех платформ, таких, «Платформа программирования J2ME для портативных устройств» как Solaris, Linux, Windows NT и Windows 2000.



Генерирование события оплаты


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

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

Системы инициализации поддерживают различные модели формирования счетов. Разнообразные модели генерируют различные типы информации о событиях выдачи счетов, которая представляет собой разные схемы оплаты. В следующем списке представлены некоторые возможные схемы оплаты:

оплата за загрузку приложения;

оплата за установку;

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

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

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

Как разработчик вы должны предусмотреть, какие схемы оплаты предпочтительнее для вас - и какие из них представлены на рынке. Информация дескриптора вашего приложения должна отражать ваши предпочтения в формировании счетов и оплате в виде информации, которую вы поставляете во время регистрации приложения.

Системы инициализации используют различные схемы для пересылки информации о событии выдачи счета системам организации счетов. Одна из схем заключается в пересылке каждого события при его осуществлении. Другой метод - сбор групп событий выдачи счетов и пересылки их как пакета для групповой обработки системой организации счетов. Групповая обработка счетов может выполняться периодически тем же образом, как обычно поставщики услуг осуществляют работу со счетами.



Графическая модель


Класс Graphics определяет возможности низкоуровневого графического рисования. Если вы уже разрабатывали программы на AWT или Swing, этот класс покажется вам очень знакомым. В действительности его свойства и программный интерфейс практически идентичны, хотя и являются подмножеством свойств класса Graphics J2SE.

Класс Graphics определяет модель, которая позволяет приложениям рисовать- или раскрашивать на языке Java - базовые двухмерные фигуры на Canvas. Описанным методом public void paint(Graphics g) осуществляется рисование в вашем подклассе Canvas, подменяя объявленный метод protected abstract в Canvas. Класс Canvas имеет пустое paint(Graphics g) определение, что объясняет, почему он не создает какого-либо визуального представления.

Каждый конкретный подкласс Canvas имеет доступ к объекту Graphics. Этот объект Graphics является копией графического контекста устройства и извлекает зависящий от реализации графический контекст устройства, являющийся частью программного обеспечения операционной системы устройства.

Объект Graphics, с которым вы работаете, создан реализацией Canvas при инициализации объекта Canvas. Это одна из главных причин, почему вы должны убедиться, что ваш конструктор подкласса Canvas вызывает super()! Реализация пересылает графический объект в ваш Canvas, когда он вызывает метод вашего класса paint (Graphics g).



Графическое рисование


Вы, несомненно, обратили внимание, что canvas, показанный на рисунке 6.2, был чистым за исключением экранной клавиши Exit (Выход). Причина этого кроется в том, что класс Canvasl не описывает свое визуальное представление. Все конкретные подклассы Canvas должны определять свой внешний вид для того, чтобы визуализировать какие-либо визуальные атрибуты. Для этого они должны привлекать помощь класса javax.microedition.lcdui.Graphics. Основная цель класса Graphics заключается в поддержке рисования на Canvas.



Иерархия Компонентов пользовательского интерфейса MIDP


Диаграмма иерархии наследования MIDP, показанная на рисунке 5.1, повторяет то, что вы уже видели на рисунке 3.7 в главе 3. Вы уже видели некоторые из компонентов пользовательского интерфейса MIDP, показанные в этой иерархии, а именно Displayable, Screen, Form и Alert.

Вы знаете, что класс Displayable определяет природу основы любого компонента, который может быть отображен, и что класс Screen определяет базовую абстракцию пользовательского интерфейса MIDP - экран. Класс Screen является первым Displayable, который вы видели, a Form был первым конкретным типом используемого экрана.

В таблице 5.1 кратко описаны все компоненты пользовательского интерфейса MIDP в пакете javax.micfoedition.lcdui.

Рисунок 5.1. Компоненты пользовательского интерфейса MIDP принадлежат либо к классу объектов Displayable, либо к классу объектов Item за исключением класса Ticker, который происходит от Object.

- абстрактный класс,
- конкретный класс

Таблица 5.1. Описание всех компонентов интерфейса пользователя MIDP

Имя класса компонента, Ul MIDP Описание Принадлежность к- API MIDP
Alert Информационное всплывающее окно, может быть модальным или рассчитанным по времени Высокоуровневый
AlertType Определяет типы объектов Alert Высокоуровневый
Canvas Экран, в котором вы можете рисовать графические объекты и получать низкоуровневые события ключ/перо Низкоуровневый
ChoiceGroup Группа выбираемых элементов, находится в Form Высокоуровневый
Command Семантическая инкапсуляция событий пользовательского интерфейса Как высокоуровневый, так и низкоуровневый
DateField Компонент, который отображает дату и время Высокоуровневый
Display Класс, который извлекает структуры данных дисплея устройства Высокоуровневый
Displayable Прародитель всех компонентов, которые могут быть отображены Как высокоуровневый, так и низкоуровневый
Font Класс, предоставляющий шрифты для экранного текста Высокоуровневый
Form Экран, который собирает элементы для отображения Высокоуровневый
Gauge Тип визуального измерителя Высокоуровневый
Graphics Отображение контекста графических элементов устройства Низкоуровневый
Image Отображение изображений в формате Portable Network Graphics [PNG, переносимая сетевая графика] Как высокоуровневый, так и низкоуровневый
Imageltem Form, размещающий отображение изображения Высокоуровневый
List Список выбираемых объектов Высокоуровневый
Screen Абстрактный прародитель всех типов экранов Высокоуровневый
Stringltem Form, размещающий отображение строки Высокоуровневый
TextBox Многострочный, многоколонковый текстовой контейнер Высокоуровневый
TextField Однострочный текстовой контейнер Высокоуровневый
Ticker Отображение тикера Высокоуровневый



Игровые действия


В дополнение к константам, которые вы уже видели, класс Canvas определяет константы GAME_A, GAME_B, GAME_C, GAME_D и FIRE, которые представляют игровые действия, отражая влияние игровой индустрии в J2ME. Значения этих констант нестандартны и изменяются в зависимости от реализации.

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

public int getKeyCode (int gameAction)

public int getGameAction(int keyCode)

В листинге 6.2 эти методы используются для вывода диагностической информации о каждом полученном событии отпускания клавиши. Если вы запустите эту программу и исследуете результат, вы увидите, что не каждая клавиша имеет связанное с ней игровое действие. В данном случае метод getGameAction () возвращает значение 0. Более того, не все устройства реализуют GAME_A, GAME_B, GAME_C и GAME_D. Примером устройства, которое не использует эти игровые действия, является Motorola Accompli 008.



Инициализация приложения с локализованными ресурсами


Все три стратегии разработки, представленные в этой главе, включают добавление локализованных ресурсов к остальному коду приложения. В реальных средах беспроводных сетей все может работать по-другому. Некоторые беспроводные транспортировщики уже поддерживают системы инициализации приложений, которые поддерживают динамическое обнаружение, извлечение и установку приложений Java на мобильных устройствах. Возможно, что вскоре все транспортировщики будут иметь такие системы. Инициализация приложений является темой главы 10.

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

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



Использование атрибутов МID-лета для определения локализованных ресурсов


Как вы знаете, вы можете размещать определяемые пользователем атрибуты в файле JAD вашего приложения. Это означает, что вы можете использовать файл JAD для определения атрибутов MID-лета, которые представляют локализованные ресурсы, используемые вашим приложением.

В данном подходе программы больше не вставляют ресурсы (например, текстовую строку) в приложение. Вместо этого программы размещают ресурсы в файле JAD. Программа ищет ресурс, извлекая значение некоторого атрибута. Программист определяет имена атрибутов так, чтобы они содержали компонент, который представляет контекст региональной настройки. Таким образом программы могут извлекать версию ресурса, который совместим с их контекстом региональной настройки среды исполнения.

Для демонстрации данного подхода я вновь использовал демонстрационную программу HelloWorld из главы 3. Приложение переименовано на IISNDemo для отличия его от оригинальной версии.

В листинге 9.1 показан файл дескриптора приложения, используемый программой IISNDemo. Несколько новых атрибутов были добавлены в файл JAD. Они представляют собой текстовые строки, которые пользователь видит во время исполнения приложения. Обратите внимание, что существует две версии каждой из данных строк: одна английская и одна французская. Этот файл поддерживает выполнение приложения в английской и французской языковых средах.

Листинг 9.1. Файл JAD содержит один атрибут на строку приложения на поддерживаемую региональную настройку

I18NDerao-alert-en_US: Alert

I18NDemo-alert-fr_FR: Alerce

H8NDemo-alert_text-en_US: The button was pressed

I18NDemo-alert_text-f£_FR: Le bouton a ete presse

I18NDemo-alert_title-en_US: Button Pressed

I18NDemo-alert_title-fr_FR: Eouton a ete Presse

I18NDemo-cancel-en_US: Cancel !18NDemo-cancel-fr_FR: Quitter

I18NDemo-exit-en_US: Exit IlSNDemo-exit-fr_FR: Sortie

I18NDemo-greeting-en_US: Another MIDlet!

I18NDerao-greeting-fr_FR: Un autre MIDlet!

I18NDemo-help-en_US: Help I18NDemo-help-fr_FR: Aider

I18NDemo-item-en_US: Item I18NDemo-item-fr_FR: Item,


I18NDemo-menu-en US: Menu

I18NDemo-menu-fr_Fr: Menu

I18NDemo-ok-en_US: OK

I18NDemo-ok-fr_FR: OK

I18NDe:r.o-sayhi-en_US: Say hi

I18NDemo-sayhi-fr_FR: Dis bonjour

I18NDemo-screen-en_US: Screen

I18NDemc-screen-fr_FR: Ecran I18NDemo-stop-en_US: Stop

I18NDemo-stop-fr_FR: Arreter I18NDemo-title-en_US: Hello, World

I18NDemo-title-fr_FR: A116, tout le Monde MIDlet-1: I18N Demo 1,

I18n.png, I18NDemo MIDlet-Info-URL:

MIDlet-Jar-Size: 19101 MIDlet-Jar-URL: ilSn.jar MIDlet-Name:

I18n MIDlet-Vendor: Vartan Piroumian MIDlet-Version: 1.0

Имена атрибутов в файле JAD, показанные в листинге 9.1, приобретают следующую форму:

<название МID-лета>-<ключ>-<обозначение региональной настройки>

Например, следующие два атрибута определяют заголовок MID-лета на английском и французском языках:

I18NDemo-title-en_US: Hello, World .

I18NDemo-title-fr_FR: A116, tout le Monde

В листингах 9.2 и 9.3 показаны два файла, которые составляют исходный код приложения. Они определяют и реализуют схему поиска атрибутов, отражаемую именами атрибутов в файле JAD. Программа извлекает версию атрибута, связанного с контекстом региональной настройки, в котором приложение работает.

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

Листинг 9.2. Измененный класс HelloWorld называется IlSNDemo. Он использует схему поиска для извлечения правильной версии атрибутов строки приложения, базируясь на региональной настройке

1 import javax.microedition.midlet.MIDlet;

2

3 import javax.microedition.Icdui.Display;

4 import javax.microedition.Icdui.Displayable;

5 import javax.microedition.Icdui.Form;

6

7 /**

8 Первая версия приложения IlSNDemo.

9

10 <р>Данная версия демонстрирует простейший подход к

11 загрузке локализованных ресурсов из файла JAD MID-лета.



12 этот подход быстро становится непригодным при большом

13 количестве ресурсов. И он полезен только для текстовых

14 сообщений, но не для других видов локализованных

15 ресурсов.

16 */

17 public class IlSNDemo extends MIDlet

18 {

19 // Региональная настройка, указанная для выполнения в

20 // данном MID-лете.

21 private String locale;

22

23 // Displayable. Этот компонент отображается

24 // на экране.

25 private HelloForm form;

26

27 // Экземпляр Display. Данный объект управляет всеми

28 // компонентами Displayable данного MID-лета.

29 private Display display;

30

31 // Экземпляр MID-лета.

32 private static IlSNDemo instance;

33

34 // Префикс имен атрибутов локализуемых

35 // ресурсов.

36 String attrPrefix = new String("I18NDemo-");

37

38 /**

39 Конструктор No-arg.

40 */

41 public I18NDemo()

42 {

43 super();

44 instance = this;

45 }

46

47 /*

48 Получает экземпляр данного класса, который существует в

49 работающем приложении.

50

51 Звоззращает экземпляр, созданный при запуске

52 приложения.

53 */

54 public static IlSNDemo getlnstance()

55 {

56 if (instance == null)

57 {

58 instance = new IlSNDemo ();

59 }

60 return instance;

61 }

62

63 /**

64 Запускает .MID-лет. Получает текущую региональную

65 настройку для реализации. Использует ее для

66 создания префикса ключей атрибутов всех

67 локализованных ресурсов. Названия локализованных

68 ресурсов в файле JAD соответствуют

69 совместимой схеме имен.

70 */

71 public void startApp()

72 {

73 // Извлекает региональную настройку из программного

74 // обеспечения AMS. Региональная настройка должна быть

75 // установлена прежде, чем данный MID-лет начнет выполняться.

76 locale =

77 System.get Property("microedition.locale");

78

79 // Создает элемент Displayable. Получает локализованную

80 // String, которая представляет заголовок

81 // Form, из определенных пользователем атрибутов файла

82 // JAD. Получает все локализованные строки таким

83 // образом.



84 String formTitle = getResource("title");

85 form = new HelloForm(formTitle);

86

87 // Это приложение просто отображает единственную форму,

88 // созданную ранее.

89 display = Display.getDisplay(this);

90 display.setCurrent(form);

91 }

92

93 /**

94 Выдает значение, связанное с указанным

95 ключом из списка определяемых пользователем

96 ресурсов MID-лета в файле JAD приложения.

97

98 @param key - ключ пары «ключ-значение».

99

100 @выдает значение, связанное с указанным

101 ключом.

102 */

103 public String getResource(String key)

104 {

105 StringBuffer index = new

106 StringBuffer(«ttrPrefix);

107 String value;

108

109 index.append(key);

110 index.append('-');

111 index.append(locale);

112

113 value = getAppProperty(index.toString ());

114 return value;

115 }

116

117 /**

118 Закрываем приложение. Уведомляем

119 реализацию о выходе.

120 */

121 public void quit() ,

122 {

123 notifyDestroyed ();

124 }

125

126 public void destroyApp(boolean destroy)

127 {

128

129 }

130

131 public void pauseApp()

132 (

133

134 }

135 }

Листинг 9.З. Класс HelloForm определяет объект формы и использует ту же самую схему, что и основной класс МID-лета

1 import javax.raicroedition.midlet.MIDlet;

2

3 import javax.microedition.Icdui.Alert;

4 import javax.microedition.Icdui.AlertType;

5 import javax.microedition.Icdui.Command;

6 import javax.microedition.Icdui.CommandListener;

7 import javax.microedition.Icdui.Display;

8 import javax.microedition.Icdui.Displayable;

9 import javax.microedition.Icdui.Form;

10

11 /*

12 Данный класс определяет Form, которая отображает

13 простой текст и меню команд. Цель данного класса

14 заключается в демонстрации i18n и 110n

15 видимых пользователю атрибутов. Класс извлекает

16 локализованные ресурсы из программного обеспечения

17 управления приложениями.

18 */

19 открытый HelloForm дополняет Form

20 {

21 // Заголовок данной Form, устанавливаемый по умолчанию.

22 private static final String DEFAULT_TITLE =



23 "Hello, World";

24

25 // Блок прослушивания команд, который обрабатывает

26 // командные события в данной Form.

27 private MyCommandListener cl = new

28 MyCommandListener ();

29

30 //. Экземпляр дисплея, связанный с

31 // данным MID-летом.

32 Display display;

33

34 // Ссылка на связанный с данным объектом

35 // объект MID-лета.

36 I18NDemo midlet;

37

38 // Уведомление, отображаемое в ответ на

39 // активацию некоторых команд данной Form.

40 Alert alert;

41

42 // Команды, размещаемые в данной форме.

43 private Command showAlert;

44 private Command sayHi;

45 private Command cancel;

46 private Command exit;

47 private Command help;

48 private Command item;

49 private Command ok;

50 private Command screen;

51 private Command stop;

52

53 /**

54 Конструктор No-arg. Устанавливает заголовок по умолчанию

55 для данной формы.

56 */

57 HelloForm()

58 {

59 this(DEFAULT_TITLE);

60 }

61

62 /**

63 Конструктор.

64

65 @param title - заголовок Form.

66 */

67 HelloForm(String title)

68 {

69 super(title);

70

71 midlet = IISNDemo.get Instance()

72

73 // Добавляет строковый элемент в форму.

74 String msg = midlet.getResource("greeting" );

75 append(msg);

76

77 display = Display.getDisplay(midlet);

78

79 // Добавляет MyCommandListener в Form для прослушивания

80 // события нажатия клавиши «Back», которое должно

81 // создавать всплывающее диалоговое уведомление Alert.

82 setCommandListener(cl);

83

84 showAlert = new

85 Command(midlet.getRe source("alert") ,

86 Command.SCREEN, 1);

87 addCommand(showAlert);

88

89 sayHi = new .

90 Command(midiet.getResource("sayhi"),

91 Command.SCREEN, 1);

92 addCommand(sayHi);

93

94 cancel = new

95 Command{midlet.getResource("cancel"),

96 Command. SCREEN,, 1) ;

97 addCommand(cancel) ;

98

99 exit = new

100 Command(midlet.getResource("exit") ,

101 Command.SCREEN, 1);

102 addCommand(exit);

103

104 help = new



105 Command(midlet.getResource("help"),

106 Command.SCREEN,, 1);

107 addCommand(help) ;

108

109 item = new

110 Command(midiet.getResource("item"),

111 Command.SCREEN, 1);

112 addCommand(item);

113

114 ok = new

115 Command(midlet.getResource("ok"),

116 Command.SCREEN, 1);

117 addCommand(ok) ;

118

119 screen = new

120 Command(midlet.getResource("screen"),

121 Command.SCREEN, 1);

122 addCommand(screen);

123

124 stop = new

125 Command(midlet.getResource("stop"),

126 Command.SCREEN, 1);

127 addCommand(stop) ;

128 }

129

130 // Данный класс просто прослушивает активацию

131 // какой-либо команды. Экземпляр HelloForm

132 // устанавливает экземпляр данного класса как

133 // свой блок прослушивания команд. Экземпляр

134 // объекта не проверяет информацию команды, а

135 // просто отображает модальное Ale показывающее,

136 // что экранная клавиша была активирована пользователем.

137 private class MyCoramandListener

138 implements CommandLister.er

139 {

140 public void commandAction(Command c,

141 Displayable d)

142 {

143 String title =

144 midlet.getResource("alert_title") ;

145 String msg = null;

146

147 if (c == showAlert)

148 {

149 msg = midlet.getResource("alert_text");

150 alert = new Alert(title,

151 msg,

152 null, AlertType.INFO);

153 alert.setTimeout(Alert.FOREVER);

154 display .setCurrer.t (alert, HelloForm.this);

155 }

156 else if (c == sayHi)

157 {

158 alert = new Alert("Button pressed",

159 msg,

160 r.ull, AlertType.INFO);

161 alert.setTimeout(Alert.FOREVER);

162 display.setCurrent(alert, HelloForm.this);

163 }

164

165 if (c == exit)

166 {

167 IISNDemo.get Instance().destroyApp (true);

168 }

169 }

170 }

171 }

Проблема разработки интернационализации заключается в схеме поиска, используемой для нахождения локализованных строк в файле JAD. Программно определяемый метод getResource (String key), заданный в строках с 103 по 115, на самом деле определяет и реализует схему поиска. Чтобы обнаружить ресурс, метод getResource (String key) создает имя атрибута, а затем ищет сам атрибут.



Например, следующие два оператора, показанные в строках 84 и 85, выбирают строку заголовка Form, использующейся в приложении.

String formTitle = getResource("title");

form = new HelloForm(formTitle);

Метод создает полное имя атрибута, объединяя три строки: 118NDemo - префикс атрибута для данного приложения, идентификатор атрибута ресурса без какой-либо меняющейся в зависимости от региональной настройки информации и обозначение региональной настройки. Параметр строки title является идентификатором атрибута ресурса, а не заголовком формы.

В строке 36 MID-лет определяет префикс атрибута I18NDemo-. Метод startApp() извлекает информацию о контексте региональной настройки, в котором исполняется приложение, из системного свойства microedition.locale и сохраняет его как экземпляр атрибута.

Объект HelloForm использует значение, выданное вызовом getResource(), как его заголовок. Класс HelloForm в листинге 9.3 повторяет этот сценарий. Он просто вызывает getResource() для поиска локализованных значений всех текстов, которые пользователь видит во время исполнения приложения.

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

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

В листинге 9.2 используется системное свойство microedition.locale для извлечения региональной настройки для того, чтобы акцентировать внимание на понятии динамически определяемого контекста региональной настройки и ресурсов, связанных с контекстами региональных настроек. Разграничение ресурсов для различных региональных настроек может помочь пониманию вашей разработки и сделать ваше программное обеспечение более восстанавливаемым. Не забывайте, что в будущем, когда устройства станут более мощными, реализации MIDP смогут очень хорошо поддерживать множество региональных настроек. Когда это произойдет, подход, показанный в листинге 9.2, станет более предпочтительным.



Взглянув на метод getResource(), показанный в строчках с 103 по 115, вы увидите, что он использует метод MIDlet.getAppProperty() для извлечения ресурсов из файла дескриптора приложения и файла манифеста. Если атрибут существует в обоих файлах с абсолютно одинаковыми именами ключа, тогда значение извлекается из файла дескриптора приложения и значение в файле манифеста игнорируется. Если не найдено ни одного атрибута или если для ключа не найдено ни одного значения, выдается значение ноль. Если вводимый ключ не найден, сбрасывается NullPointerException.

Значения атрибутов в файле JAD (или манифеста) должны быть кодированы с помощью символьной кодировки, которая поддерживает нужный язык. Существует два способа выполнения этого:

Кодировать значения атрибутов с помощью символьной кодировки, предназначенной для языка региональной настройки. Символьная кодировка может быть той, что соответствует более чем одному лишь нужному языку, как, например, LJTF-8.

Кодировать значения атрибутов с помощью последовательностей переключения кода Уникод, например \u4EA9. Файл все равно состоит только из символов ASCII, но последовательности переключения уникода могут представлять любой символ любого письменного языка.

Листинг 9.2 включает поддержку английской и французской региональных настроек. Символьная кодировка ISO8859-1 может представлять английский и французский алфавиты. Если вы желаете локализовать данное приложение на языки, не поддерживаемые семейством ISO8859 (китайский, например), вам придется кодировать значения атрибутов с помощью соответствующей многобайтовой символьной кодировки.

Если вы выбрали первый из только что описанных подходов (кодирование с помощью символьной кодировки, предназначенной для языка региональной настройки), вам понадобится найти текстовой редактор, который поддерживает методы ввода китайского языка и записывает символы в соответствующую кодировку. Либо вы можете использовать второй подход и вводить последовательности переключения уникода Java для каждого символа. Просто найдите точку кодирования уникода для каждого символа в вашей строке. Этот подход работает, поскольку класс Java.lang.String знает, как создавать строковые объекты из последовательностей переключения уникода. Ваше приложение может затем считывать значения атрибутов и создавать из них объекты String.



Вы можете определить имена атрибутов с помощью панели Settings J2MEWTK. Поскольку WTK не поддерживает ввод не-ASCII текста, однако, вы не можете определить не английский локализованный текст значений атрибутов. Чтобы ввести не английские символы, вам придется использовать текстовой редактор для ввода символов непосредственно в файл JAD. Вы можете использовать тот, что поддерживает редакторы методов ввода (IME) для назначенного языка, или вводить последовательности переключения уникода.

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

Чтобы поддерживать нестроковые локализованные ресурсы - например, чувствительный к региональной настройке числовой форматер, - вы можете установить значение атрибута на имя класса, который реализует ваш форматер. Например, вы можете определить атрибут следующим образом:

I18NDemo-number_forraat-fr_FR: NumberFormat_FR

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

...

try

{

String name =

getAppProperty("I18NDemo-number_format-fr_FR");

// "name" теперь эквивалентно "NumberFormat_FR"

Class с = Class.forName(name);

NumberFormat_FR nf =

(NumberFormat_FR) с.new Instance();

}

catch (InstantiationException ie)

{

...

}

catch (IllegalAccessException iae)

{

...

catch (ClassNotFoundException cnfe)

{

...

}

...

Конечно, вы должны предоставить MIDP-совместим'ый классификационный файл Java с файлом JAR вашего приложения для того, чтобы эта схема работала.

Другой недостаток использования дескрипторов приложения заключается в том, что они неправильно обращаются с файлами JAD и манифеста для программно определяемых свойств. Вы, возможно, думаете, что это просто философское размышление, но это отражается на производительности. Чтобы прочитать файлы JAD или манифеста, вы должны привлечь помощь AMS. Единственный вызов вовлекает несколько компонентов реализации. Файл JAD на самом деле не предназначен для подобного частого доступа. Вы могли заметить, что происходит снижение производительности при попытке прочесть большой файл локализованных ресурсов - или любого другого типа программно определяемых ресурсов.

Кроме того, этот единственный файл JAD должен соответствовать всем МЮ-летам в наборе MID-летов, что делает его еще больше. При использовании файла JAD не в простой демонстрационной программе, а где-либо еще он станет слишком громоздким, так как число атрибутов вырастет.

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


Использование классификационных файлов Java для определения интернационализированных ресурсов


В данном третьем подходе приложения определяют классы Java, которые содержат локализованные ресурсы. Каждый класс содержит ресурсы для одной региональной настройки. Файлы откомпилированы и упакованы как часть JAR приложения. При работе локализованные ресурсы затем достаются с помощью создания экземпляра соответствующего класса.

Эта разработка аналогична разработке иерархии пакетов ресурсов J2SE. Классы java.util.ResourceBundle и java.util.ListResourceBundle J2SE являются абстрактными классами, определяющими структуру создания агрегаций произвольных чувствительных к региональным настройкам объектов Java. Эти объекты могут быть любыми объектами Java.

Этот подход к разработке интернационализации определяет свою собственную версию классов ResourceBundle и ListResourceEundle J2SE. В листингах 9.7 и 9.8 показаны их реализации, которые определяют, соответственно, подмножества классов ResourceBundle и ListResourceBundle платформы J2SE. Хотя эти реализации являются собственными, сигнатуры методов являются теми же, что и у их аналогов в J2SE.

Листинг 9.7. Класс ResourceBundle определяет структуру для агрегирования ресурсов, не заключающую в себе информацию об абстракции, требуемой для выполнения агрегирования

import Java.util.Hashtable;

/**

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

приложения. Он реализует подмножество класса java.util.ResourceBundle J2SE,

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

public abstract class ResourceBundle

«Родительские» ресурсы. Если ресурс не найден в данном пакете, производятся

поиски родительского пакета.

*/

protected ResourceBundle parent;

/**

Конструктор No-arg. public ResourceBundle () super();

/**

Получает пакет ресурсов с указанным именем класса.

Имя класса уже содержит язык и код страны назначения в стандартном формате.

Например, имя класса пакета ресурсов может быть «I18NDeraoResources_fr_FR».

@param className Полное имя класса, такое, как «I18NDemoResources_fr_FR».


@возвращает объект пакета ресурсов.

*/

public static ResourceBundle getBundle(String classNarae) throws IllegalArgumentException,

KissingResourceException

{

return ResourceBundle.getBundle(className, "");

}

/**

Получает пакет ресурсов с указанным базовым именем.

@ param baseName Полностью определенное имя класса извлекаемого пакета.

Например, базовое имя «I18NDemo_fr_FR» - «HSNDerao».

Sparam строка региональной настройки, представляющая региональную настройку,

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

Ожидаемая форма <язык>.<страна> в соответствии с ISO 639 и ISO 3166, соответственно.

@выдает пакет ресурсов для возвращения

*/

public static ResourceBundle getBundle(String baseName, String locale)

throws IllegalArgumentException, MissingResourceException

{

Class c; if (baseName == null)

{

throw new IllegalArgumentException("No basename.");

{

String className = baseName + "_" + locale;

ResourceBundle bundle = null;

try

{

с = Class.forName(className);

bundle = (ResourceBundle) с.newlnstance();

}

catch (ClassNotFoundException cnfe)

throw new

MissingResourceException("Class not found.");

}

catch (InstantiationException ie)

{

throw new

MissingResourceException("Can11 instantiate.") ;

}

catch (IllegalAccessException iae)

{

throw new

MissingResourceException("Can1t access.");

}

return bundle;

}

/**

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

@param key Ключ объекта

@выдает объект с указанным ключом

*/

public final Object getObject(String key)

throws MissingResourceException

}

Object obj; if (key == null)

{

throw new NullPointerException();

}

obj = handleGetObject(key); if (obj == null SS parent 1= null)

{

obj = parent.getObject(key);

}

if (obj == null)

{

throw new MissingResourceException ();

return obj;

}

/**

Ищет данный пакет ресурсов для объекта с указанным ключом.



@ param key Ключ поиска желаемого объекта.

@выдает объект с указанным ключом.

*/

protected abstract Object handleGetObject(String key);

}

Листинг 9.8. Класс. ListResourceBundle использует «список» (в действительности двухмерный массив объектов) для агрегирования ресурсов

/**

Этот класс определяет пакет ресурсов как подходящий массив ресурсов.

Он воспроизводит класс того же имени, определяемый платформой J2SE, java.util.ListResourceBundle.

<р>Данный класс абстрактен. Приложения вынуждены создавать его

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

<р>0пределенные подклассы конкретного приложения должны быть названы так,

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

стандартами ISO 639 и ISO 3166 для языковых и страновых кодов соответственно.

*/

открытый абстрактный класс ListResourceBundle дополняет ResourceBundle

/**

Конструктор No-arg.

*/

public ListResourceBundle()

super();

// Массив ресурсов в формате ключ-значение, private static final Object [][] contents = null;

/**

Получает массив ресурсов.

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

*/

public abstract Object [][] getContents();

/**

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

@param key Ключ пары ключ-значение.

@выдает объект, который представляет значение пары ключ-значение.

*/

public final Object handleGetObject(String key)

{

Object value = null; if . (key == null)

{

return null;

}

Object [][] pairs = getContents ();

for (int i = 0; i < pairs. length; i + +) if (key.equals(pairs [i] [0]))

value = (pairs [i] [1]) ;

}

}

return value;

}

}

Смысл данной разработки заключается в том, что разработчики приложения создают подклассы ListResourceBundle. Каждый подкласс представляет собой агрегацию локализированных ресурсов для определенной региональной настройки. В листинге 9.9 показан конкретный подкласс ListResourceBundle, который предоставляет ресурсы приложения, локализованные под англоязычный регион. Отметьте, как имя класса отражает поддерживаемую региональную настройку. Эта схема присвоения имен не только облегчает управление классом во время разработки, она также помогает обнаруживать местонахождение и загружать класс во время работы приложения.



Листинг 9.9. Конкретный подкласс ListResourceBundle легко определяет локализованные ресурсы. Каждый подкласс определяет «список» значений ресурсов (в действительности являющийся массивом) и определяет метод getContents (). import javax.microedition.Icdui."Image

import Java. io.lOException;

/**

Данный класс определяет локализованные ресурсы приложения I18NDemo3.

Вы извлекаете ресурс, вызывая метод getObject() в классе ResourceBundle.

*/

public class I18NDemoResources_en_US extends ListResourceBundle

// Содержит один из локализованных ресурсов. Нам необходимо

// инициализировать данную переменную в статическом

// инициализаторе данного класса, private static Image applcon;

private Object [][] contents =

{

("title", "Hello, World"}, // Form title.

("greeting", "My third MIDlet"}, // Form text.

("alert_title", "Button Pressed"), // Alert title.

{"alert_text", "A button was pressed!"),// Alert text.

{"exit", "Exit"}, // "Exit" menu item.

{"menu", "Menu"}, // "Menu" soft button.

{"cancel", "Cancel"}, // "Cancel" menu item.

{"stop", "Stop"}, // "Stop" menu item.

{"ok", "OK"}, // "OK" menu item.

{"alert", "Alert"}, // "Alert" soft button.

{"sayhi","Say Hi"}, // "Say Hi" menu item.

{"screen", "Screen"}, // "Screen" menu item.

{"item", "Item"}, // "Item" menu item.

{"help", "Help"}, // "Help" menu item.

{"app_icon", applcon} // Application icon.

};

/**

Конструктор No-arg.

*/

public I18NDemoResources_en_US()

{

super();

}

public Object ij[] getContents()

{

return contents;

}

// Необходим статический инициализатор для инициализации

// переменных, которые не могут быть инициализированы в



// массиве содержимого. Например, мы не можем выделить что-либо

// в массиве содержимого'для создания изображения и,

// выполнить требуемую обработку исключений.

static

{

try

{

applcon = Image.createlmage("i!8n-en_US.png");

}

catch (lOException ioe)

{

System.оut.println(ioe.getMessage)));

ioe.printStackTrace();

}

}

}

Классы, которые определяют локализованные ресурсы для других региональных настроек, должны создавать подкласс непосредственно класса ListResourceBundle. В листинге 9.10 показан подкласс, содержащий ресурсы, локализованные под французский язык. Единственное усилие, требующееся для создания этого класса, - это изменение суффикса имени класса и редактирование текстовых строк. За исключением имени и значений атрибутов класс идентичен англоязычной версии.

Если класс определяет другие ресурсы кроме текстовых строк, тогда при создании экземпляра класса должны быть созданы объекты, соответствующие региональной настройке. Последний объект в списке является примером нетекстового ресурса, который инициализируется при создании экземпляра класса. Класс использует статический инициализатор Java для создания экземпляра статических нестроковых объектов при загрузке класса. Наша программа должна использовать статический инициализатор, потому что каждый класс локализованного ресурса создает определяемое региональной настройкой изображение.

Листинг 9.10. Ресурс каждой региональной настройки определяется в своем собственном соответствующем подклассе ListResourceBundle. Данный подкласс определяет атрибуты, локализованные для франкоязычного региона

import javax.microedition.lcdui.Image;

import Java.io.lOException;

/'**

Класс, представляющий локализованные ресурсы для французского языка региона Франции.

Обратите внимание на использование последовательностей

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

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

этот файл с помощью одних только символов ASCII, делая его эксплуатацию



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

*/

public class I18NDemoResources_fr_FR

extends ListResourceBundle

{

// Содержит один из локализованных ресурсов. Нам необходимо

// инициализировать данную переменную в статическом

// инициализаторе данного класса.

private static Image applcon;

private Object [][] contents =

{ {"title", "All\uOOf4, tout le Monde"), // Form title.

// Создаем текст: "My third MIDlet". ("greeting", "Mon troisi\uOOe8me MIDlet"),

// «Кнопка была нажата» ("Button was Pressed").

{"alert_title", "Bouton a \uCOe9t\uOOe9 press\uOOe9"),

// «Кнопка была нажата» ("The button was pressed").

{"alert_text", "Le bouton a \uOOe9t\uOOe9 press\uOOe9!"},

("exit", "Sortie"), // Пункт меню «Выход» ("Exit").

("menu", "Menu"), // Экранная клавиша «Меню» ("Menu").

("cancel", "Quitter"), // Пункт меню «Отмена» ("Cancel").

("stop", "Arreter"), // Пункт меню «Стоп» ("Stop").

("ok", "OK"), // Пункт меню "OK".

("alert", "Alerte"), // Экранная клавиша «Уведомление» ("Alert").

i"sayhi","Dis bonjour"), // Пункт меню «Скажи- привет» ("Say Hi").

("screen", "Ecran"), // Пункт меню «Экран» ("Screen").

{"item", "Item"), //.Пункт меню «Предмет» ("Item").

("help", "Aider"), // Пункт меню «Помощь» ("Help").

("app_icon", applcon) // Значок приложения.

};

/**

Конструктор No-arg.

*/

public I18NDemoResources_fr_FR()

{

super();

/**

Получает содержимое пакета ресурсов.

@возвращает массив пар ключ-значение.



public Object [][] getContents()

{

return contends;

}

// Обратите внимание, что статический инициализатор создает

// экземпляры класса Image с другими изображениями, нежели он

// использует в региональной настройке en_US. static

{

try

{

applcon = Image.createlmage("i!8n-fr_FR.png");

}

catch (lOException ioe)

{

System.out.printIn(ioe.getMessage());

io.e.printStackTracel) ;

}

}

}

В листинге 9.11 показана программа I18NDemo3, которая использует данный набор классов пакетов ресурсов. Метод startAppO данного MID-лета создает экземпляр соответствующего класса пакета ресурсов. Он создает имя класса, связывая базовое имя семейства файлов локализованных ресурсов, I18NDemoResources, с конечной региональной настройкой. С помощью всего лишь нескольких операторов приложение получает доступ ко всем локализованным ресурсам.

Листинг 9.11. Класс I18NDemo3 создает экземпляр соответствующего класса пакета ресурсов для контекста рабочей региональной настройки. Ресурсы любого типа Java данного пакета легко доступны

import javax.microedition.midlet.MIDlet;

import javax.microedition.Icdui.Display;

import javax.microedition.Icdui.Displayable;

import ]avax.microedition.Icdui.Form;

import Java.util.Hashtable;

Третья версия приложения IlSNDemo.

<р>Данная версия IlSNDemo использует пакет ресурсов для определения

локализованных ресурсов. Приложение определяет текущую региональную

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

соответствующие локализованные ресурсы. Если оно не может найти эти ресурсы,

оно загружает ресурсы U.S. English, представленные языком en_US и страной назначения.

<р>Этот подход наиболее предпочтителен. Легко поддерживаются локализованные

ресурсы, отличные от строк.

*/

public class I18NDemo3 extends MIDlet

{

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

// данного МID-лета.

private String locale;

// Пакет ресурсов, который содержит локализованные ресурсы

// для выполнения данного приложения, private static ResourceBundle bundle;



{

// Displayable. Этот компонент отображается

// на экране.

private HelloForm3 form;

// Экземпляр Display. Этот объект управляет всеми

// компонентами Displayable для данного MID-лета.

private Display display;

// Экземпляр MID-лета.

private static !18NDerao3 instance;

/**

Конструктор No-arg.

*/

public I18NDemo3()

{

super();

instance = this;

}

/**

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

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

*/

public static I18NDemo3 getlnstance()

{

if (instance == null)

{

instance - new I18NDemo3();

}

return instance;

}

/**

Получает пакет ресурсов, используемый данным MID-летом.

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

к локализованным ресурсам приложения.

@выдает локализованные ресурсы MID-лета.

*/

public static ListResourceBundle getResourceBundle ()

{

return (ListResourceBundle) bundle;

}

/**

Запускает MID-лет. Определяет текущую региональную настройку среды исполнения

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

это имя для создания имени класса Java, который затем загружается с помощью

Class. Если нет соответствия пакету ресурсов, по умолчанию используется пакет

ресурсов U.S. English.

*/

public void startApp()

{

// Извлекает региональную настройку из программного обеспечения

// AMS.Региональная настройка должна быть установлена

// до выполнения данного MID-лета.

locale = System.getProperty("microedition.locale");

bundle = null;

cry

{

bundle =

ResourceBundle.getBundle("IlSNDemoResources", locale);

if (bundle == null)

{

bundle =

ResourceBundle.getBundle("IlBNDemoResources", "en_US");

}

}

catch (MissingResourceException mre)

mre.printStackTracef);

}

try

}

/ Создаем элемент Displayable. Получаем локализованную

// String, которая представляет заголовок Form.

String formTitle = (String)

bundle.getObject("title");

form = new HelloForm3(formTitle);



}

catch (MissingResourceException mre)

{

rare.printStackTrace();

}

// Это приложение просто отображает одну форму, созданную ранее, display = Display.getDisplay(this); display.setCurrent(form);

}

/**

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

пользователем ресурсов MID-лета в файле JAD приложения.

@param key Ключ пары ключ-значение.

@выдает значение, связанное с указанным ключом.

*/

public Object getResource(String key)

}

Object resource = null;

try

{

resource = bundle.getObject(key);

}

catch (MissingResourceException mre)

}

}

return resource;

/**

Выход из MID-лета. Уведомляет реализацию, что она

может прервать работу всех ресурсов приложения.

Реализация вызывает destroyApp().

*/

public void quit()

{

notifyDestroyed();

/*

public void destroyApp(boolean destroy)

{

{

public void pauseApp()

{

}

}

На рисунке 9.1 показано основное окно, созданное программой I18NDemo3 при ее запуске в региональной настройке en_US. Программа динамически извлекает локализованные ресурсы, описанные в листинге 9.9. На рисунке 9.2 показан экран меню того же приложения, запущенного в региональной настройке fr_FR, которая использует локализованные ресурсы, описанные в листинге 9.10. Код приложения I18NDemo3 абсолютно не изменяется. Он просто динамически определяет контекст региональной настройки при инициализации и загружает соответствующий пакет ресурсов.



Рисунок 9.1. Весь текст, видимый пользователю, локализован. Программа извлекает локализованные англоязычные ресурсы с помощью того же механизма, что и для любой другой региональной настройки



Рисунок 9.2. Логическая схема приложения извлекает франкоязычные ресурсы из объекта, который определяет франкоязычные ресурсы приложения

Важным моментом этой разработки является прозрачность и организационная простота, осуществленная с помощью использования последовательностей переключения кода Unicode Java для кодирования не-ASCII строковых литералов в подклассах ListResourceBundle. Эти файлы содержат классы Java, которые вы откомпилировали вместе с остальным исходным кодом приложения. Компилятор преобразует последовательности переключения уникода в строковых литералах на двоичные значения уникода. Поскольку компиляторы Java понимают последовательности переключения уникода, вам не придется выполнять какое-либо преобразование кодировки для получения локализованного текста в форме, требуемой при выполнении, а именно в форме двоичных значений символьной кодировки уникода.



Исследование листингов 9.9 и 9. 10 может не убедить вас в выгодах использования последовательностей переключения уникода. Как-никак, большинство текстовых редакторов и операционных систем изначально поддерживают западноевропейские языки, такие, как французский. По этой причине легче создавать локализованные ресурсы для западноевропейских региональных настроек без повторной сортировки последовательностей переключения уникода. Например, пользователи могут создавать французские символы, вводя двухклавишные последовательности переключения в большинстве текстовых редакторов или вставляя их с помощью специальной функции меню.

Возможно, следующий пример более явно отразит выгоды использования последовательностей переключения уникода. В листинге 9.12 показан класс I18NDemoResources_ru_RU, который определяет локализованные ресурсы для русского языка. На рисунке 9.3 показан внешний вид экрана, показанного на рисунке 9.2, когда региональная настройка устанавливается на ru_RU, которая представляет собой русский язык. Ввод русских символов с помощью системы западных языков более сложен, чем ввод французских символов. Однако структуру класса I18NDemoResources_ru_RU и инструменты, требуемые для его создания, не приходится изменять для поддержки использования кириллицы.



Рисунок 9.3. Последовательности переключения уникода легко поддерживают все письменные языки. С помощью простого текстового редактора вы можете создавать локализованные ресурсы для языков, которые не представлены на вашей компьютерной клавиатуре

Листинг 9.12. Файл русского локализированного ресурса также содержит последовательности переключения уникода, которые дают вам возможность представлять символы кириллицы без использования каких-либо специальных текстовых редакторов или инструментов

import javax.microedition.Icdui.Image;

import Java.io.lOException;

/"*

Данный класс определяет локализованные ресурсы

для приложения I18NDemo3. Вы извлекаете ресурс, вызывая метод getObjectf)

в классе ResourceBundle.



*/

public class I18NDemoResources_ru_RU

extends ListResourceBundle

{

// Содержит один из локализованных ресурсов. Нам необходимо

// инициализировать эту переменную в статическом инициализаторе

// данного класса.

private static Image applcon;

private Object [][] contents =

// "Привет, мир".

("title", "\u0417\u0434\u0440\u0430\u0441\u0442\u0432\u0443\u0439,

\u041c\u0446\uO*440!"),

// "Мой третий MID-лет".

{"greeting", "\u041c\043e\u0439 \u0442\u0440\u0435\u0442\u0438\u0439 MIDlet!"},

// "Кнопка нажата".

{"alert_title",

"\u041a\u043d\u043e\u043f\u043a\u0430 \u041d\u0430\u0436\u0430\u0442\u0430"},

// "Кнопка была нажата!".

("alert_text", "\u041a\u043e\u043e\u043f\u043a\u0430

\u0411\u044b\u043b\u0430 \u043d\u0430\u0436\u0430\u0442\u0430!"},

// Экранная клавиша "Выход".

("exit", "\u0412\u044b\u0445\u043e\u0434"},

{

// Экранная клавиша "Меню".

("menu", "\u041c\u0435\u043d\u044e"},

// Пункт меню "Отмена".

{"cancel",

"\u041f\u0440\u0435\u043a\u0440\u0430\u0442\u0446\u0442\u044c"),

// Пункт меню "Стоп".

("stop", "\u0421\u0442\u043e\u043f"},

// Пункт меню "ОК". {"ok", "OK"},

// Экранная клавиша "Предупреждение".

("alert", "\u0412\u043d\u0446\u043c\u0430\u043d\u0446\u0435"),

// Пункт меню "Скажи привет".

("sayhi","\u0421\u043a\u0430\u0436\u0446

\u043f\u0440\u0446\u0432\u0435\u0442"),

it Пункт меню "Экран".

{"screen", "\u042d\u043a\u0440\u0430\u043d"),

// Пункт меню "Предмет".

("item", "\u041f\u0440\u0435\u0434\u04c3\u0435\u0442"),

// Пункт меню "Помощь".

("help", "\u041f\u043e\u043c\u043e\u0449\u044c"},

// Значок приложения. ("app_icon", applcon} };



/**

Конструктор No-arg.

*/

public I18NDemoResources_ru_RU()

super();

}

public Object [][] getContents()

}

return contents;

}

// Необходим статический инициализатор для инициализации

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

// в массиве содержимого. Например, мы не можем выделить

// что-либо в массиве содержимого для создания изображения и

// выполнить требуемую обработку исключений.

static

{

try

{

applcon = Image.createlmage("i!8n-ru_RU.png");

}

catch (lOExce'ption ioe)

{

System.out.print In(ioe.getMessage());

ioe.printStackTrace() ;

}

}

}

Если вы все еще не убеждены, взгляните на листинг 9.13, который показывает ресурсы того же самого приложения, локализованные на японский язык. Класс I18NdemoResources_ja JP был создан с помощью того же текстового редактора, основанного на ASCII. Японские символы не могут быть введены в традиционном текстовом редакторе без поддержки IME. И, если вы используете IME, вы должны убедиться, что используете уникод для записи строковых литералов в файл. В противном случае вашему приложению придется выполнять преобразование посимвольной кодировки.

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

import javax.microedition.Icdui.Image;

import Java.io.lOException;

/**

Данный класс определяет локализованные ресурсы для приложения I18NDemo3.

Вы извлекаете ресурс, вызывая метод getObject() в классе ResourceBundle.

*/

public class I18NDemoResources_ja_JP

extends ListResourceBundle

{

// Содержит один из локализованных ресурсов. Нам необходимо

// инициализировать эту переменную в статическом инициализаторе

// данного класса.

private static Image applcon;

private Object [][] contents =

{

// "Привет, мир"

{"title", "\u24f64\u3055\u3093, \u3053\u3093\u306b\u3061\u306f"),

// "Мой третий MID-лет".

("greeting", "\u79cl\u306e 3 \u3063\u3081\u306e MIDlet"},



// "Кнопка нажата".

{"alert_title")

"\u30dc\u30bf\u30f3\u304c\u62bc\u3055\u308c\u307e\u3057\u305f"},

// "Кнопка была нажата".

"alert_text",

"\u30dc\u30bf\u30f3\u304c\u62bc\u3055\u308c\u3C7e\u3057\u305f!"}

// Пункт меню "Выход", {"exit", "\u51fa\53e3"},

// Экранная клавиша "Меню".

("menu", "\u30el\u30cb\u30e6\u30fc"),

// Пункт меню "Отмена".

("cancel", "\u3Cad\u30e4\u30f3\u30bb\u30eb"),

// Пункт меню "Стоп". {"stop", "\u505c\u6b62"),

// Пункт меню "ОК". ("ok", "OK"},

// Экранная клавиша "Предупреждение", {"alert", "Alert"),

// Пункт меню "Скажи привет", ("sayhi","\u30cf\u30a4"},

// Пункт меню "Экран".

{"screen", "\u30b9\u30af\u30ea\u30f3"),

// Пункт меню "Предмет", {"item", "\u9805\u76ee"),

// Пункт меню "Помощь".

("help", "\u308d"},

// Значок приложения.

{"app_icon", applcon)

/**

Конструктор No-arg.

*/

public I18NDemoResources_ja JP()

{

super();

)

public Object [][] getContents ()

{

return contents;

{

// Необходим статический инициализатор для инициализации

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

// массиве содержимого. Например, мы не можем выделить что-либо

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

// требуемую обработку исключений.

static

{

try

{

applcon = Image.createlmage("i!8n-ja_JP.png");

{

catch (lOException ioe)

{

System.out.println(ioe.getMessage());

ioe.printStackTracef);

}

}

}

В листинге 9.14 показан файл I18NDemoResources_zh_CH. Java, который определяет локализованные ресурсы для упрощенного китайского языка.

Листинг 9.14. Этот файл определяет локализованные ресурсы для региональной настройки zh_CN, Китай, приложения I18NDemo3



import javax.microedition.Icdui.Image; import Java.io.lOException;

/**

Данный класс определяет локализованные ресурсы для приложения I18NDemo3.

Вы извлекаете ресурс, вызывая метод getObjectO в классе ResourceBundle.

*/

public class I18NDemoResources_zh_CN

extends ListResourceBundle

{

// Содержит один из локализованных ресурсов. Нам необходимо

// инициализировать эту переменную в статическом инициализаторе

// данного класса.

private static Image applcon;

private Object [][] contents =

{

// Заголовок формы "Hello, World".

("title", "\u54c8\u7f57\u4el6\754c"),

// Текст формы "My third MIDlet".

("greeting", "\u62ll\u7684\7b2c\u4e09\u4187 MIDlet"},

// Заголовок уведомления "Button Pressed". ("alert_title", "\u6309\u4eOb\u6309\u9215"],

// Текст уведомления "A button was pressed!". ("alert_text", "\u6309\u4eOO\u4187\u6309\u9215!"},

// Пункт меню "Exit".

("exit", "\u767b\u51fa"},

// Экранная клавиша "Menu", ("menu", "\u76ee\u5f54"},

// Пункт меню "Cancel", {"cancel", "\u53d6\u6d88"j,

// Пункт меню "Stop", ("stop", "\u505c\u6b62"},

// Пункт меню "OK". {"ok", "OK"),

// Экранная клавиша "Alert", {"alert", "\u8b66\u793a"),

// Пункт меню "Say Hi", ("sayhi", "\u55e8"},

// Пункт меню "Screen". ("screen", "\u87a2\u5e55"),

// Пункт меню "Item", ("item", "\u9879\u76ee"},

// Пункт меню "Help", {"help", "\u8bf4\u660e"},

// Значок приложения. {"app_icon", applcon}

};

/**

Конструктор No-arg.

*/

public I18NDemoResources_zh CN()

{

super!);

{

public Object [][] getContents ()

{

return contents;

}

// Необходим статический инициализатор для инициализации



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

// массиве содержимого. Например, мы не можем выделить что-либо

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

// требуемую обработку исключений.

static

{

try

{

applcon = Imagb.createlraage("i!8n-zh_CN.png");

}

catch (lOException ioe)

{

System.out.println(ioe.getMessage!)); ioe.printStackTrace();

}

}

}

Использование классификационных файлов Java имеет несколько преимуществ перед двумя предыдущими разработками. Прежде всего, оно позволяет избежать создания комплексной структуры потоков и анализа текстовых файлов, которые вы видели в предыдущем подходе. Доступ к ресурсам так же прост, как создание экземпляра класса. Более важно то, что пакеты ресурсов могут быть легко приспособлены к любому из объектов Java - не только к строкам - как локализованные ресурсы. Первым двум подходам, представленным в этой главе, приходилось определять атрибуты, чьи значения были именами классов, экземпляры которых нужно было создавать, и затем создавать экземпляры данных классов после считывания и анализа файла ресурса. Подход, основанный на пакетах ресурсов, создает экземпляры всех объектов неявно, когда пакет ресурсов создан. И классы пакетов ресурсов оставляют небольшой след, используя меньше ресурсов рабочей памяти, чем предыдущий подход.

Подход пакетов ресурсов также содействует легкому переносу приложений в среду J2SE. Реализации классов пакетов ресурсов, показанных в листингах 9.7 и 9.8, создают только подмножество необходимых свойств. Но их строгое следование интерфейсам версий J2SE означает, что подклассы ListResourceBundle вашего приложения совместимы снизу вверх.

Пакеты ресурсов также способствуют лучшей восстанавливаемости и большей понятности. Зависящие от приложения подклассы ListResourceBundle могут быть легко восстановлены только лишь с помощью текстового редактора, основанного на ASCII. Любой ASCII-текстовой редактор может считывать и записывать символы ASCII или последовательности переключения кода Unicode Java, присутствующие в пакетах ресурсов. Кроме того, поскольку это исходные файлы Java, разработчики могут вставлять комментарии, которые ясно описывают каждый ресурс и контекст, в котором приложение его использует.



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

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

Установка нескольких файлов классов Java для локализованных ресурсов может потребовать больше ресурсов хранения устройства, чем вы можете себе позволить. Наиболее очевидной проблемой при разработке MIDP является потребление памяти. Хотя два первых подхода неудобны по нескольким причинам, они затрачивают меньше ресурсов памяти, чем подход классификационного файла Java. Иногда, когда вы не можете позволить себе лишние траты памяти, вы можете позволить приложению затратить несколько дополнительных секунд при запуске на считывание и анализ локализованных ресурсов. Одним из возможных компромиссов является игнорирование иерархии наследования ResourceBundle и предоставление единственного класса, который содержит локализованные ресурсы для каждой региональной настройки. Предоставьте соответствующий локализованный классификационный файл указанной региональной настройке приложения. Здесь вы жертвуете гибкостью ради производительности. Вы также теряете совместимость снизу вверх с J2SE, но это может быть неважно.


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


Второй подход к локализации использует программно определяемые текстовые файлы, которые содержат локализуемые атрибуты. Приложение с помощью данной схемы может, например, определить один файл локализованных атрибутов для каждой региональной настройки. Схема имен может соответствовать обозначению региональной настройки, например, en_US.txt для английской, fr_FR.txt для французской, ja_JP.txt для японской и так далее. В листинге 9.4 показан один пример такого файла, содержащего пары «ключ-значение» локализованных строк.

Листинг 9.4. Имя данного файла - fr_FR.txt. Он состоит из франкоязычных версий строк приложения

alert: Alerte

alert_title: Bouton Presse alert_text: Le bouton a ete presse

cancel: Quitter exit: Sortie

greeting: Mon troisieme MIDlet!

help: Aider

item: Item

menu: Menu

ok: OK

sayhi: Dis bonjour

screen: Ecran

stop: Arreter

Mtle: A116, tout le Monde

Этот подход по существу тот же самый, что и предыдущий, за исключением того, что теперь вы должны создавать и поддерживать файл ресурса. Любые файлы ресурсов, которые вы создаете, должны быть частью JAR приложения. Вспомните, что спецификация MIDP запрещает прямой доступ к файлам на родной платформе.

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

В листингах 9.5 и 9.6 содержится код, который реализует одну возможную разработку, которая использует файлы текстовых ресурсов. Этот код считывает файлы, отформатированные подобно тому, что показан в листинге 9.4.

Листинг 9.5. Класс I18NDemo2 использует потоки для чтения файлов текстового ресурса. Реализация getResource () теперь отражает новую разработку для извлечения ресурсов из файлов, находящихся в файле JAR приложения


1 import javax.microedition.midlet.MIDlet;

2

3 import javax.microedition.Icdui.Display;

4 import javax.microedition.Icdui.Displayable;

5 import javax.microedition.Icdui.Form;

6

7 import java.io.DatalnputStream;

8 import Java.io.InputStream;

9 import Java.io.InputStreamReader;

10 import Java . io . EOFException;

11 import Java.io.lOException;

12 import Java.io.Reader;

13 import Java.io.UTFDataFormatException;

14 import Java.io.UnsupportedEncodingException;

15

16 import Java.util.Hashtable;

17

18 /**

19 Вторая версия приложения HSNDemo.

20

21 <р>Эта версия'также дехонстрирует простой способ определения

22 локализованных ресурсов. Она считывает файл, который является

23 частью файла JAR приложения (не файла JAD)

24 для загрузки локализованных ресурсов. Файл

25 состоит из набора пар «ключ-значение», одной на строку,

26 которые представляют локализованные строки.

27 MID-лет должен затем проанализировать содержимое файла

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

29

30 <р>Этот подход требует слишком много усилий по обработке

31 потока, который содержит файл

32 локализованных ресурсов. Более того, этот подход

33 не отвечает за локализацию ресурсов, которые

34 не являются строками.

35 */

36 public class I18NDemo2 extends MIDlet

37 {

38 // Файл, который содержит ресурсы для

39 // активной локальной настройки.

40 private String resourceFile;

41

42 // Региональная настройка, указанная для выполнения данного

43 // MID-лета.

44 private String locale;

45

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

47 // используемая данной платформой.

48 private String encoding;

49

50 // HashTable, которая содержит локализованные

51 // ресурсы.

52 private Hashtable resources = new Hashtable () ;

53

54 // Displayable. Этот компонент отображается

55 // на экране.

56 private HelloForm2 form;

57

58 // Экземпляр Display. Данный объект управляет всеми

59 // компонентами Displayable данного MID-лета.



60 private Display display;

61

62 // Экземпляр данного MID-лета.

63 private static I18NDemo2 instance;

64

65 /**

66 Конструктор No-arg.

67 */

68 public I18NDemo2()

69 {

70 super();

71 instance = this;

72 }

73

74 /*"

75 Получает экземпляр данного класса, который существует

76 в действующем приложении.

77

78 @выдает экземпляр, созданный при запуске

79 приложения..

80 */

81 public static I18NDemo2 getlnstance ()

82 {

83 return instance;

84 {

85

86 /**

87 Запускает MID-лет. Получает текущее название

88 региональной настройки. Использует его для создания

89 имени файла, который содержит локализованные

90 ресурсы региональной настройки. Файл ресурса

91 находится в файле JAR приложения.

92 */

93 public void startApp()

94 {

95 // Извлекает региональную настройку из программного

96 // обеспечения AMS. Региональная настройка должна быть

97 // установлена до выполнения данного MID-лета.

98 locale =

99 System.getProperty("microedition.locale");

100

101 // Названия файлов локализованных ресурсов, соответствующих

102 // форме: <язык>_<страна>.txt.

103 // Создает строку имени файла и передает ее в

104 // метод, который открывает файл и извлекает

105 // содержимое.

106 resourceFile = locale + ".txt";

107 int status = loadResources(resourceFile);

108

109 if (status < 0)

110 {

111 quit();

112 return;

113 }

114

115 // Создаем элемент Displayable. Получаем

116 // локализованную String, которая представляет

117 // заголовок Form.

118 String formTitle = getResource ("title" );

119 form = new HelloForm2(formTitle);

120

121 // Это приложение просто отображает одну .

122 // форму, созданную выше.

123 display = Display.getDisplay (this);

124 display.setCurrent(form);

125 }

126

127 /**

128 Загружает определенные пользователем ресурсы приложения

129 из указанного файла. Файл является частью файла JAR

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

131 J2MEWTK хранит файл в файле JAR приложения, расположенном



132 в директории приложения bin/.

133

134 @param file - имя определенного пользователем файла

135 ресурса приложения.

136 */

137 private int loadResources(String file)

138 {

139. Class с = getClass () ;

140

141 if (file == null)

142 {

143 return -1 ;

144 }

145 InputStream is = null;

146 is = с.getResourceAsStream(file);

147 if (is == null)

148 {

149 return -1;

150 }

151 Reader reader = new InputStreamReader(is);

152 processStream(reader);

153 return 0;

154 }

155

156 /**

157

158 */

159 private void processStream(Reader stream)

160 {

161 if (stream == null)

162 {

163 return;

164 }

165 StringBuffer key = new StringBuffer();;

166 StringBuffer value = new StringBuffer ();;

167 while (true)

168 {

169 // Считываем строку. Предполагается, что каждая строка

170 // содержит ключ и значение,

171 // отделенные двоеточием. Если -1, значит

172 // мы достигли конца файла.

173 key.deletef(), key.length());

174 value.delete(0, value.length());

175 int status = readLine(key, value, stream);

176 if (status == -1)

177 {

178 break;

179 }

180

181 // Вставляем этот ресурс в хэшированную таблицу

182 // ресурсов приложения.

183 resources.put(key, value);

184 }

185 }

186

187 /**

188 Считывает и обрабатывает следующую не пустую строку

189 из потока. Формат строки ожидается следующий

190 <ключ>[ \t]*:[ и]*<значение>, где

191 <ключ> and <значение> являются метками, состоящими

192 из буквенных символов или знаков пунктуации, но не

193 из пустых знаков пробела.

194 */

195 private int readLine(StringBuffer key,

196 StringBuffer value,

197 Reader stream)

198 {

199 if (key == null || value == null ||

200 stream == null)

201 {

202 return -1;

203 }

204

205 try

206 {

207 char c;

208 while (true)

209 {

210 // Пропускаем символы новой строки.

211 while (true)

212 {

213 с = (char) stream.read ();

214 if (c == r\n')

215 {

216 continue;

217 }

218 break;

219 }

220

221 if (lisWhiteSpace(c) Si !isDelimeter(c))



222 {

223 key.append(c);

224 }

225

226 // Пропускаем впередиидущий пробел.

227 while (true)

228 {

229 с = (char) stream.read();

230 if (isWhiteSpace (c))

231 {

232 continue;

233 }

234 break;

235 }

236

237 if (lisWhiteSpace(c) S& !isDelimeter(c))

238 {

239 key.append (с);

240 }

241

242 // Считываем ключ.

243 while (true)

244 {

245 с = (char) stream.read();

246 if (isWhiteSpace(c) II isDeliraeter(c))

247 {

248 break;

249 }

250 else

251 {

252 key.append(c);

253 }

254 }

255

256 // Пропускаем пробел, идущий перед или

257 // после символа.

258 while (true)

259 {

260 с = (char) stream.read();

261 if (isWhiteSpace(c) II isDelimeter(c))

262 {

263 continue;

264 }

265 value.append(c);

266 break;

267 }

268

269 // Считываем остальную часть значения метки.

270 while (true)

271 {

272 с = (char) stream.read();

273 if (c == '\n')

274 {

275 break;

276 }

277 else

278 {

279 value.append(c);

280 }

281 }

282 break;

283 }

284 }

285 catch (lOException ioe)

286 {

287 ioe.printStackTrace() ;

288 return -1;

289 }

290 return 0;

291 }

292

293 /**

294

295 */

296 private boolean isWhiteSpace(char c)

297 {

298 if (c == ' ' И с == '\t')

299 {

300 return true;

301 }

302 else

303 {

304 return false;

305 }

306 }

307

308 /**

309

310 */

311 private boolean isDelimeter(char c)

312 {

313 if (c == ':')

314 {

315 return true;

316 }

317 return false;

318 }

319

320 /**

321 Выдает значение, связанное с указанным

322 ключом из пакета ресурсов приложения.

323

324 @param key - ключ пары «ключ-значение».

325

326 @выдает значение, связанное с

327 указанным ключом.

328 */

329 private String getResource(String key)

330 {

331 if (resources == null)

332 {

333 return null;

334 }

335 return (String) resources .get (-key) ;

336 }

337

338 /**

339 Прекращает выполнение. Запрашивает реализацию

340 на завершение данного MID-лета.

341 */

342 public void quit()

343 {

344 notifyDestroyed ();



345 }

346

347 public void destroyApp(boolean destroy)

348 {

349

350 }

351

352 public void pauseApp()

353 {

354

355 }

356 }

Листинг 9.6. Класс HelloForm2 теперь использует API I18Nderao2.getResource() для извлечения локализованных ресурсов

1 import javax.microedition.midlet.MIDlet;

2

3 import javax.microedition.Icdui.Alert;

4 import javax.microedition.Icdui.AlertType;

5 import javax.microedition.Icdui.Command;

6 import javax.microedition.Icdui.CommandListener;

7 import javax.mi'croedition. Icdui .Display ;

8 import javax.microedition.Icdui.Displayable;

9 import javax.microedition.Icdui.Form;

10

11 /**

12 Данный класс определяет Form, которая отображает некоторый

13 простой текст и меню команд. Цель данного класса

14 продемонстрировать интернационализацию и локализацию

15 видимых пользователю атрибутов. Он работает с классом

16 I18NDemo2.

17 */

18 public class HelloForm2 extends Form

19 {

20 // Заголовок даннвй Form, устанавливаемый по умолчанию.

21 private static final String DEFAULTJTITLE =

22 "Hello, World";

23

24 // Блок прослушивания команд, который обрабатывает

25 // командные события в данной Form.

26 private MyCommandListener cl = new

27 MyCommandListener (1;

28

29 // Экземпляр дисплея, связанный с данным

30 // MID-летом.

31 Display display;

32

33 // Ссылка на связанный с данным объектом

34 // объект MID-лета.

35 IlSNDemo midlet;

36

37 // Уведомление, отображаемое в ответ на активацию

38 // некоторой из команд данной Form.

39 Alert alert;

40

41 private Command showAlert;

42 private Command sayHi;

43 private Command cancel;

44 private Command exit;

45 private Command help;

46 private Command item;

47 private Command ok;

48 private Command screen;

49 private Command stop;

50

51 /**

52 Конструктор No-arg. Устанавливает заголовок

53 по умолчанию данной формы.

54 */

55 HelloForm2()

56 {

57 this(DEFAULT_TITLE);

58 }

59

60 /**

61 Конструктор.

62

.63 @param title - Заголовок данной Form.



64 */

65 KelloForm2(String title)

66 {

67 super (title);

68

69 midlet = IlSNDemo.getlnstance();

70

71 // Добавляет строковый элемент в форму.

72 String msg = midlet.getResource("greeting");

73 append (msg);

74

75 display = Display.getDisplay(midlet);

76

77 // Добавляет MyCommandListener в Form для прослушивания

78 // события нажатия клавиши «Back», которое должно

79 // создавать всплывающее уведомление Alert.

80 setCommandLiscener (cl) ;

81

82 showAiert = new

83 Command(midlet.getResource("alert"),

84 Command.SCREEN, 1);

85 addCommand(showAlert);

86

87 sayHi = new

88 Command(midlet.getResource("sayhi") ,

89 Command.SCREEN, 1) ;

90 addCommand(sayHi);

91

92 cancel = new

93 Command(midlet.getResource("cancel"),

94 Command.SCREEN, 1);

95 addCommand(cancel);

96

97 exit = new

98 Command(midlet.getResource("exit"),

99 Command.SCREEN, 1);

100 addCommand(exit);

101

102 help = new

103 Command(midlet.getResource("help"),

104 Command.SCREEN, 1);

105 addCommand(help);

106

107 item = new

108 Command(midlet.getResource ("item"),

109 Command.SCREEN, 1);

110 addCommand(item);

111

112 ok = new

113 Command(midlet.getResource("ok"),

114 Command.SCREEN, 1) ;

115 addCommand(ok);

116

117 screen = new

118 Command(midlet.getResource("screen") ,

119 Command.SCREEN, 1);

120 addCommand(screen);

121

122 stop = new

123 Command(midlet.getResource("stop"),

124 Command.SCREEN, 1);

125 addCommand(stop);

126 }

127

128 // Данный класс просто прослушивает активацию

129 // какой-либо команды. Экземпляр HelloForm

130 // устанавливает экземпляр данного класса как

131 // свой блок прослушивания команд. Экземпляр

132 // объекта не проверяет информацию команды,

133 // а просто отображает модальное Alert, показывающее,

134 // что экранная клавиша была активирована пользователем.

135 public class MyCommandListener

136 реализует CommandListener



137 {

138 public void commandAction(Command c,

139 Displayable d)

140 {

141 String title =

142 midlet.getResource("alert_title") ;

143 String msg = midlet.getResource("alert_text");

144

145 if (с == showAlert)

146 {

147 alert = new Alert(title,

148 msg,

149 null, AlertType.INFO);

150 alert.setTimeout(Alert.FOREVER);

151 display.setCurrent(alert, HelloForm2.this);

152 }

153 else if (c == sayHi)

154 {

155 alert = new Alert(title,

156 msg,

157 null, AlertType.INFO);

158 alert.setTimeout(Alert.FOREVER);

159 display.setCurrent(alert, HelloForm2.this);

160 }

161

162 if (c == exit)

163 {

164 I18NDemo.getInstance-() .destroyApp (true) ;

165 }

166 }

167 }

168 }

Наиболее проблематичным аспектом данного подхода является то, что вы, разработчик, должны создать инфраструктуру, которая позволит вашим приложениям считывать и анализировать файлы ресурсов. Также приложение должно создавать структуры внутренних данных, которые содержат локализованные ресурсы, считанные из файлов. Самым проблематичным аспектом создания этой инфраструктуры является предоставление адекватной обработки потоков, особенно обработки потоков для поддержки считывания значений строковых атрибутов. Метод MIDlet.getAppProperty(), использовавшийся в предыдущей схеме, основанной на файле JAD, извлекает информацию об обработке потоков. Но в данной схеме вы должны проделать всю эту работу самостоятельно.

Метод Class.getResourceAsStream(String name) является единственным способом, с помощью которого MID-лет может считывать файл из JAR приложения. Параметр имени представляет собой имя файла без информации о пути. Этот метод выдает объект java.io.InputStream, который является байтовым потоком.

Вы должны преобразовать этот байтовый поток в символьный для того, чтобы считывать значения строковых атрибутов в вашей программе. Единственный практичный способ преобразовать байтовые потоки в символьные - это использовать класс java.io.InputStreamReader. Вы создаете экземпляр данного класса, пересылая ваш объект InputStream в конструктор InputStreamReader. В строках с 137 до 154 листинга 9.5 символьный поток срздает определяемый приложением метод loadResources ().



Чтобы преобразовывать из байтов в символы, вы должны знать символьную кодировку файла ресурса, который вы считываете. В листинге 9.5 происходит преобразование из кодировки ISO8859-1 (используемой файлом en_US.txt) в уникод. При считывании символьных данных в программу конечной кодировкой всегда является уникод. Java всегда представляет символы и строки внутренне с помощью уникода.

Первая форма конструктора InputStreamReader, показанная в таблице 9.1 и использованная в листинге 9.5, преобразует из символьной кодировки, устанавливаемой платформой по умолчанию, в уникод. Если ваш файл ресурсов использует кодировку, отличную от кодировки, используемой платформой по умолчанию, вы должны использовать второй конструктор InputStreamReader, который принимает аргумент, указывающий- кодировку потока, считываемого вами.

Важным решением проектировки интернационализации и локализации является выбор символьной кодировки ваших файлов ресурсов. Значения строковых атрибутов локализованы и должны быть кодированы с помощью символьной кодировки, которая поддерживает язык локализации. Ключи атрибутов, однако, не локализуются, и они могут быть написаны с помощью ASCII. Варианты выбора кодировки должны учитывать следующее:

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

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

Лучше всего использовать одну символьную кодировку для всего файла. В противном случае вам понадобится создать два символьных потока: один для анализа ключей атрибутов и второй для анализа значений. Подобная схема добавляет вашей обработке потоков ненужный уровень сложности.

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



Двумя практичными вариантами символьных кодировок являются последовательности переключения кодов UTF-8 и Unicode Java. UTF-8 - это код изменяющейся ширины, который поддерживает определения символьной кодировки ASCII. Он вмещает все символы всех языков. К несчастью, потоковые классы MIDP не имеют удобных методов, таких, как DatalnputStream.readUTFO J2SE, для считывания строк UTF. Итак, вам все равно придется выполнять анализ потока собственноручно. Другая сложность заключается в том, что теперь вам придется записывать ваши файлы ресурса в формате UTF-8. Поэтому вам нужны текстовые редакторы и другие инструменты, которые поддерживают создание файлов, кодированных в UTF-8.

Простейшее решение - использовать последовательности переключения кода Unicode Java для кодирования значений строковых атрибутов. Каждая последовательность переключения кода представляет собой уникальный символ уникода. У этого подхода есть два преимущества. Во-первых, вы можете записывать эти последовательности в файл как символы ASCII. Во-вторых, уникод поддерживает все языки. В листинге 9.4 используется ISO8859-1. Он адекватен для франкоязычных ресурсов, но в то же время он может не поддерживать корейский язык, например, тогда как уникод поддерживает. Вы можете использовать некоторые другие многобайтные кодировки, но затем вам придется положиться на редакторы методов ввода и другие инструменты для считывания и записи файла ресурса в этой кодировке.

Если вы используете другие многобайтные кодировки, вам придется учитывать проблемы совместимости и возможность отладки. Есть ли у вас инструменты - текстовые редакторы, редакторы методов ввода и так далее - для поддержки всех ваших региональных настроек? Есть ли у вас те же инструменты, что и у вашей команды по локализации или, по крайней мере, совместимые с ними? Кто будет следить за правильностью работы вашего приложения? Есть ли у них те же инструменты, что и у остальных? Все эти аспекты надо рассматривать при выборе метода кодировки.

Когда вы создали ваш объект InputStreamReader, который является символьно ориентированным потоком, вы можете извлекать символы из него с помощью методов read(). Они происходят из класса, стоящего над ними, java.io.Reader. Эти методы выдают char уникода. В таблице 9.1 перечислены методы класса Reader.



Таблица 9.1. Конструкторы и методы java.io.Reader

Название Конструктора и метода java.io. Reader Элемент Описание
InputStreamReader (InputStream is) Конструктор Создает поток, который преобразует из кодировки, устанавливаемой платформой по умолчанию, в уникод
InputStreamReader (InputStream is, String enc) Конструктор Преобразует из указанной кодировки в уникод
void closed Метод Закрывает поток
void mark(int readAheadLimit) Метод Устанавливает лимит опережающего считывания для метки
boolean markSupported() Метод Показывает, поддерживает ли данный поток разметку
int read() Метод Считывает один символ
int read (char [] cbuf, int off, int len) Метод Считывает количество «len» символов в части символьного массива, начиная от указанной сдвига
boolean ready () Метод Показывает, должно ли здесь считываться что-либо
void reset () Метод Переустанавливает поток в последнюю позицию метки
long skip (long n) Метод Пропускает указанное число символов
Единственной пропущенной задачей является анализ символов, которые вы считываете. К сожалению, MIDP не имеет удобных классов, таких, как класс StringTokenizer J2SE, который облегчает расстановку меток. Вы должны поэтому самостоятельно проанализировать локализованные ресурсы, один символ за раз, с помощью любой из двух форм перегрузки Reader.read(). Если вы использовали такой формат файла, как в листинге 9.4, самое меньшее, что вы должны сделать затем, это отделить поле ключа от поля значения для каждого атрибута, убрать пробелы и так далее. В листинге 9.5 весь код в строках 127-318 посвящен обработке потока.

Одним из существенных недостатков данного проектирования интернационализации является дополнительное кодирование, необходимое для создания.анализаторов потоков. Кроме того, что это включает дополнительную работу по разработке данного кода, ваше приложение также ограничивается средой исполнения. Файл ввода/вывода может отнять много рабочих ресурсов и выдать при этом только минимально приемлемую производительность. Это важно рассматривать при разработке приложений MIDP.

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

За исключением этих недостатков этот второй подход в значительной степени сходен с первым, который использует файл JAD для хранения локализованных ресурсов. Как и подход с файлом JAD, этот подход может обрабатывать нестроковые ресурсы, определяя атрибуты, чьи значения являются именами чувствительных к региональным настройкам классов.


Экранная навигация


Пример HelloWorld2 демонстрирует центральную абстракцию MIDP - Screen. Вы, несомненно, уже обратили внимание, что.можете отображать одновременно только один Displayable - один Screen. Когда приложению необходимо отобразить Alert, ему приходится заменять основной экран.

Причина этой одноэкранной абстракции кроется в ограниченности экранных ресурсов устройства. В отличие от инструментов графических пользовательских интерфейсов для настольных компьютеров, таких, как J2SE Swing toolkit, вы не можете иметь большое количество накладываемых друг на друга окон, всплывающих уведомлений, диалоговых окон и так далее. Хотя внедрение этих абстракций и не невозможно, требования памяти и ЦП на современных мобильных устройствах таковы, что фактически запрещают это. Более того, жидкокристаллические дисплеи с низким разрешением, низким потреблением энергии и маленькой площадью, характерные для большинства мобильных устройств, не приспособлены для этих абстракций. Даже дисплеи «карманных компьютеров» имеют минимально приемлемые характеристики для наложения окон, всплывающих окон и тому подобного. Однако очень вероятно, что примерно через год эти устройства будут иметь значительно большие мощности, порядка 50 МГц и 32 Мб RAM.

Существует простая идиома экранной навигации, связанная с этой абстракцией экранов дисплея. Если вы хотите отобразить новый экран, просто установите, что экран является отображаемым в настоящий момент (current displayable). Чтобы сделать это, вы запрашиваете.объект Display вашего MID-лета на отображение Screen. Вспомните, в главе 2 вы узнали, что каждому MID-лету при запуске присваивается уникальный объект Display реализацией MIDP. Вы никогда не создаете объект Display, но вы можете получить ссылку на него, сделав следующий вызов со ссылкой на ваш MID-лет как на аргумент: Display.getDisplay(midlet);

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

display.setCurrent(nextDisplayable)


Вы можете найти эти две строчки кода в методе startAppf) обеих версий приложения HelloWorld.

Разработка навигации и перемещений в вашем МЮ-лете включает следующие этапы:

Создание экранов.

Создание команд, которые вам нужны для каждого экрана.

Присвоение команд экранам.

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

Важным атрибутом успешных приложений MIDP является легкая, интуитивная навигация между окнами. Анализ задач пользователя является темой отдельной книги и лежит за пределами темы данной книги. Самым важным, однако, является умение думать с точки зрения пользователя. Делайте все простым. Не путайте ваших пользователей, прыгая с места на место, так что пользователь не сможет следовать навигации. Слишком легко пользователю потеряться при просмотре на маленьком экране без контекстной привязки ко всему приложению. И никогда не создавайте ваши экраны, приспосабливая их к внутренней организации вашего приложения, его структуре данных, классам и так далее. Наоборот, позвольте дизайну вашего приложения следовать расположению, дизайну, навигации, последовательности экранов и так далее.


Экраны и экранные элементы


Первый пример в этой главе показывает вам основную разницу между двумя типами компонентов пользовательского интерфейса MIDP: компонентами Displayable и компонентами Item. Иерархия наследования, изображенная на рисунке 5.1, ясно отображает Эти две категории. Иерархия Displayable заключает в себе экраны, которые вы отображаете. Иерархия Item классифицирует элементы, которые могут быть собраны в один экран. Следующие примеры демонстрируют использование различных компонентов пользовательского интерфейса MIDP. Мы объясняем их использование по мере ознакомления с каждым.

В листинге 5.1 показан файл под названием UIComponentDemo.java, который определяет исходный код новой программы, демонстрирующий использование элементов MIDP. Этот файл использует код в других файлах, которые вместе составляют полную демонстрационную программу компонента пользовательского интерфейса.

Листинг 5.1. Исходный код UlComponentDemo

import javax.raicroedition.midlet.MIDlet;

import javax.microedition.lcdui.Choice;

import javax.microedition.lcdui.Command;

import javax.microedition.lcdui .CommandListener;

import javax.microedition.lcdui.Display;

import javax.microedition.lcdui.Displayable;

import javax.microedition.Icdui.List;

/**

Демонстрируется использование высокоуровневых компонентов

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

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

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

демонстрационных программ. MID-лет создает экземпляр класса,

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

и затем выполняемого им.

*/

public class UlComponentDemo extends MIDlet

implements CommandListener

private Command exit = new Command("Exit", Command.EXIT, 1);

// Имена различных демонстрационных программ: Элементы в этом списке

// являются именами первичных .классов для каждой демонстрационной

// программы, private static String [] demos =

"AlertDemo",


"DateFieldDemo",

"GaugeDemo",

"StringltemDemo", "TickerDemo",

"ImageltemDemo"

}i;

private static UIComponentDemo instance = null;

// Реальный компонент List, который отображает элементы,

// перечисленные в списке «demos» выше.

private List mainMenu = new List ("Select demo", Choice.IMPLICIT,

demos, null) ;

// Конструктор No-arg. public UIComponentDemo()

// Обратите внимание на вызов super(). Он выполняет

// конструктор no-arg в классе MID-лета. super () ;

instance = this;

}

/**

Возвращает один экземпляр этого класса. Вызов этого метода перед

созданием объекта возвратит пустой указатель.

^возвращает экземпляр этого класса.

*/

public static UIComponentDemo getlnstance()

{

return instance;

{

public void startApp()

{

Display display;

mainMenu.addCommand(exit);

mainMenu.setCommandListener(this) ;

*

display = Display.getDisplay(this);

display.setCurrent(mainMenu) ;

public void pauseAppf)

{

}

void quit() ,;

destroyApp(true); notifyDestroyed();

)

public void destroyApp(boolean destroy)

(

}

public void display!)

}

Display.getDisplay(this).setCurrent(mainMenu);

}

public void commandAction(Command c, Displayabie d)

{

Displayabie displayable = null;

if (c == List.SELECT_COMMAND)

{

int index = mainKenu.getSeiectedlndex(); try

{

displayable = (Displayable)

Class.forName(demos[index]).new!nstance();

if (displayable == null)

}

return;

}

Display display = Display.getDisplay(this);

display.setCurrent(displayable);

}

catch (Exception e)

{

System.out.println("Got exception here!!!");

e.printStackTrace() ;

return;

}

}

else if (c == exit) 1 quit() ;

}

}

}

Код, описанный в листинге 5.1, является первым примером, потому что он создается с использованием одного вида экранного объекта. Экран является ядром организационной основы всех MID-летов.

В листинге 5.1 определяется MID-лет. Его высокоуровневый экран является компонентом List, который отображает список вариантов, отражающих различные элементы, которые демонстрирует программа. На рисунке 5.2 показан список верхнего уровня демонстрационных приложений, которые вы можете запустить. Этот основной экран является экземпляром List.



Обратите внимание на стрелку на экране, указывающую вниз. Она указывает, что существуют еще элементы, для которых недостаточно места на экране. Если вы прокрутите немного вниз, стрелка, указывающая вниз, исчезнет, и вместо этого появится стрелка, указывающая наверх. Эти стрелки размещены на экране реализацией компонента List.

List является видом Screen, который, конечно, является Displayable, и приспосабливается к знакомой ему общей структуре приложения. Вы можете видеть в листинге 5.1, что экземпляр List является отображаемым в настоящее время, по существу это объект, который получает события вызова команды. Сам MID-лет является блоком прослушивания таких событий, регистрируя себя как CommandListener для этих событий. Он реализует интерфейс CommandListener, а также определяет метод commandAction () .

Альтернативным способом создания блоков прослушивания является создание самого компонента блока прослушивания событий, которые в нем происходят. Чтобы выполнить это, однако, вам бы пришлось создать в классе компонента подклассы, в данном случае создав подкласс класса List. Я выбрал первый подход и использую стандартный класс List без создания подклассов.

На рисунке 5.2 изображен список демонстрационных программ компонентов пользовательского интерфейса. Имена, которые вы видите, являются именами основных классов для каждой демонстрационной программы. При выборе одного из них выполняется соответствующая демонстрационная программа. Конечно, вы должны откомпилировать демонстрационные программы прежде, чем пытаться их запустить. Иначе вы получите ошибку ClassNotFoundException.

Если вы используете J2ME Wireless Toolkit, вам нужно только поместить ваши исходные файлы в директорию проекта UIComponents/src/. Затем создайте проект. Wireless Toolkit откомпилирует все исходные файлы в директории sic/. Он запустит верификатор предварительной проверки и, наконец, разместит файлы .class в директории проекта classes/. С этого момента вы можете выполнять демонстрационные программы, перечисленные в основном окне MID-лета.



В следующем примере я сначала компилирую, а затем делаю доступной программу AlertDemo, первый элемент в списке. Чтобы запустить откомпилированную демонстрационную программу, просто выберите AlertDemo из списка, показанного на рисунке 5.2. Повторяйте эти шаги создания и выполнения для каждой из остальных демонстрационных программ.

На рисунке 5.3 показан экран, который появляется, когда вы выбираете элемент AlertDemo из списка демонстрационных программ верхнего уровня. Этот экран отображает другой набор элементов - набор типов уведомлений - с помощью другого компонента MIDP, называемого ChoiceGroup. Экран, содержащий типы уведомлений, создается кодом в файле AlertDemo.java, показанном в листинге 5.2. Выбор одного из элементов на этом экране создает и отображает экземпляр этого типа компонента Alert.

Иерархия наследования, изображенная на рисунке 5.1, показывает, что ChoiceGroup не является ни Screen, ни Displayable. Это вид Item. Вспомните из главы 3, что Item является компонентом, который может быть агрегирован в Form. Обратите внимание, что класс AlertDemo дополняет Form, который дает ему возможность агрегировать элементы TextField и ChoiceGroup.

На рисунке 5.3 вы видите Form - экземпляр AlertDemo - который содержит объекты ChoiceGroup и TextField. Вспомните, что Form является единственным компонентом MIDP, который может включать другие компоненты. Таким образом, программа AlertDemo должна использовать Form для хранения элементов ChoiceGroup и TextField.



Рисунок 5.2. Изображение главного экрана UIComponentDemo. Элементы являются названиями основных классов для каждой демонстрационной программы



Рисунок 5.3. Главный экран демонстрационной программы уведомлений является формой, которая объединяетTextField и ChoiceGroup

Листинг 5.2. Уведомления являются экранами, но они не могут содержать объекты Command. Вы должны указать Displayable, который должен быть показан, когда уведомление будет недоступно

import javax.microedition.lcdui.Alert;

import javax.microedition.lcdui.Choice;



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.Icdui.TextField;

/**

Демонстрирует использование объектов Alert.

*/

public class AlertDemo extends Form implements CommandListener

{

private Command go = new Command("Go", Command.SCREEN, 1);

private Command back = new Command ("Back", Command.BACK, 1);

private ChoiceGroup type; private TextField tPref;

private String [] elements =

{

"Alarm", "Confirmation", "Error", "Information", "Warning" );

// Это необходимо/ чтобы другие экраны могли ссылаться

// на экземпляр этого класса, private static Displayable instance;

/**

Конструктор.

*/

public AlertDemo()

{

'super ("Build alert");

type = buildAlertTypeSelection ();

tPref = buildTimeoutPrefPrompt();

append(type); appendftPref) ;

addCommand(go); addCommand(back);

setCommandListener(this) ; instance = this;

}

/**

Возвращает единственный экземпляр этого класса.

Вызов этого метода перед созданием объекта возвращает Пустой указатель.

@возвращает экземпляр этого класса.

*/

static Displayable getlnstance ()

{

return instance;

}

private ChoiceGroup buildAlertTypeSelection ()

{

// He работает, если это Choice.IMPLICIT. Смотри документацию Choice.

// Тип IMPLICIT действителен только для объектов List,

return new ChoiceGroup ("Alert Type", Choice.EXCLUSIVE, elements, null);

}

private TextField buildTimeo-utPref Prompt ()

}

String MAX_TIMEOUT_VALUE = "5"; int MAX_SIZE = 8;

return new TextField("Timeout (sec.)", MAX_TIMEOUT_VALUE,

MAX_SIZE, TextField.NUMERIC);

}

public void comraandAction(Command c, Displayable d)

{

UIComponentDemo demo = UIComponentDemo.getlnstance();

Display display = Display.getDisplay(demo); int timeSec; int timeMillis;



if (c == go)

// Уведомления не принимают определенные приложением команды.

String title = elements[type.getSelectedlndex()]; 1;

Alert alert = new Alert (title) ;

alert.setString("A '" + title + "' alert"); timeSec = Integer . parselnt(tPref.getString());

timeMillis = timeSec * 1000; if (timeMillis <= 0)

(

timeMillis = Alert.FOREVER;

}

alert.setTimeout(timeMillis);

display.setCurrent(alert, AlertDemo.getlnstance());

}

if (c == back)

(

UIComponentDemo.getlnstance().display ();

}

)

}

Когда вы будете экспериментировать с этим приложением, обратите внимание, что вы можете прокрутить List вверх и вниз, выделяя различные элементы List, но программного выбора событий не осуществляется. Подобным образом на экране Build Alert (Создание уведомления) вы можете прокручивать и многократно выбирать элементы ChoiceGroup без активации какого-либо действия.

В обоих случаях событий не генерируется, пока вы не вызовете активацию команды. На экране List вы должны нажать на кнопку выбора Select, чтобы перейти к экрану Build Alert (Создать уведомление). Когда вы окажетесь на экране Build Alert (Создать уведомление), вы должны выбрать экранную кнопку Go, чтобы просмотреть отображенный Alert. Изменение выбора в любой реализации Choice не активирует какую-либо Command в компоненте.

Оба экрана, изображенные на рисунках 5.2 и 5.3, показывают наборы элементов, из которых пользователь может сделать выбор. Оба компонента List и ChoiceGroup реализуют интерфейс javax.microedition.ldcui.Choice, который указывает характеристики поведения компонентов, поддерживающих выбор одного или более своих элементов. Интерфейс Choice определяет три константы:

IMPLICIT (Неявный): выбирается элемент, на котором в настоящее время сфокусировано внимание.

EXCLUSIVE (Исключающий): может быть выбран один-единственный элемент.

MULTIPLE (Множественный): могут быть выбраны несколько элементов.

Только объекты List могут устанавливать активацию IMPLICIT. Когда вы активизируете клавишу выбора Select устройства при неявном List, какой бы элемент List и был бы выделен в данный момент, он будет выбран. Листинг 5.1 демонстрирует эту неявную команду. ChoiceGroup не может быть неявным. Конструктор ChoiceGroup сбрасывает IllegalArgumentException, если вы пытаетесь создать его экземпляр с типом Choice.IMPLICIT.



Есть еще один тип информации, которая может быть собрана из этого неявного List. Ранее я говорил, что событие команды посылается в Displayable в ответ на нажатие пользователем кнопки Select на устройстве. Тип этой команды, однако, отличается от любого из типов, которые определяет класс Command.

Класс List определяет особый объект Command, List.SELECT_COMMAND. Активация списка IMPLICIT генерирует эту особую команду и посылает ее в блок прослушивания команд без какой-либо явной операции выбора, осуществляемой пользователем. Истинная цель этой команды заключается в том, чтобы дать возможность методу блока прослушивания commandAction() распознать активацию операции выбора устройства. В листинге 5.3 показано, как метод UIComponentDemo.commandAction() использует эту специальную константу.

Листинг 5.3. Блок прослушивания команд должен проверять активацию специальной команды List.SELECT_COMMAND, если приложение использует неявные списки

public .class UIComponentDemo extends MIDlet .

implements CommandListener

{

public void cornrnandAction (Command c, Displayable d)

{

Displayable displayable = null;

if (c == List.SELECT_COMMAND)

}

int index = mainMenu.getSelectedlndex ();

try i displayable = (Displayable)

Class.forName(demos[index]).new Instance));

Display display = Display.getDisplay(this);

display.setCurrent(displayable);

}

,catch (Exception e)

}

e.printStackTrace(); return;

}

}

else

{

return;

}

}

Названия типов выбора EXCLUSIVE и MULTIPLE говорят сами за себя. Реализации MIDP , визуализируют значки отображения выбора различно для исключающего и множественного списков. Исключающие списки выбора имеют кружок слева от текста элемента, сходный с селективными кнопками в приложениях AWT и Swing, и внутреннюю точку, показывающую выбранный элемент. Множественные списки выбора визуализируют компонент с квадратиком слева от текста элемента, сходным с кнопками для отметки в приложениях AWT и Swing.

Вы уже видели пример Alert в главе 3, здесь вы видите его вновь. Метод commandAction() класса AlertDemo создает пять различных уведомлений в зависимости от данных, которые пользователь вводит в экран Build Alert (Создание уведомления), показанный на рисунке 5.3. Конструктор класса Alert принимает значение AlertType, которое отражает тип уведомления, изменяющийся в зависимости от создания. Класс AlertType определяет пять констант, которые представляют собой возможные типы уведомлений, показанные в таблице 5.2.



Таблица 5.2. Константы класса AlertType, которые представляют собой возможные типы объектов Alert

Константа класса AlertType Описание
ALARM (внимание) Уведомление, которое отображает появление аварийного события
CONFIRMATION (подтверждение] Диалоговое окно, которое запрашивает у пользователя подтверждение действия
ERROR (ошибка) Диалоговое окно, которое уведомляет пользователя об ошибке
INFO (инфо) Диалоговое окно, в котором присутствует информационное сообщение для пользователя
WARNING (предупреждение) Диалоговое окно, которое показывает предупреждение
Внешний вид уведомления не изменяется в зависимости от типа уведомления. Цель типов уведомлений заключается в том, чтобы дать программисту возможность более легко различать различные объекты уведомлений. Это также дает реализации возможность отображать отдельные типы уведомлений по-разному.

Тип уведомления не влияет на его поведение. Вы видели сходную организацию объектов Command в приложениях HelloWorld. Простое присвоение определенного типа Command не изменяет его поведение никоим образом. Выбор остается за вами как за программистом в создании последовательности тем способом, которым вы обращались со сходными типами объектов Command и Alert.

Если вы запустите программу, приведенную в примере, вы увидите, что экраны уведомлений не имеют команд, связанных с ними, на самом деле они и не могут их иметь. Вы также заметите, что экраны уведомлений исчезают через 5 секунд и возвращают экран Build Alert (Создание уведомления). Причина этого кроется в том, что программа установила по умолчанию 5-секундную длительность для всех уведомлений.

Величина длительности появления уведомления должна быть больше 0. Установление значения менее 0 приведет к IllegalArgumentException. Вы устанавливаете время истечения уведомления с помощью метода Alert.setTimeout(). Если вы укажете константу Alert.FOREVER, реализация поместит экранную клавишу Done (Готово) на уведомление. Уведомление будет оставаться открытым до тех пор, пока пользователь не нажмет Done (Готово).



В демонстрационной программе (которую вы можете найти в Web на сайте http://www.phptr.com), прокрутите вниз экран Build Alert (Создание уведомления) и вы увидите объект текстового поля, который содержит строку «5». Вы можете отредактировать этот объект TextField, который является другим компонентом пользовательского интерфейса, чтобы изменить значение времени истечения. Если вы укажете 0, приложение создаст уведомление с FOREVER (НИКОГДА) в качестве времени истечения.

TextField является последним новым компонентом, который вводит эта демонстрационная программа. TextField также является разновидностью Item, как показано на рисунке 5.1. TextField - это один из двух компонентов для ввода текста. Другим является TextBox, который мы обсудим далее в этой главе. Компоненты для ввода текста используют понятие ограничения ввода, которое ограничивает ввод до определенного набора разрешенных знаков, определяемого действующими ограничениями компонента.

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

Таблица 5.3. Типы ограничений, устанавливаемые классом TextField

Константа ограничения Описание
ANY Любые буквенно-цифровые знаки
EMAILADDR Только синтаксически правильный e-mail
NUMERIC Только цифровые знаки
PASSWORD Знаки не отображаются на дисплее
PHONENUMBER Только цифровые знаки, реализация предоставляет задание формата
URL Только синтаксически правильный LJRL
Вы определяете ограничения в конструкторе, чтобы создать экземпляр с желаемыми текстовыми атрибутами. Чтобы создавать экземпляры, которые поддерживают обработку комбинации текстовых категорий, перечисленных в таблице 5.3, укажите логический AND этих категорий. Вы можете определить установленные ограничения, исследовав поле CONSTRAINT_MASK объекта TextField.


Kaк рисуются компоненты


Вы, возможно, заметили, что метод toggleTranslation() в листинге 6.8 вызывает Canvas.repaint (). Этот вызов требует, чтобы реализация перерисовывала дисплей.

Вызов Canvas.repaint() выражается в событии внутренней реализации, представляя запрос обновления. Реализация обрабатывает событие внутренне. Она назначает вызов метода paint () Canvas, который выполняется реализацией, а не вашей программой.

Canvas должен быть закрашен для визуализации всех элементов, изображенных в его контексте, или для перерисовки поврежденных пикселей. Однако вы никогда не должны вызывать paint () прямо. Если вы желаете перерисовать ваш Canvas, вы должны создать вызов repaint (). Или вы можете вызвать следующую версию перегрузки, которая также определяется в классе Canvas:

void repaint(int x, int у, int width, int height)

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

Обратите внимание, что вы все равно должны перерисовать поврежденные пиксели, прежде чем создавать- вызов на перерисовку Canvas. Это требование отличается от требований приложений, написанных в AWT или Swing. В AWT и Swing вызов repaint() выполняет две операции: он сначала вызывает update(), а затем - paint (Graphics g). Вызов update () приводит к тому, что реализация стирает Panel, Canvas или JComponent. Такого вызова в МГОР нет, так что вы должны перерисовать поврежденные пиксели сами. Обратите внимание, что в листинге 6.6 метод paint (Graphics g) все равно вызывает метод paintClipRect(Graphics g).



Класс Graphics


Класс Graphics поддерживает следующие абстракции:

рисование и заливка двухмерных геометрических фигур;

выбор цветов для графической ручки;

выбор шрифтов для рисования текста;

отсечение областей для рисования (clipping);

перенос координатной системы Graphics.

Устройства различаются в своей поддержке атрибутов, таких, как цвет. Поэтому класс Display предоставляет методы:

public int isColorO

public int numColors()

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

Первостепенной абстракцией, определяемой классом Graphics, является представление о Canvas, как о двухмерной сетке точек или пикселей. На рисунке 6.3 представлено схематичное изображение этой области для рисования. Графический контекст определяет эту координатную плоскость (х, у), в которой координаты лежат между пикселями, практически так же, как и курсор вашего любимого текстового редактора всегда лежит между двумя символами.

Рисунок 6.3. Класс Graphics представляет дисплей как двухмерную сетку пикселей



Классы и интерфейсы cтpyктypы общих соединений


Пакет javax.microedition.io определяет один класс и набор интерфейсов, которые представляют различные типы содержимого соединений. Класс Connector является единственным конкретным элементом в структуре общих соединений. Вы должны использовать его для получения текущих соединений с ресурсами. Он действительно содержит фабричный метод, который создает различные типы структур соединений для поддержки различных протоколов.

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

На рисунке 8.2 показана иерархия наследования интерфейсов MIDP, которые являются частью общей структуры соединений.

Рисунок 8.2. Каждый из типов соединений поддерживает определенный уровень абстракции, который отражается в каждом интерфейсе с помощью методов. Возможности увеличиваются, а абстрактность уменьшается по мере того, как вы двигаетесь вниз по иерархии. Все интерфейсы находятся в пакете javax.microedition.io

На самом верху иерархии находится интерфейс Connection. Как предполагает его название, он представляет наиболее общий, абстрактный тип соединения. Естественно, все остальные типы соединений происходят из него. Интерфейс Connection содержит только один-единственный метод

public void close ()

Как вы знаете, соединение будет уже открыто при его создании классом Connector, поэтому в интерфейсе нет метода ореn(). При завершении соединения, однако, приложение должно закрыть его.

Прямые подинтерфейсы Connection представляют немного менее абстрактные типы соединений. По мере того как вы спускаетесь вниз с верхнего уровня иерархии соединений, интерфейс получает все большие возможности. Интерфейс InputConnection представляет поток данных соединения как InputStream, то есть поток данных с байтовой организацией. В таблице 8.1 показаны два его метода.

Таблица 8.1. Методы интерфейса InputConnection


Имя метода InputConnection Описание
DatalnputStream openDatalnputStream ( ) Открывает и возвращает DatalnputStream, который соединяется с сетевым ресурсом, связанным с этим соединением
InputStream openlnputStreamf) Открывает и возвращает InputStream, который соединяется с сетевым ресурсом, связанным с данным соединением
Эти методы возвращают типы объектов InputStream. Вспомните, что DatalnputStream является подклассом InputStream. Смысл заключается в том, что вы можете получить потоки, способствующие преобразованию данных в байтовые данные. Если вы желаете интерпретировать данные другим способом, ваша задача - создать подходящее «преобразование», которое позволит вам получать доступ и интерпретировать данные желаемым образом.

Интерфейс OutputConnection является еще одним подинтерфейсом Connection. Он работает с исходящими потоками и также определяет содержимое своих потоков как байтовые данные. Его методы показаны в таблице 8.2. Вы должны использовать этот интерфейс при записи байтовых данных в удаленный ресурс.

С помощью этих двух интерфейсов вы можете затем интерпретировать входящий или выходящий поток данных ресурса как последовательность необработанных байтов, анализируя их с помощью методов интерфейсов Datalnput или DataOutput. Конечно, вы должны знать формат данных, посылаемых устройством, или формат, ожидаемый устройством, соответственно. Другими словами, не существует абстракции данных, которая устраняет необходимость знать синтаксис и семантику данных в InputConnection или OutputConnection.

Таблица 8.2. Методы интерфейса OutputConnection

Имя метода OutputConnection Описание
DataOutputStream openDataOutputStream () Открывает и возвращает DataOutputStream, который соединяется с сетевым ресурсом, связанным с этим соединением.
OutputStream openOutputStream() Открывает и возвращает OutputStream, который соединяется с сетевым ресурсом, связанным с этим соединением.

Клавишные события


Класс Canvas 1 подменяет метод keyReleased() в Canvas. Поскольку объект регистрируется как блок прослушивания событий, он получает клавишные события в ответ на действия пользователя, связанные с клавиатурой.

Нажатие на любую клавишу клавишной панели приводит к формированию двух клавишных событий: событие нажатия клавиши и событие отпускания клавиши. Эта программа выводит информацию о событиях отпускания клавиши. Информация о клавишном событии включает название клавиши, код клавиши и, возможно, связанное с ним обозначение игрового действия.

Название клавиши является String, которая представляет собой удобное для чтения представление клавиши, обычно сходное (если не такое же) с текстом, физически напечатанным на клавише устройства. Код клавиши является целым числом, чье значение однозначно представляет каждую клавишу. Для стандартных клавиш ITU-T, а именно от 0 до 9, * и #, код клавиши является значением уникода символа.

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

Таблица 6.2. Константы класса Canvas, представляющие клавиши ITU-T

Константа класса Canvas Описание
public static final int KEY NUMO Представляет клавишу 0 клавишной панели
public static final int KEY NUM1 Представляет клавишу 1 клавишной панели
public static final int KEY NUM2 Представляет клавишу 2 клавишной панели
public static final int KEY_NUM3 Представляет клавишу 3 клавишной панели
public static final int KEY NUM4 Представляет клавишу 4 клавишной панели
public static final int KEY NUM5 Представляет клавишу 5 клавишной панели
public static final int KEY_NUM6 Представляет клавишу 6 клавишной панели
public static final int KEY NUM7 Представляет клавишу 7 клавишной панели
public static final int KEY_NUM8 Представляет клавишу В клавишной панели
public static final int KEY NUM9 Представляет клавишу В клавишной панели
public static final int KEY POUND Представляет клавишу * клавишной панели
public static final int KEY STAR Представляет клавишу # клавишной панели
<
Для нестандартных ( зависящих от устройства) клавишей, таких, как кнопки Up (Вверх), Down (Вниз), Left (Влево), Right (Вправо) и Select (Выбор) на мобильных устройствах, код клавиши является значением, зависящим от реализации, и должен быть отрицательным в соответствии со спецификацией MIDP. Опять же, однако, вы должны использовать предопределенные константы, показанные в таблице 6.3, и не думать о настоящем целом значении.

Таблица 6.3. Константы класса Canvas, представляющие игровые действия, отображаемые на клавишах мобильного устройства

Константа класса Canvas Описание
public static final int UP Представляет клавишу панели со стрелкой вверх
public static final int DOWN Представляет клавишу панели со стрелкой вниз
public static final int LEFT public static final int RIGHT Представляет клавишу панели со стрелкой влево Представляет клавишу панели со стрелкой вправо
public static final int FIRE Представляет клавишу панели со стрелкой запуска (выбора] на мобильных устройствах

Компараторы записей


Вы, несомненно, заметили, что второй аргумент, пересланный в enumerateRecords () в предыдущих примерах, был равен нулю. Этот второй параметр является «заполнителем» для компаратора записей. Компаратор записей - это объект, который сравнивает две записи для определения их упорядочивания или сортировки. Компараторы предоставляют приложениям возможность выполнять различную сортировку.

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

int ccmparefbyte [] recordl, byte [] record2)

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

Таблица 7.1. Константы RecordComparator

Константа Описание
public static int EQUIVALENT Две записи эквивалентны в соответствии с семантикой сравнения
public static int FOLLOWS Запись 1 «больше», чем запись 2, в соответствии с семантикой сравнения
public static int PRECEDES Запись 1 «меньше», чем запись 2, в соответствии с семантикой сравнения

Идея использования компараторов сходна с понятием фильтрации записей. Вы определяете класс, который реализует интерфейс javax.microedition.rras.RecordComparator. Вы передаете его экземпляр в вызов enumerateRecords (). Записи, извлеченные из хранилища записей, сравниваются друг с другом, по две одновременно, а затем сортируются в соответствии с результатами сравнения. Вы можете таким образом извлекать записи из списка в порядке, определяемом компаратором.

В листинге 7.4 демонстрируется использование компаратора записей. Он определяет новый внутренний класс класса AddressBook, который вы видели в листинге 7.1. Новый внутренний класс AlphabeticalOrdering реализует RecordComparator. Его метод сравнения извлекает поле имени из каждого параметра байтового массива и сравнивает их лексикографически (по словам).

Листинг 7.4. Этот компаратор записей определяет семантику упорядочивания записей, базируясь на лексикографической сортировке значений их полей имени


/*'*

Этот внутренний класс реализует RecordComparator,

чья политика заключается в выполнении сортировки по алфавиту.

*/

class AlphabeticalOrdering implements RecordComparator

/**

Конструктор No-arg.

*/

public AlphabeticalOrdering()

}

super(); )

public int comparelbyte [] reel, byte [] rec2)

ByteArraylnputStream baisl =

new ByteArraylnputStream(reel);

DatalnputStream disl = new DatalnputStream (baisl);

ByteArraylnputStream bais2 -

new ByteArraylnputStream(rec2);

DatalnputStream dis2 = new DatalnputStream(bais2);

String namel = null;

String name2 = null; try

(

namel = disl.readUTF ();

name2 = dis2.readUTF () ;

catch (lOExceotion ioe)

ioe.pnntStackTrace () ;

}

if (namel == null I| name2 == null) return 0;

int result = namel.compareTo(narae2);

if (result < 0)

return RecordComparater.PRECEDES;

else if (result == 0)

return RecordComparator.EQUIVALENT;

else

return RecordComparator.FOLLOWS;

}

}

Ваша адресная книга может использовать этот новый компаратор для лексикографической сортировки списка имен, извлеченных из хранилища записей. Например, чтобы отсортировать имена, выведенные поиском, вы просто создаете экземпляр вашего нового компаратора и пересылаете его как второй аргумент в вызов enumerateRecords (). Следующий фрагмент кода, показанный в листинге 7.5, является новой версией вызова метода getMatchesByName(String matchKey) в классе AddressBook.

Листинг 7.5. Чтобы осуществить сортировку, просто перешлите экземпляр компаратора в вызов списка записей из хранилища записей. Различные списки могут определять различную политику сортировки

RecordEnumeration getMatchesByName(String matchKey)

throws RecordStoreNotOpenException

{

MatchAllNaraesFilter filter =

new MatchAHNamesFilter (matchKey) ;

AlphabeticalOrdering comparator =

new AlphabeticalOrdering();

return recordStore.enumerateRecords(filter,

comparator, false) ;

}

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


Конфигурации и профили


Конфигурация включает три базовых элемента:

набор свойств языка программирования Java;

набор свойств виртуальной машины Java;

набор поддерживаемых библиотек Java и программных интерфейсов приложения (API).

Создатели J2ME определили только две конфигурации для избежания фрагментированного представления несовместимых платформ. Две конфигурации, которые существуют в настоящее время, представляют две категории портативных устройств, описанных ранее в этой главе, а именно:

личные, не стационарно подключаемые мобильные устройства - поддерживаемые конфигурацией Connected, Limited Device Configuration (CLDC, конфигурация для подключенных ограниченных устройств);

постоянно соединенные сетевые устройства - поддерживаемые конфигурацией Connected Device Configuration (CDC, конфигурация для подключенных устройств).

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

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



Конфигурация Connected Device Configuration (CDC)


Конфигурация Connected Device Configuration (CDC) предназначена лишь для фиксирования основных возможностей каждого вида устройств в категории устройств, для которой она предназначена, а именно, устройств с 2МБ или более полной памяти, включая как RAM, так и ROM.

Как вы видели на рисунке 1.1, конфигурация задает как набор поддерживаемых свойств виртуальной машины Java, так и набор библиотек классов. В CDC определено использование виртуальной машины полной платформы Java 2, которая, в этом контексте, называется компактной виртуальной машиной (Compact Virtual Machine (CVM)).

CVM. Хотя CVM поддерживает те же свойства, что и J2SE VM, она создана для потребительских и встраиваемых устройств. Это означает, что стандарт VM J2SE был модернизирован, чтобы соответствовать ограничениям устройств с ограниченными ресурсами. Сюда включены следующие свойства получившегося в результате продукта CVM:

улучшенная запоминающая система;

небольшие временные интервалы сборки мусора в среднем;

полное отделение виртуальной машины от системы памяти;

модульные сборщики мусора;

сборка мусора по поколениям.

В частности, CVM была спроектирована с учетом предоставления следующих свойств:

портативность;

быстрая синхронизация;

выполнение классов Java отдельно от постоянной памяти (ROM);

поддержка естественных потоков;

зоны обслуживания малых классов;

предоставление интерфейсов и поддержка служб операционной системы реального времени (RTOS);

преобразование потоков Java непосредственно в естественные потоки;

поддержка всех свойств и библиотек виртуальной машины версии 1.3, Java 2: безопасность, слабые ссылки, Java Native Interface (JNI, собственный интерфейс Java), Remote Method Invocation (RMI, интерфейс вызова удаленных методов), Java Virtual Machine Debugging Interface (JVMDI, интерфейс отладки виртуальной машины Java).

Библиотеки классов в CDC. CDC устанавливает минимальный набор библиотек классов и API. Она поддерживает следующие стандартные пакеты Java:

java.lang — системные классы виртуальной машины Java;


java.util — базовые утилиты Java;

java.net — дейтаграмма Universal Datagram Protocol (UDP) и ввод/вывод (I/O);

java.io — файловый ввод/вывод Java;

Java.text — самая минимальная поддержка интернационализации (I18N — смотри главу 9);

Java.security — минимальная защита на мелком уровне и шифрование сериализации объекта.

Как вы можете видеть, эти API не включают полный набор пакетов набора инструментальных средств разработки программного обеспечения (software development kit (SDK)) Java 2. В некоторых случаях эти пакеты и классы являются подгруппами пакетов и классов Java 2 SDK. Также убраны все устаревшие API J2SE. В таблице 1.1 перечислен полный набор пакетов, поддерживаемых CDC.

Таблица 1.1. Пакеты CDC

Название пакета CDC Описание
java.io Стандартные классы и интерфейсы ввода/вывода
java.lang Классы виртуальной машины
java.lang.ref Классы для работы с ссыпками на объекты
Java . lang. reflect Классы и интерфейсы, поддерживающие отражение (динамическую информацию о классах)
Java .math Математический пакет
Java .net Сетевые классы и интерфейсы
Java. security Классы и интерфейсы безопасности
Java . security .cert Классы сертификации безопасности
Java . text Текстовой пакет
Java . util Классы стандартных утилит
Java .util . jar Классы утилиты архиватора Java (JAR)
Java .util . zip Классы утилиты ZIP
javax.microedition.io Классы и интерфейсы структуры общих соединений CDC
Профиль Foundation Profile. Конфигурация вместе с профилем формирует исполняемую среду J2ME. Свойства и службы системного уровня, поддерживаемые конфигурацией, более или менее спрятаны от разработчика приложений. В действительности разработчику приложения запрещен прямой доступ к ним. Если это не было соблюдено, приложение не будет считаться соответствующим требованиям J2ME.

С точки зрения программиста профиль необходим для «полезной» работы. Профиль определяет уровень, который содержит АРГи, с которыми программист обычно имеет дело. Создатели J2ME в начале задали один профиль CDC, профиль Foundation, который основан на выпуске J2SE версии 1.3. Он был разработан стандартным комитетом Java Community Process, экспертной группой компаний, работающих в сфере потребительских электронных товаров. Профиль Foundation содержит в себе пакеты J2SE, перечисленные в таблице 1.2.



Таблица 1.2. Пакеты профиля Foundation

Название пакета профиля Foundation Описание
java.lang Дополняет поддержку языка Java пакета java.lang.* J2SE (Compiler, UnknownError)
java.util Добавляет полную поддержку zip и другие утилиты J2SE (java.util. Timer)
Java .net Добавляет TCP/IP Socket и соединения HTTP
java.io Дополняет поддержку ввода/вывода языка Java пакета Java , io . * J2SE (классы Reader и Writer)
Java .text Дополняет поддержку интернационализации пакета Java. text.* J2SE (I18N): Annotation, Collator, Iterator
Java. security Добавляет подпись и сертификацию кодов
Вышеуказанный список пакетов выглядит точно так, как список, который включает в себя CDC. В действительности они одинаковы. Говоря, что профиль Foundation заключает в себе эти пакеты, мы на самом деле имеем в виду, что они доступны профилю Foundation. Замысел заключается в том, что профиль Foundation используется с CDC. Различие между профилем и конфигурацией заключается в понятиях, но не в физическом отношении.

Отметьте, что вся иерархия java.awt Abstract Window Toolkit (AWT, абстрактного оконного инструментария) и Java.swing пакета Swing, которая определяет API графического пользовательского интерфейса (GUI), отсутствует в поддерживаемых пакетах. Если приложению необходим GUI, потребуется дополнительный профиль. Профили могут быть внедрены поверх друг друга. Продукт платформы J2ME, однако, может содержать только одну конфигурацию.

Отсутствие поддержки GUI в профиле Foundation имеет меньшее воздействие на семейство постоянно подключенных сетевых устройств с общим доступом, таких, как компьютерные приставки к телевизору, чем оно влияет на персональные мобильные устройства, с которыми работают при помощи второй конфигурации J2ME, CLDC.

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

Профиль Personal Profile. Спецификация профиля Personal была разработана в Java Community, конечным результатом которой стал JSR-62. Профиль Personal обеспечивает среду с полной поддержкой AWT. Замысел его создателей заключался в том, чтобы обеспечить платформу, подходящую для Web-апплетов. Он также предоставляет способ перемещения J2ME для приложений Personal Java.



Профиль Personal версии 1. 0 требует внедрения профиля Foundation версии 1.0. Это расширенный набор профиля Personal Basis Profile версии 1.0. Однако профиль Personal является подгруппой платформы J2SE версии 1.3.1, которая дает приложениям, созданным в профиле Personal, большую совместимость снизу вверх с J2SE версии 1.3.1.

В таблице 1.3 перечислены пакеты, которые включены в профиль Personal версии 1.0.

Таблица 1.3. Пакеты профиля Personal

Название пакета профиля Personal Описание
Java. applet Классы, необходимые для создания апплетов, и используемые апплетами
Java .awt Классы AWT для создания пользовательского интерфейса программ
Java . awt . data transfer Классы и интерфейсы для пересылки данных внутри и между приложениями
]ava .awt .event Классы и интерфейсы для обработки событий AWT
Java. awt . font Классы и интерфейсы для работы со шрифтами
Java. awt . im Классы и интерфейсы для описания редакторов методов ввода
Java .awt. im. spi Интерфейсы, которые помогают в разработке редакторов методов ввода для любой среды исполнения Java
Java .awt . image Классы для создания и изменения изображений
Java. beans Классы, которые поддерживают разработку компонентов JavaBean
javax.microedition.xlet Интерфейсы, используемые приложениями и диспетчерами приложений профиля J2ME Personal для коммуникации
Профиль RMI. Профиль RMI является профилем, созданным для платформ, которые поддерживают конфигурацию CDC. Он был задан JSR-66 и определен различными компаниями, принимавшими участие в Java Community Process.

Профиль RMI требует внедрения профиля Foundation и внедряется поверх него. Продукты профиля RMI должны поддерживать следующие свойства:

полную семантику RMI вызовов;

поддержку объектов маршалинга;

RMI проводного протокола;

экспорт удаленных объектов через API UnicastRemoteObject;

распределенную сборку мусора и интерфейсы еборщика мусора как для клиента, так и для сервера;

интерфейс активатора и протокол активации для клиента;



интерфейсы реестра RMI и экспорт реестра удаленных объектов.

Профиль RMI поддерживает подгруппу RMI API J2SE в. 1.3. Следующие интерфейсы и свойства являются частью спецификации RMI J2SE в. 1.3 и публичных API, но поддержка этих интерфейсов и функциональных возможностей исключена из технических требований профиля RMI из-за ограниченности вычислительных мощностей устройств, сетевой производительности и пропускной способности:

RMI через брандмауэры и прокси;

RMI мультиплексный протокол;

модель реализации «активизируемого» («activatable») удаленного объекта;

нерекомендуемые методы, классы и интерфейсы;

поддержка протокола скелетона/заглушки для RMI в. 1.1;

компилятор скелетона и заглушки.

Поддержка следующих свойств J2SE RMI в. 1.3 не включена:

Java. rmi. server. disableHttp;

Java.rmi.activation.port;

Java.rmi.loader.packagePrefix;

Java.rmi.registry.packagePrefix;

java.rmi.server.packagePrefix.


Конфигурация Connected, Limited Device Configuration (CLDC)


Вторая из двух конфигураций J2ME, Connected, Limited Device Configuration (CLDC), поддерживает персональные мобильные устройства, которые составляют значительно менее мощный класс устройств, чем тот, который поддерживает CDC. Спецификация CLDC распознает устройства этой категории по следующим характеристикам:

от 160 до 512 KB полной памяти, доступной для платформы Java;

16-битный или 32-битный процессор;

низкое потребление электроэнергии, часто питание от батарей;

нестационарная сетевая связь (часто беспроводная) с потенциально ограниченной пропускной способностью.

Цель CLDC заключается в том, чтобы установить стандартную платформу Java для этих устройств. Из-за широкого выбора системного программного обеспечения на различных персональных устройствах CLDC исходит из минимальных предположений о среде, в которой она существует. Например, одна ОС может поддерживать множественные параллельные процессы, другая может или не может поддерживать файловую систему и тому подобное.

CLDC отличается от CDC и представляет из себя ее подгруппу. Однако эти конфигурации независимы друг от друга, так что они не должны использоваться вместе при описании платформы. На рисунке 1.2 показана связь между двумя конфигурациями и платформой J2SE.

Рисунок 1.2. CLDC является подгруппой CDC. Ни CLDC, ни CDC, однако, не являются полностью подгруппами платформы J2SE, поскольку обе эти конфигурации добавляют новые классы, необходимые для создания служб в соответствующих семействах устройств

Как и CDC, CLDC определяет требуемый уровень поддержки языка программирования Java, требуемую функциональную поддержку соответствующей требованиям виртуальной машины Java и требуемый набор библиотек классов.

Поддержка языка Java. Спецификация CLDC не включает поддержку следующих свойств языка Java:

вычисления с плавающей точкой;

финализация объекта;

иерархия класса Java.lang.Error во всей его полноте.

Конечно, эти свойства включают также VM и описаны в главе 5 о спецификации CLDC («Adherence to Java Virtual Machine Specification» - «Соблюдение спецификации виртуальной машины Java»). Я, однако, ссылаюсь на них здесь, поскольку они проявляются на уровне языка, что затрагивает программистов.


Отсутствие поддержки плавающей точки является основным отличием на языковом уровне виртуальной машины Java, которая поддерживает CLDC, от стандартной VM J2SE, что очевидно для программистов. Это означает, что программы, предназначенные для запуска на CLDC, не могут использовать константы, типы и величины с плавающей точкой. Вы не можете использовать встроенный тип float и класс Java.lang.Float был удален из библиотек CLDC. Это свойство не присутствует из-за отсутствия аппаратного или программного обеспечения с плавающей точкой на большинстве мобильных устройств.

Финализация объекта также отсутствует. Это означает, что метод Object.finalized был удален из библиотек CLDC.

Иерархия исключений Java.lang.Error также была удалена из библиотек CLDC и поэтому недоступна для приложений. Основная причина того, что обработка ошибок отсутствует, заключается в ограниченной памяти мобильных устройств. Это обычно не создает никаких неудобств при разработке приложений, как-никак, приложения не рассчитаны на восстановление из ошибочных состояний. И ресурсная цена реализации обработки ошибок высока и лежит за пределами возможностей сегодняшних мобильных устройств. Кроме того, нейтрализация ошибок на портативных устройствах, таких, как мобильные телефоны, зависит от конкретного устройства. И, наконец, не имеет смысла оговаривать механизм восстановления, который устройства должны использовать. Этот механизм легко может находиться за пределами встроенной виртуальной машины.

Поддержка виртуальной машины Java и библиотек. В CLDC определены требования для виртуальной машины Java. Они зависят от VM, которая высоко-портативна и создана для ресурсно ограниченных небольших устройств. Поддержка нескольких свойств, которые существуют в стандартной J2SE VM, была исключена из спецификации CLDC. В следующем списке перечислены свойства, которые не поддерживаются в CLDC-совместимой виртуальной машине. Свойства, перечисленные в этом списке, были исключены как из-за изменения библиотек, так и из-за соображений безопасности:



Java Native Interface (JNI, собственный интерфейс Java);

загрузчики определяемых пользователем классов;

отражение (reflection);

группы нитей и демоны нитей (thread daemons);

финализация (отсутствие метода Object.finalizeQ в библиотеках CLDC);

слабые ссылки (weak references);

ошибки (поддерживается небольшая подгруппа ошибок J2SE);

проверка класса файла.

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

Виртуальная машина, которая устанавливается вместе с внедрением CLDC, называется Kilobyte Virtual Machine (KVM), названа она таким образом потому, что использует всего лишь несколько килобайт рабочей памяти. KVM не является полнофункциональной J2SE VM.

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

Как вы знаете, конфигурация является базой для одного или более профилей. CLDC -это конфигурация, поверх которой встраиваются один или более профилей таким же образом, как профиль Foundation встраивается поверх CDC Смысл заключается в том, что АРГи в профиле CLDC поддерживают разработку приложений для рынка персональных устройств массового потребления. Поэтому CLDC предназначена для разработчиков отдельных комплектующих приложений. Вот чем она отличается от CDC, которая предназначена для разработчиков OEM (комплектного оборудования).

В таблице 1.4 перечислены пакеты, которые включает в себя CLDC. Заметьте, что он значительно меньше, чем список пакетов, которые содержит CDC, показанный ранее в таблице 1.1.

Табпииа 1.4. Пакеты CLDC



Название пакета СШС Описание
Java. io Стандартные классы и пакеты ввода/вывода Java, подмножество пакета J2SE
Java . lang Классы и интерфейсы VM, подмножество пакета J2SE
Java .util Классы и интерфейсы стандартных утилит, подмножество пакета J2SE
javax.microedition. io Классы и интерфейсы структуры общих соединений CLDC
Первые три пакета используют префикс Java, в своем имени, потому что каждый из них содержит подгруппу стандартных классов платформы J2SE. Последний, однако, должен использовать префикс javax., поскольку он описывает новое «стандартное расширение», которое не является частью основной платформы Java.

Профиль Mobile Information Device Profile. Поскольку категория, обслуживаемая CLDC, включает в себя такое множество различных типов персональных устройств, потенциально для их поддержки необходимо множество различных профилей. Наиболее популярным и хорошо известным из них является профиль Mobile Information Device (MIDP), иногда называемый MID Profile. MIDP лежит поверх CLDC и задает набор API пользовательского интерфейса (UI), созданного для современных беспроводных устройств.

Следуя традициям языка Java, MIDP-приложения называются MID-леты. МГО-лет является приложением Java, которое использует профиль MIDP и конфигурацию CLDC. Эта книга делает акцент на обучении вас тому, как писать MID-леты, поскольку подавляющее большинство программистов на J2ME будут сталкиваться с платформой CLDC/MIDP намного чаще, чем с другими платформами J2ME. И, с практической точки зрения, MIDP является единственным профилем, доступным на сегодняшний день.

Другой профиль, профиль PDA, в настоящее время находится на стадии описания. Профили PDA также принадлежат к общей категории мобильных информационных устройств. Однако профиль PDA, возможно, никогда не будет внедрен, поскольку сомнительно, предлагает ли он достаточно отличий и улучшений к спецификации MIDP, чтобы оправдать его разработку. Профиль PDA также ставит задачи портативности перед разработчиками.



Спецификация MIDP, как и профиль Foundation конфигурации CDC, была создана экспертной группой, в этом случае экспертной группой профиля Mobile Information Device Profile, которая является международным форумом, включающим представителей нескольких компаний со сферой деятельности в области мобильных устройств. MIDP предназначен для мобильных информационных устройств (mobile information device, MID), таких, как мобильные телефоны, двусторонние пейджеры и тому подобного, которые приблизительно соответствуют следующим характеристикам:

размер экрана примерно (как минимум) 96x54 пикселей;

глубина экрана 1 бит;

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

128 Кб энергонезависимой памяти для MIDP-компонентов;

8 Кб энергонезависимой памяти для данных постоянного хранения;

32 Кб энергозависимой оперативной памяти для области динамической памяти Jra:

двусторонняя беспроводная связь.

Поскольку диапазон возможностей MID столь широк, MIDP устанавливает рабочую величину минимального общего знаменателя возможностей устройств. MIDP поэтому определяет следующие API:

приложения (семантика и управление приложениями MIDP);

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

постоянное хранение;

организация сетей;

таймеры.

В таблице 1.5. перечислены пакеты, которые содержит MIDP.

Таблица 1.5. Пакеты MIDP

Название пакета MIDP Описание
javax.microedition. Icdui Классы и интерфейсы интерфейса пользователя
javax.microedition.rms Система организации ведения записей (Record management system, RMS], поддерживающая постоянное хранение устройства
javax.microedition.midlet Типы классов поддержки определения приложений МЮР
javax.microedition . io Классы и интерфейсы структуры общих соединений МЮР
java.io Классы и интерфейсы стандартного ввода/ вывода Java
Java. lang Классы и интерфейсы виртуальной Java машины
Java .util Классы и интерфейсы стандартных утилит
Вы узнаете больше о деталях программирования API, перечисленных в таблице 1.5, в главах 3-9.



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

На рисунке 1.3 сопоставляются структуры данных платформ CDC и CLDC. Как в CDC, так и в CLDC нет ничего такого, что препятствует производителю подключать любую из платформ к данному семейству устройств. Тем не менее, структуры платформ - особенно свойства конфигураций и профилей - были определены для работы с практическими ограничениями различных семейств аппаратных устройств.

Название пакета МIDР Описание
javax.microedition.midlet Типы классов поддержки определения приложений МЮР
javax.microedition . io Классы и интерфейсы структуры общих соединений МЮР
java.io Классы и интерфейсы стандартного ввода/ вывода Java
Java. lang Классы и интерфейсы виртуальной Java машины
Java .util Классы и интерфейсы стандартных утилит
Рисунок 1.3. CDC предназначена для стационарных устройств с фиксированным соединением и общим доступом. CLDC предназначена для персональных мобильных устройств с ограниченным соединением


Модель хранения данных RMS


RMS поддерживает создание множества хранилищ записей, показанных на рисунке 7.1, и управление ими. Хранилище записей - это база данных, основным понятием которой является запись. Каждое хранилище записей содержит ноль или больше записей. Название хранилища записей чувствительно к регистру и может состоять максимум из 32 знаков уникода. Хранилище записей создается МШ-летом.

Рисунок 7.1. RMS состоит из одного или нескольких хранилищ записей, каждое из которых содержит ноль или более записей, представляющих собой массив байтов

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

В этой связи вы должны знать, что когда все MID-леты в наборе MID-летов удаляются с устройства, AMS устройства удаляет все хранилища записей в пространстве имен набора MID-летов. Все данные постоянного хранения будут потеряны. По этой причине вы должны обдумать при разработке приложения включение предупреждения или подтверждения, требующего, чтобы пользователи подтвердили, что они поняли потенциальную угрозу потери данных при удалении приложений! Приложения могут также включать механизм резервного копирования записей хранилища данных в другое место. Это может потребовать поддержки со стороны сервера, задача, которую я описываю в главе 11.

RMS определяет следующие абстрактные операции для отдельного хранилища записей:

Добавление записи.

Удаление записи.

Изменение записи.

Просмотр (извлечение) записи.

Составление списка всех записей.

Записи однозначно идентифицируются с помощью ID записи, который является единственным поддерживаемым важнейшим ключевым типом. Тип ID всех записей является встроенным типом Java int. RMS не поддерживает свойств -таких, как таблицы, строки, столбцы, типы данных и так далее, - которые присутствуют в реляционных базах данных.



Модель компонентов пользовательского интерфейса MIDP


Компоненты пользовательского интерфейса MIDP определены в пакете javax.microedition.Icdui. Название этого пакета, возможно, изменится в будущих выпусках, поскольку его имя слишком близко связано с определенным типом физических устройств отображения. Компоненты пользовательского интерфейса MIDP составляют большинство классов в этом пакете. Понимание организации этих компонентов пользовательского интерфейса является наиважнейшим при создании приложений MIDP.

Листинг 3.1 предлагает первую демонстрацию использования некоторых из этих компонентов. Последние две строчки метода startApp() обращают особое внимание на вопрос модели программирования пользовательского интерфейса MIDP и демонстрируют, как взаимодействуют классы основных компонентов пользовательского интерфейса:

display = Display.getDisplay (this);

display.setCurrentl form);

Первая из двух показанных выше строчек получает ссылку на объект Display. Объект Display является вбъектом Java, который представляет физическое отображение экрана устройства. В следующей строчке говорится: «Сделать эту форму текущим отображаемым объектом».

Форма является одним из видов отображаемых компонентов, которые могут иметь визуальное представление. Отображаемый компонент в MIDP является верхнеуровневым компонентом пользовательского интерфейса. Верхнеуровневые компоненты могут быть самостоятельно отображены MID-летом. То есть они не нуждаются в том, чтобы содержаться внутри любого другого компонента - в действительности они и не могут. Приложение MIDP может отображать только один компонент верхнего уровня за раз.

Для программистов на AWT и Swing компонент верхнего уровня MIDP эквивалентен java.awt.Frame или java.awt.Window в инструментариях Swing и AWT. Продукт MIDP управляет компонентами верхнего уровня так же, что и собственная система окон управляет Window в реализации платформы J2SE.

Когда AMS запускает MID-лет, она делает следующее:

Она задает класс Display.

Она связывает экземпляр Display с вашим экземпляром MIDlet.


Ваша программа никогда не создает объект Display, а реализация MIDP делает это. Ваш MID-лет создает только компоненты пользовательского интерфейса - экземпляры конкретного подкласса Displayable или Item, которые будут отображены на экране в течение жизненного цикла вашего MID-лета. Вы сообщаете объекту Display, когда показывать ваши отображаемые компоненты, вызывая метод Display.setCurrent().

Здесь взаимодействуют три основных объекта: ваш экземпляр MIDlet, экземпляр Display, созданный AMS, и компонент Displayable, который вы хотите отобразить на экране. На рисунке 3.6 показана диаграмма связей между объектами.



Рисунок 3.6. Реализации MIDP создают только один объект Display на один MID-лет. Ваш MID-лет является примером вашего основного класса, который дополняет класс MID-лета. Однако он может создавать много объектов Displayable

Важными понятиями являются следующие:

Объект Display управляет физическим дисплеем.

Display может отображать объекты Displayable.

Вы должны получить ссылку на объект Display, связанный с вашим MID-летом реализацией MIDP.

Только один объект Displayable может быть отображен единовременно.

Диаграмма наследования является прекрасным инструментом, способным помочь в систематизации понимания модели программирования и связей между классами. На рисунке 3.7 показана диаграмма наследования всех классов в пакете javax.microedition.lcdui.

В частности, отметьте равнородные связи между типами Display и Displayable, их цели различны, поэтому они не имеют связей наследования. Также отметьте, что Form, которую создала наша программа «Hello World», является видом Displayable, называемым Screen. По идее эта организация поддерживает понятие того, что Form может взять на себя роль screen верхнего уровня.

Screen также является абстрактным классом. Он инкапсулирует природу всех типов объектов верхнего уровня экрана в MIDP. Form является отдельным конкретным подклассом Screen, используемым MID-летом HelloWorld.

MID-лет HelloWorld добавляет String в Fora. Эта способность объединять объекты делает класс Form разновидностью контейнера. Хотя контейнеры являются основой моделей программирования AWT и Swing, MIDP не имеет на самом деле такого понятия. Класс Form является единственным типом MIDP, который способен содержать что-либо еще.



Формы могут содержать только три типа объектов: Strings, Images и Items. Form не может содержать другой Displayable любого рода, даже Screen или другой Form. Иерархия наследования, показанная на рисунке 3.7, подтверждает это. Это означает, что формы не могут быть представлены в форме вложений. Эта модель значительно упрощает структуру приложений MIDP по сравнению с графическим интерфейсом пользователя AWT или Swing. Поддержка вложенной структуры означала бы, что реализации пришлось бы поддерживать абстракцию визуального представления исполняемой иерархии вложенности для пользователя. Эта возможность была намеренно исключена из MIDP, потому что ресурсы, требуемые для поддержки родственных абстракций, слишком дороги для мобильных устройств.

Обратите внимание, что на рисунке 3.7 классы Item и Image не находятся под иерархией Displayable и поэтому не являются отображаемыми объектами. Items, Images и Strings могут быть добавлены в формы с помощью методов из класса Form, показанных в таблице 3.3.

Таблица 3.3. Методы класса формы для добавления элементов в объект Form

Название метода класса формы Описание
public int append (Item item) К данной форме добавляется объект Item
public int append (String string) К данной форме добавляется объект String
public int append (Image image) К данной форме добавляется объект Image
Класс Form реализует абстракции, необходимые для отображения объектов String, Image и Item. Он также отвечает за реализацию политики организации объектов, которые были к нему добавлены. Другими словами, реализация Form определяет политику расположения.

Тем не менее, в MIDP нет понятия диспетчеров расположения, которыми мог манипулировать программист на AWT или Swing. Спецификация MIDP рекомендует, чтобы реализации Form сопровождались компоновкой, но она не устанавливает обязательных правил. Реализации могут варьироваться в способе, которым они осуществляют компоновку формы.

Иерархия Item определяет визуальные компоненты. Вы должны, однако, различать эти компоненты, которые имеют визуальное представление и отображаемые компоненты, которые являются компонентами высшего уровня. Отражены могут быть конкретные подклассы Itern. Однако они не могут быть отображены независимо как компоненты высшего уровня Screen. Более того, они могут быть отображены только с помощью объекта Fo rm, но не другого типа Screen.



Рисунок 3.7. Диаграмма наследования компонентов пользовательского интерфейса MIDP показывает связи между MID-летом, связанным с ним объектом Display и его объектами Displayable. Если не определено иное, все классы принадлежат пакету javax.microedition.lcdui.
абстрактный класс,
конкретный класс


Модель организации сетей в MIDP


В MIDP, как и в J2SE, потоки ввода-вывода являются наиважнейшим механизмом, доступным приложениям, для чтения и записи потоков данных. Как J2SE, так и J2ME имеют пакет java.io, который содержит эти классы потоков. Кроме того, MIDP имеет пакет javax.microedition.io, который поддерживает сетевую работу и коммуникации в приложениях MIDP. Этот пакет отличается от пакета java.net J2SE, который определяет поддержку сетевой работы на данной платформе.

Приложения MIDP используют типы javax.microedition.io для создания и работы с различными видами сетевых соединений. Затем они считывают данные с этих соединений и записывают в них с помощью типов пакета java.io MIDP, который содержит подмножество классов и интерфейсов пакета java.io J2SE.

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



Модель состояний MID-лета


MID-леты переходят к различным состояниям в течение их жизненного цикла. Спецификация MIDP определяет модель перехода из режима в режим. В таблице 3.1 перечислены возможные состояния MID-лета и их соответствующие описания.

Таблица 3.1. Состояния MID-лета

Название состояния MID-лета Описание
Paused (Приостановлен) MID-лет не выполняется. Он не может начать работу до тех пор, пока не перейдет в активное состояние.
Active (Активен) MID-лет либо готов к запуску, либо запущен. Процесс, который управляет MID-летом, может не быть в запущенном состоянии, но MID-лет все равно активен.
Destroyed (Прерван) MID-лет не запущен и уже не может переходить в другие состояния.

На рисунке 5 показана диаграмма перехода из состояния в состояние, которая представляет из себя эти режимы MID-лета и события, которые служат причиной перехода из одного состояния в другое. Методы startApp(), pauseApp() и destroyApp(), которые вы видели в листинге 3.1, позволяют MID-лету изменять свое состояние. Технически программа управления приложениями устройства изменяет состояние MID-лета, вызывая один из этих методов в MID-лет. MID-лет не может сам изменять свое состояние, хотя он может запрашивать об изменении состояния AMS.

Программа управления приложениями сначала создает экземпляр класса вашего MID-лета, вызывая его конструктор no-argument. Затем она устанавливает его в приостановленное состояние. Прежде чем MID-лет сможет выполняться, AMS должна поместить MID-лет в активное состояние в первый раз. Она помещает MID-лет в активное состояние и затем вызывает метод MID-лета startApp ().

Рисунок 3.5. MID-лет может находиться в одном из трех состояний. Когда AMS впервые создает МЮ-лет, MID-лет существует в приостановленном состоянии

Программа управления приложениями помещает MID-лет в приостановленное состояние, вызывая метод pauseApp(). MID-лет может также запрашивать AMS о входе в приостановленное состояние, вызывая свой метод notifyPausedf). MID-лет может после этого запрашивать, чтобы он был помещен в активное состояние, вызывая resumeRequest ().


AMS может сигнализировать MID-лету, что он должен привести себя в порядок и подготовиться к тому, что он будет прерван с помощью вызова метода MID-лета destroyApp(). MID-лет может сигнализировать о завершении своего выполнения AMS, вызывая notifyDestroyed(). В таблице 3.2 перечислены методы класса javax.microedition.midlet.MIDlet, которые управляют состоянием MID-лета.

Таблица 3.2. Методы классов MID-летов, управляющие состояниями MID-летов

Название метода класса MID-лета Описание
protected abstract void destroyAppf) AMS сигнализирует MID-лету о прекращении работы. MID-лет входит в прерванное состояние
void notifyDestroyed () MID-лет запрашивает о входе в прерванное состояние
void notifyPausedf) MID-лет запрашивает о дезактивации и входе в приостановленное состояние
protected abstract void pauseApp() AMS сигнализирует MID-лету остановиться, MID-лет входит в приостановленное состояние
void resumeRequest () МЮ-лет запрашивает о повторном входе в активное состояние
protected abstract void startApp() AMS сигнализирует MID-лету, что он активен
Обратите внимание на то, что программа в листинге 3.1 не вызывает System.exit(). Приложения MIDP отличаются от приложений J2SE тем, каким образом они заканчивают работу. Для завершения работы вашего MID-лета вы просто должны вызвать метод MID-лета notifyDestroyed(). Это сигнализирует AMS, что ваш MID-лет завершил выполнение. AMS закрывает MID-лет и все его объекты. Однако виртуальная машина продолжает работу.

Вы хотите, чтобы виртуальная машина продолжала работу, чтобы можно было запустить другие MID-леты. Вызов System.exit() сигнализирует виртуальной машине завершить свою работу. Такое поведение нежелательно в приложениях MIDP. Ваши приложения не должны завершать работу виртуальной машины, в действительности они и не могут этого сделать. Если ваше приложение вызывает System.exit(), java.lang.SecurityException обязательно прекратит работу. Вы увидите что-либо сходное со следующим:

java.lang.SecurityException: MIDP lifecycle does not support system exit.

(Жизненный цикл MIDP не поддерживает системный выход).

at Java.lang.Runtime.exit(+9)

at Java.lang.System.exit(+7)

at HelloWorld3$MyCommandListener.commandAction(+15)

at javax.microedition.Icdui.Display$DisplayAccessor.

commandAction(+99)

at сот.sun.kvem.midp.Icdui.EmulEventHandler$EventLoop.run(+430)

MID-лет не должен закрывать виртуальную машину по двум причинам. Во-первых, другие приложения могут быть запущены, и, закрывая виртуальную машину, вы можете разорвать их работу. Во-вторых, вы никогда не запускаете виртуальную машину, поэтому вы не должны ее закрывать. Виртуальной машиной управляет AMS. Она запускает ее и закрывает, когда обнаруживает, что она не нужна или если ей нужно управлять системными ресурсами.


Обмен сообщениями


Теоретически существует, грубо говоря, три типа обмена сообщениями в беспроводных средах:

мгновенный обмен сообщениями;

электронная почта;

интегрированная система обработки сообщений.

Точные определения этих типов обмена сообщениями являются чем-то неопределенным, потому что они зависят от характеристик конкретной реализации. Например, насколько «мгновенны» мгновенные сообщения? Тем не менее, существуют общепринятые трактовки этих терминов.

В беспроводных средах мгновенный обмен сообщениями обычно означает обмен SMS-сообщениями, поскольку системы каналов передачи SMS обычно используются для реализации службы IM. Адреса сообщений состоят из MSN. Каналы передачи SMS предоставляют мгновенный обмен сообщениями в пределах того, что сообщения посылаются «немедленно». Конечно, получается ли сообщение немедленно, зависит от нагрузки системы, управления потоком, ограничений полосы пропускания и так далее, что может привести к задержкам, которые будут выше, чем в технологиях мгновенной передачи сообщений проводных сетей.

Электронная почта (e-mail) - это передача сообщений произвольной длины с помощью модели с промежуточным хранением. Термин электронная почта подразумевает использование схемы адресации, сходной со схемой e-mail Интернета, пример которой показан ниже:

user@some-host.com

Конкретные возможности систем электронной почты в беспроводных сетях зависят от реализации. Например, не все реализации могут поддерживать доставку сообщений произвольной длины. Если электронная почта доставляет сообщения через стандартный канал передачи SMS, сообщения ограничиваются до 128 байтов. В Японии, например, два из основных беспроводных транспортировщиков поддерживают электронную почту на мобильных устройствах с длиной сообщений до нескольких килобайт. Эти системы используют собственную систему пересылки, полностью независимую от SMS.

Интегрированная система обработки сообщений - это понятие единого механизма, который интегрирует другие системы обработки сообщений и дает пользователям единую точку доступа ко всем службам обработки сообщений. Интегрированная система обработки сообщений может иметь пользовательский интерфейс как для Web-доступа, так и доступа с устройств с беспроводной связью. Коммерческие поставщики предлагают интегрированные системы обработки сообщений, которые обычно включают API для интерфейсов приложений. Например, приложение MIDP может взаимодействовать с системой через внешний API и представлять пользователю интегрированное отображение SMS и электронной почты. Этот сценарий работает только на телефонах, которые его поддерживают? Смысл в том, что интегрированные системы обработки сообщений извлекают подробную информацию о взаимодействии с отдельными системами обработки сообщений, такими, как серверы электронной почты или серверы SMS.



Обновление приложения


Обновление приложения - это процесс обновления приложения, которое уже находится на устройстве, на более новую версию. В соответствии с приложением «Инициированная пользователем беспроводная инициализация» (Over the Air User Initiated

Provisioning) спецификации MIDP для поддерживания обновлений уже установленных на устройства приложений требуется система инициализации ОТА. Вы найдете ссылку на этот документ в разделе «Ссылки» в конце данной книги.

Программное обеспечение пользовательского агента устройства и программное обеспечение диспетчера инициализации использует обязательный атрибут MIDlet-Version файла JAD приложения для согласования обновления приложения. Кроме того, дескриптор приложения должен однозначно идентифицировать набор МГО-летов, который должен быть загружен, так чтобы устройство могло определять, должно ли оно выполнять обновление или новую установку. Разработчики должны убедиться, что они поставляют правильную информацию о версии MID-лета и идентификации набора MID-летов.



Oбpaбoткa кoмaнд


Высокоуровневый API MIDP поддерживает обработку событий с помощью использования команд. Команда представляет из себя действие пользователя - например, что-то, что пользователь делает на экране, к примеру, нажимает функциональную клавишу. Событие - это проявление результата действия. События могут представлять собой вызов команды в ответ на действие пользователя.

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

Класс Command в пакете javax.microedition.lcdui описывает команды. Этот класс инкапсулирует информацию о:

метке (label);

приоритетности (priority);

типе команды (command type).

Метка - это String, подходящая для дисплея, с условием, что она может предоставлять пользователю семантику команды. Приоритетность является int, которая отражает важность команды по отношению к другим командам. Тип команды - это внутреннее представление намеченного использования команды. Текущая спецификация определяет типы команды, перечисленные в таблице 4.1.

Таблица 4.1. Типы команд

Константа типа команды Описание
public static. int BACK Возврат к логически предыдущему экрану
public static int CANCEL Стандартный отрицательный ответ на запрос в диалоге
public static int EXIT Указание на выход из приложения
public static int HELP Запрос помощи в онлайновом режиме
public static int ITEM Подсказки приложения для реализации, к которой команды имеют отношение, по определенному элементу на экране, возможно, по выбранному в настоящее время элементу
public static int OK Стандартный положительный ответ на запрос в диалоге
public static int SCREEN Программно определяемая команда, имеющая отношение к отображаемому в настоящее время экрану
public static int STOP Остановка некоторой выполняемой в настоящее время операции



Oбработка команд и событий


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

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

Реализация MIDP передает информацию о событии низкого уровня объекту Canvas, вызывая соответствующий метод в объекте Canvas. В таблице 6.1 перечислены возможные методы.

Таблица 6.1. Методы уведомления о событиях низкоуровневого API

Название метода Описание
protected void keyPressedfint KeyCode) Клавиша была нажата и отпущена
protected void keyReleased.(int KeyCode) Клавиша была отпущена
protected void keyRepeated(int KeyCode) Клавиша была нажата несколько раз
protected void pointerPressed (int x, int y) Указатель был нажат
protected void pointerDragged (int x, int y) Указатель был перемещен
protected void pointerReleased(int x, int y) Указатель был отпущен
protected abstract void paint (Graphics g) Произошел запрос Canvas на перерисовку

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

В листингах 6.1 и 6.2 представлена простая схема обработки команд и событий в Canvas. Код в листинге 6.1 является кодом MID-лета для демонстрационной программы, большая часть которой выглядит знакомо. Код в листинге 6.2, однако, создает подкласс Canvas - Displayable, который согласно коду, показанному в листинге 6.1, размещается на экране.


Листинг 6.1. Демонстрационной программе CanvasDemol требуется MID-лет, как и любое другое приложение МIDР

import javax.microedition.midlet.MIDlet;

import javax.microedition.lcdui.Display;

/"

Определяет MID-лет, отображающий пустой Canvas на дисплее устройства.

Отображаемый Canvas является Экземпляром класса Canvasl.

@смотри Canvasl

*/

public class CanvasDemol extends MIDlet

{

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

private static CanvasDemol midlet;

// Поддерживает ссылку на Canvas, который пользователь

// видит на дисплее.

private static Canvasl instance;

private Display display; private Canvasl canvas;

/**

Конструктор No-arg. Вызывает конструктор no-arg класса MID-лета.

*/

public CanvasDemol()

super();

display = Display.getDisplay(this); instance = canvas; midlet = this;

{

/**

Возвращает ссылку на MID-лет, связанный с данным отображаемым объектом.

@возвращает MID-лет, который отображает этот объект.

**/

public static CanvasDemol getMIDlet()

{

return midlet;

{

public void startApp()

{

canvas = new Canvasl ();

display.setCurrent(canvas);

(

public void pauseApp()

{

}

public void destroyApp(boolean destroy)

{

instance = null;

canvas = null;

void quit ()

{

destroyApp(true);

notifyDestroyed();

}

}

Листинг 6.2. Чтобы использовать Canvas, вы должны создать подкласс Canvas

import javax.microedition.lcdui.Canvas;

import javax.microedition.lcdui.Command;

import javax.microedition.lcdui.CommandListener;

import javax.microedition.lcdui.Display;

import javax.microedition.lcdui.Displayable;

import javax.microedition.lcdui.Graphics;

/**

Определяет подкласс Canvas, который отображается с помощью MID-лета

CanvasDemol. Этот Canvas имеет единственную команду «Exit», так что

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

@Осмотри CanvasDemol

*/

public class Canvasl extends дополняет Canvas

implements CommandListener

{

private Command exit = new Command("Exit", Command.EXIT, 1);



/**

Конструктор No-arg.

*/

public Canvasl ()

{

// Обратите внимание на вызов super (), который является конструктором

// r.o-arg Canvas! Это очень важно. Без вызова superf) ваши экземпляры

// Canvas не смогут действовать как настоящие Canvas. Они не будут

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

// не смогут обрабатывать события, super () ;

addCommand(exit); setCommandListener (this);

printCanvasInfo() ;

}

/**

Рисует внешний вид Canvas, видимый пользователю.

В данном случае он не рисует ничего.

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

*/

public void paint(Graphics g)

{

}

public void commandAction(Command c, Displayable d)

{

if (c == exit)

CanvasDemol.getMIDlet().quit();

}

/**

Определяет, что обработка должна быть сделана в ответ на

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

Этот метод подменяет тот же самый метод в Canvas.

*/

public void keyReleased(int keyCode)

{

printKeyEventlnfo(keyCode);

}

/**

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

Этот метод просто печатает в стандартном результате некоторую

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

*/

protected void printKeyEventlnfо(int keyCode)

{

System.out.println("Key code = " + keyCode);

System.out.println("Key name = " + getKeyName(keyCode));

System.out.println("Game action = " + getGameAction(keyCode));

}

/*'*

Печатает диагностическую информацию об атрибутах

и возможностях объекта Canvas.

"/

protected void printCanvasInfо ()

{

System.out.println("Device height = " + getHeight ());

System.out.println("Device width = " + getWidth());

System.out.println("Pointer events = " + hasPointerEvents());

System, out. printl'n ("Pointer motion events = " +

hasPointerMotionEvents());

System.cue.println("Repeat events = " + hasRepeatEvents());

}

}

Чтобы убедиться, что в Canvas все еще осуществима обработка высокоуровневых команд, запустите MID-лет, показанный в листинге 6.2. Вы увидите, что дисплей, показанный на рисунке 6.2, имеет экранную клавишу Exit (Выход), которая при активации завершает работу МID-лета.



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