Платформа программирования J2ME для портативных устройств
CDC предназначена для стационарных
Рисунок 1.3. CDC предназначена для стационарных устройств с фиксированным соединением и общим доступом. CLDC предназначена для персональных мобильных устройств с ограниченным соединениемCLDC является подгруппой CDC Ни
Рисунок 1.2. CLDC является подгруппой CDC. Ни CLDC, ни CDC, однако, не являются полностью подгруппами платформы J2SE, поскольку обе эти конфигурации добавляют новые классы, необходимые для создания служб в соответствующих семействах устройств
Как и CDC, CLDC определяет требуемый уровень поддержки языка программирования Java, требуемую функциональную поддержку соответствующей требованиям виртуальной машины Java и требуемый набор библиотек классов.
Поддержка языка Java. Спецификация CLDC не включает поддержку следующих свойств языка 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-совместимой виртуальной машине. Свойства, перечисленные в этом списке, были исключены как из-за изменения библиотек, так и из-за соображений безопасности:
Виртуальная машина, которая устанавливается вместе с внедрением 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 |
Профиль 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), таких, как мобильные телефоны, двусторонние пейджеры и тому подобного, которые приблизительно соответствуют следующим характеристикам:
Конфигурации и профили
Конфигурации и профилиКонфигурация включает три базовых элемента:
Технические спецификации конфигурации требуют, чтобы все классы Java, адаптированные с J2SE, были идентичны или соответствующей подгруппой оригинального класса J2SE. То есть класс не может добавлять методы, которых нет в версии J2SE. Однако конфигурации могут включать дополнительные классы в свои спецификации, конфигурации сами по себе необязательно являются соответствующими подгруппами J2SE. Обе конфигурации, которые были определены под классы добавления данных, не представлены в J2SE для того, чтобы обращаться к атрибутам и ограничениям устройств.
Конфигурация Connected Device Configuration (CDC)
Конфигурация 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:
Конфигурация Connected Limited Device Configuration (CLDC)
Конфигурация Connected, Limited Device Configuration (CLDC)Вторая из двух конфигураций J2ME, Connected, Limited Device Configuration (CLDC), поддерживает персональные мобильные устройства, которые составляют значительно менее мощный класс устройств, чем тот, который поддерживает CDC. Спецификация CLDC распознает устройства этой категории по следующим характеристикам:
CLDC отличается от CDC и представляет из себя ее подгруппу. Однако эти конфигурации независимы друг от друга, так что они не должны использоваться вместе при описании платформы. На рисунке 1.2 показана связь между двумя конфигурациями и платформой J2SE.
Определение платформы Java для портативных устройств
Определение платформы Java для портативных устройствКонфигурации и профили являются основными элементами, которые составляют модульную схему J2ME. Эти два элемента дают возможность поддержки огромного количества устройств, которые поддерживают J2ME.
Конфигурация J2ME определяет минимальную Java-платформу для семейства устройств. Все члены данного семейства имеют сходные требования к памяти и производительности. Конфигурация является на самом деле спецификацией, которая определяет доступные ресурсы системного уровня, такие, как набор свойств языка Java, характеристики и свойства имеющейся виртуальной машины и минимальные библиотеки Java, которые поддерживаются. Разработчики программного обеспечения могут рассчитывать, что определенный уровень системной поддержки будет доступен для семейства устройств, которое использует определенную конфигурацию.
Конфигурация также определяет минимальный набор свойств для категории устройств. Производители устройств внедряют профили для обеспечения реальной платформы для семейства устройств, которая имеет возможности, определяемые данной конфигурацией.
Другой строительный блок J2ME, профиль, определяет программный интерфейс для определенного класса устройств. Реализация профиля состоит из набора библиотек классов Java, которые обеспечивают интерфейс программного уровня. Таким образом, профиль теоретически должен определять все виды функциональных возможностей и служб.
Однако это не является намерением создателей. Создатели J2ME планируют, что профиль будет предназначаться для нужд определенной категории устройств или вертикального рынка, относящегося к этой категории устройств. Мысль заключается не в том, чтобы помещать огромное количество несвязанных свойств программного уровня в профиль. Скорее основная цель заключается в том, чтобы гарантировать возможность взаимодействия - которая необязательно предполагает совместимость конечных продуктов различных производителей - между всеми устройствами одной категории или семействами вертикального рынка для определения стандартной платформы разработки приложений на Java.
Например, профиль может поддерживать возможность сетевой коммуникации для популярного стандарта Short Message Service (SMS), широко используемого в мобильных телефонах. Поскольку стандарт SMS является повсеместно распространенным свойством в сотовой телефонии, имеет смысл задать эту службу в профиле, который предназначен для мобильных телефонов, вместо того чтобы встраивать ее в конфигурацию.
Профиль внедряется поверх конфигурации, на одну ступень ближе к выполнению практических приложений. Обычно профиль включает библиотеки, которые соответствуют более специфичным характеристикам категории устройств, которую они представляют, чем библиотеки, которые содержат конфигурации. Приложения затем встраиваются поверх конфигурации и профиля, они могут использовать только библиотеки классов, предоставляемые этими двумя низкоуровневыми спецификациями. Профили могут быть встроены поверх друг друга. Конечный продукт платформы J2ME, однако, может содержать только одну конфигурацию. На рисунке 1.1 показаны схематичные уровни, из которых состоит платформа J2ME.
Платформа J2ME состоит из ряда
Рисунок 1.1. Платформа J2ME состоит из ряда уровней, которые поддерживают базовую среду исполнения с корневыми библиотеками Java и Виртуальной машиной (VM), набора программных интерфейсов приложения системного уровня (API) в конфигурации и набора API программного уровня в профилеДо настоящего времени эти представления об определениях конфигураций, профилей и платформ были чем-то абстрактным, отвлеченным. В следующем разделе вы получите более конкретное описание характеристик реальных сред.
Системы управления приложениями устройств
Системы управления приложениями устройствВсе приложения J2ME - MID-леты и другие - являются настоящими приложениями Java, которые запускаются под контролем Java VM. Но что контролирует Java VM, например, на мобильном телефоне? Не существует командного процессора, с которого вы можете активизировать ваши любимые приложения Java, как вы можете сделать на рабочей станции. Запуск, остановка и управление приложениями Java контролируется программами управления приложениями {application management software, AMS), которые находятся на этом устройстве. В действительности AMS контролирует весь жизненный цикл приложения от установки, обновления и управления версиями до удаления программного обеспечения.
Производители устройств обычно предоставляют программное обеспечение AMS. Это самый логичный сценарий, потому что программное обеспечение AMS должно работать вместе с программным обеспечением родной системы устройства, которую, по всей видимости, производитель знает лучше всего. Тем не менее, производители комплектующего оборудования также могут разрабатывать системы AMS для определенных устройств. Программное обеспечение AMS может быть написано, например, в Java или некоторых других машинных языках, таких, как С. Понимание вопросов, связанных с управлением приложениями, важно для разработчика на J2ME. Управление приложениями описывается в главе 10. Вы должны знать о последствиях вашего выбора в отношении упаковки, лицензирования, загрузки для использования и так далее, и как эти решения повлияют на удобство, простоту использования и жизнеспособность вашего программного обеспечения.
Пакеты 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 |
С точки зрения программиста профиль необходим для "полезной" работы. Профиль определяет уровень, который содержит АРГи, с которыми программист обычно имеет дело. Создатели J2ME в начале задали один профиль CDC, профиль Foundation, который основан на выпуске J2SE версии 1.3. Он был разработан стандартным комитетом Java Community Process, экспертной группой компаний, работающих в сфере потребительских электронных товаров. Профиль Foundation содержит в себе пакеты J2SE, перечисленные в таблице 1.2.
Пакеты профиля Foundation
Таблица 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 | Добавляет подпись и сертификацию кодов |
Отметьте, что вся иерархия 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.
Пакеты профиля Personal
Таблица 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 требует внедрения профиля Foundation и внедряется поверх него. Продукты профиля RMI должны поддерживать следующие свойства:
Пакеты 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 | Классы и интерфейсы стандартных утилит |
Реализация MIDP должна состоять из пакетов и классов, указанных в спецификации MIDP. Кроме того, она может иметь зависимые от реализации классы для доступа программного и аппаратного обеспечения родной системы.
На рисунке 1.3 сопоставляются структуры данных платформ CDC и CLDC. Как в CDC, так и в CLDC нет ничего такого, что препятствует производителю подключать любую из платформ к данному семейству устройств. Тем не менее, структуры платформ - особенно свойства конфигураций и профилей - были определены для работы с практическими ограничениями различных семейств аппаратных устройств.
| Название пакета МIDР | Описание |
| javax.microedition.midlet | Типы классов поддержки определения приложений МЮР |
| javax.microedition . io | Классы и интерфейсы структуры общих соединений МЮР |
| java.io | Классы и интерфейсы стандартного ввода/ вывода Java |
| Java. lang | Классы и интерфейсы виртуальной Java машины |
| Java .util | Классы и интерфейсы стандартных утилит |
Платформа программирования J2ME для портативных устройств
Чтобы создать новый проект вы
Рисунок 2.2. Чтобы создать новый проект, вы должны задать, по крайней мере, один MID-лет. Вы должны предоставить имя проекта и имя основного класса Java для первого MID-лета
После того как вы введете и подтвердите имя проекта и имя класса MID-лета, появится окно, показанное на рисунке 2.3. Это окно предложит вам ввести необходимую информацию о вашем проекте, которая будет использоваться для создания файла манифеста JAR и файла JAD. Заметьте, что закладка Required (Требуемые атрибуты) всегда показывается вначале при появлении окна. Атрибуты, которые вы видите, соответствуют атрибутам, перечисленным в таблице 2.4, обязательным атрибутам дескриптора приложения. Вы можете изменять информацию, установленную по умолчанию, например, атрибуты MIDlet-Vendor или MIDlet-Jar-URL.
Главное окно AMS дает вам возможность
Рисунок 2.11. Главное окно AMS дает вам возможность выбрать MID-лет, который вы хотите выполнить. Если более одного MID-лета присутствуют в наборе MID-летов, вы увидите список их всех. Заметьте, что кнопка Launch (Запуск) предоставляется системой AMS
Использование J2ME Wireless Toolkit
Использование J2ME Wireless ToolkitЭтот раздел покажет вам, как использовать J2SE Wireless Toolkit, разработанный в отделе "Java Software" компании "Sun", для выполнения всех этапов цикла разработки, который вы выполнили вручную. Вы можете загрузить J2ME Wireless Toolkit бесплатно с Web-страницы Java Software на сайте Sun Microsystems, http://java.sun.com. Загрузите версию, соответствующую вашей операционной системе, и следуйте инструкциям по установке, предоставляемым при загрузке.
Является тем же что и рисунок
Рисунок 2.12 является тем же, что и рисунок 3.1. В главе 3 описывается исходный код приложения HelloWorld и его варианты в деталях. В этой главе я описываю только процесс разработки приложения.На рисунке 2.13 показано главное окно эмулятора J2MEWTK после того, как вы завершите эмуляцию MID-лета HelloWorld. Заметьте, что оно выводит некоторую диагностическую информацию о процессе эмуляции.
Эмулятор выводит результат диагностики на консоль
Рисунок 2.13. Эмулятор выводит результат диагностики на консоль
Тестирование ваших приложений в эмуляторе является важным первым шагом. Однако этого недостаточно, чтобы быть уверенным в правильной работе и мобильности, и никогда нельзя заменять этим тестирование на реальном устройстве. Создание ваших приложений мобильными - ключ к их успеху.
Этап упаковки на самом деле компилирует
Рисунок 2.9. Этап упаковки на самом деле компилирует приложение прежде, чем его упаковать. Результат диагностики отражает выполнение этапов компиляции и упаковки
Вы можете вновь проверить наличие этих файлов, вручную выведя описание содержимого директории bin/ проекта:
$
pwd /cygdrive/c/J2mewtk/apps/HelloWorld/bin
$ Is -1
total 3
-rw-r--r-- 1 vart'an None 282 HelloWorld.jad
-rw-r--r-- 1 vartan None 6960 HelloWorld.jar
-rw-r--r-- 1 vartan None 29V MANIFEST.MF
S
На самом деле упаковка вашего приложения с помощью J2MEWTK сначала компилирует и предварительно проверяет вашу программу, а затем упаковывает ее. Так что вы можете пропустить процесс явной компиляции, описанный в предыдущем разделе, и просто упаковать ваше приложение до его раскрытия и тестирования. Однако явный этап компиляции важен, если вы хотите откомпилировать вашу программу без ее упаковки.
Это единственное окно показываемое
Рисунок 2.12. Это единственное окно, показываемое приложением HelloWorld. Заметьте, что здесь нет кнопки выхода из приложения. Вы можете нажать на красную кнопку Hang Up (Отбой), чтобы вернуться к главному окну AMS
Важно запускать ваши MID-леты с помощью различных устройств в эмуляторе, чтобы облегчить обнаружение и понимание проблем, связанных с мобильностью. Каждое устройство имеет уникальные размеры дисплея, кнопки, поддержку экранных клавиш и так далее. Кроме того, существуют другие проблемы мобильности, с учетом которых, вероятно, ни один эмулятор не может предоставить реалистичную среду устройства для всех устройств. Например, программные средства собственной платформы каждого устройства имеют различную поддержку временных зон, местной специфики, коммуникационного протокола и так далее. Вы узнаете об этих областях далее в книге.
Компиляция пpoeктa
Компиляция пpoeктaТеперь вы готовы к компиляции. Нажмите на кнопку Build (Создать) на панели кнопок главного окна KToolbar. Wireless Toolkit откомпилирует исходный файл HelloWorld.java и выдаст результат диагностики в главном окне KToolbar, которое показано на рисунке 2.7. Конечно, если ваша компиляция не удастся, обычный благоприятный результат компиляции появится на этой панели.
Компиляция вашего проекта выведет
Рисунок 2.7. Компиляция вашего проекта выведет дополнительные результаты диагностики в главном окне KToolbar
Если результаты вашей компиляции кажутся вам неубедительными, вы можете использовать ваш командный процессор для подтверждения наличия файлов .class в директориях tmpclasses/ и classes/:
$ pwd
/cygdrive/с/J2mewtk/apps/HelloWorId/tmpclasses
$ Is -1
total 8
-rw-r--r— 1 vartan None 2036 HelloWorld.class
$
? cd ../classes/
5 pwd
/cygdrive/с/J2mewtk/apps/HelloWorld/classes
$ Is -1
total 8
-rw-r--r-- 1 vartan None 2036 HelloWorld.class
Как вы уже знаете, директория tmpclasses/ содержит файлы .class, созданные в самом процессе компиляции. Директория classes/ содержит предварительно проверенные файлы, созданные утилитой preverifу. J2ME WTK запускает утилиту preverify автоматически, когда вы нажимаете на кнопку Build (Создать) KToolbar.
Компиляция
КомпиляцияСледующим этапом в цикле разработки после создания вашей программы является компиляция исходной программы. Прежде чем вы приступите к компиляции, убедитесь, что список командных путей среды вашей оболочки включает маршрут к директории, в которой содержатся утилиты J2ME на вашем компьютере.
Общая форма строки компиляции представляет из себя следующее:
S javac -d
Указание -d сообщает компилятору директорию, в которую нужно записывать непроверенные откомпилированные классы. Указание -bootclasspath указывает местоположение файла midpapi.zip, который поставляется вместе с инструментарием J2ME Wireless Toolkit, разработанным "Java Software", и содержит все классы MIDP, которые вам необходимы для написания приложений на J2ME. Среды разработки коммерческих производителей также включают этот файл. Указание -bootclasspath также сообщает компилятору о превосходстве над любой спецификацией CLASSPATH, которую вы, возможно, установили в среде своей оболочки. Заметьте, что это должен быть относительный маршрут доступа к файлу (relative pathname,) - относительный к корневой директории проекта. Наконец, вы указываете имена путей исходных файлов Java, которые вы компилируете.
Чтобы откомпилировать набор MID-летов HelloWorld из директории apps/HelloWorld/, используйте следующую команду:
$ javac -d tmpclasses \
-bootclasspach ../../lib/midpapi.zip src/HelloWorld.Java
$
Указание -d сообщает компилятору записать непроверенные компилированные классы в директорию tmpclasses, которая является поддиректорией каталога HelloWorld/. Указание -bootclasspath определяет путевое имя относительно данного каталога. Наконец, последний параметр указывает относительное путевое имя исходного файла HelloWorld.Java.
Вы узнали, что библиотеки MIDP и CLDC определяют полную платформу для создания приложений на MIDP. Следовательно, вам не придется включать путь для любой J2SE установки в CLASSPATH вашей среды при компилировании ваших приложений. В действительности вы не можете включить его. Если вы это сделаете, вы получите ошибку компиляции, поскольку компилятор найдет конфликтующие определения в библиотеках J2SE и J2ME.
После завершения компиляции ваших файлов директория tmpclasses будет содержать непроверенные файлы .class:
$ Is -I tmpclasses/
total 0
-rw-r--r-- 1 vartan None 922 HelloWorld.class
$
KToolbar является главным окном
Рисунок 2.1. KToolbar является главным окном, из которого вы можете получить доступ ко всем функциям Wireless Toolkit
Первый этап затем заключается в создании нового проекта. Я собираюсь создать проект HelloWorld и вновь использовать исходный код, который вы уже видели. На рисунке 2.2 показано окно, которое всплывает, когда вы выбираете пункт New Project... (Новый проект...) в строке меню KToolbar.
Pacкpытиe u выполнение
Pacкpытиe u выполнениеК настоящему моменту мы уже прошли этапы редактирования (создания программы), компилирования, предварительной проверки и упаковки. Наконец, вы готовы к распаковке и запуску вашего приложения. В действительности разработчик MID-лета загрузил бы файл JAR на какую-либо систему инициализации приложений (системы инициализации приложений описываются в главе 10). Системы инициализации предлагают распаковку приложения вслед за его загрузкой. Пользователи загружают файл JAR набора MID-летов на свои устройства и запускают его с помощью программного обеспечения системы управления приложениями устройства.
В этой главе распаковка означает размещение файлов под управлением эмулятора инструментария J2ME Wireless Toolkit. Вы можете затем запустить приложение в эмуляторе, имитируя его выполнение на реальном устройстве.
Вместо того чтобы просто показать вам, как размещать упакованные файлы приложения под управлением Wireless Toolkit для выполнения, в следующем разделе вам будет показано, как выполнять полный цикл разработки, который вы только что завершили, с помощью Wireless Toolkit. Последняя часть этого описания покажет вам, как выполнять ваши приложения.
Pacкрытие приложения
Pacкрытие приложенияФактически при использовании Wireless Toolkit нет разграничения этапов разработки. Инструментарий создает компоненты, которые вам нужно будет раскрыть в реальной системе, а именно файл дескриптора приложения и файл JAR приложения. В главе 10 описывается, что вы будете делать с этими файлами в реальной системе, которая предлагает приложения MIDP для загрузки на реальные устройства.
Показывает результат диагностики
Рисунок 2.9 показывает результат диагностики, созданный, когда вы закончили процесс упаковки. Заметьте, что он показывает, что Wireless Toolkit создал файлы Hello World jar и HelloWorld.jad.После того как вы завершите ввод
Рисунок 2.6. Разработчики приложения могут устанавливать определенные атрибуты для одного или более MID-летов в наборе MID-летов
Если вы взглянете еще раз на рисунки 2.3 и 2.4, вы увидите, что панели Required (Требуемые атрибуты) и Optional (Необязательные атрибуты) не позволяют вам добавлять какие-либо атрибуты в них. Вы можете только редактировать значения атрибутов, которые уже есть. Вы не можете добавлять обязательные поля, потому что они стандартизованы. Набор необязательных полей также стандартизован, хотя их присутствие не обязательно.
После того как вы закончите этот цикл начального определения набора MID-лета, вы всегда можете отредактировать значения любого из атрибутов MID-лета. Выберите кнопку Settings (Параметры) в строке меню KToolbar. Когда вы это сделаете, вновь появится окно, показанное на рисунке 2.3. Внесите желаемые изменения и нажмите ОК.
Предварительная проверка
Предварительная проверкаСледующим этапом после компиляции является предварительная проверка файлов .class, которые вы только что откомпилировали. Чтобы провести ее, запустите следующую команду:
$ preverify -classpath "../../lib/midpapi.zip;tmpclasses" -d classes \
tmpclasses
S
Если вы используете J2ME Wireless Toolkit, вы должны отделить элементы пути классов точками с запятой, или заключить их в кавычки, если вы используете оболочку Unix, чтобы избежать того, что оболочка начнет интерпретировать точки с запятой. Элементы путей классов представляют собой директории, из которых должны загружаться классы. Разделитель элемента пути класса - точка с запятой в данном случае -зависит от платформы.
Параметр -d указывает директорию, в которую должны быть записаны предварительно проверенные выходные классы, генерируемые с помощью этой команды. Наконец, имя замыкающей директории, tmpclasses, показывает местонахождение, из которого можно получить непроверенные файлы классов, которые были созданы на предыдущем этапе компиляции.
Запуск вышеуказанной команды preverify создает предварительно проверенные файлы . class в директории классов в соответствии с вашими указаниями:
S Is -I classes/
total 0
-rw-r--r-- 1 vartan None 922 HelloWorld.class
$
Команда preverify является инструментом предварительной проверки файлов классов, который используется в процессе проверки файлов классов. Проверка файлов классов в CLDC, как и в J2SE, является процессом проверки истинности файлов классов Java и отклоняет неправильные файлы. Однако в отличие от процесса проверки в J2SE проверка файлов классов в CLDC включает два этапа:
Причина появления этого нового процесса проверки заключается в том, что обычный верификатор файлов классов J2SE требует больше памяти и возможностей по обработке данных, чем стандартные мобильные устройства могут реально предоставлять. Он использует около 50 Кб места под двоичный код и от 30 до 100 Кб динамической памяти при работе. Новый верификатор CLDC требует намного меньше RAM и является при этом намного более эффективным. Для стандартных файлов классов верификатор CLDC использует только около 10 Кб кодового пространства и требует только 100 байт динамической памяти при работе.
Новый верификатор может достигать такой высокой производительности благодаря новому алгоритму, который он использует. Этот новый алгоритм, однако, требует наличия специальных атрибутов в каждом файле классов Java. Верификатор предварительной проверки записывает эти новые атрибуты в каждый файл классов Java. Верификатор затем использует атрибуты, созданные верификатором предварительной проверки. Новые файлы классов приблизительно на 5 процентов больше, чем их немодифицированные версии.
Верификатор предварительной проверки выполняет две задачи:
Атрибуты, которые верификатор предварительной проверки записывает в файлы классов CLDC, называются атрибутами стековой карты. Атрибуты стековой карты определяются структурой данных StackMap_attribute. Эти атрибуты являются субатрибутами атрибута Code, определяемого и используемого обычной виртуальной машиной J2SE. Имя стековая карта отражает природу атрибута как описания типа локальной переменной или элемента стека операндов. Такое имя выбрано потому, что эти элементы всегда находятся в стеке интерпретатора.
Тип Code_attribute является другим типом, определяемым стандартной виртуальной машиной. Он определяет атрибут Code, используемый стандартной виртуальной машиной J2SE. Для получения полного описания этих структур, пожалуйста, смотрите спецификацию виртуальной машины Java "Java Virtual Machine Specification", которая отмечена в разделе ссылок в конце этой книги. Верификатор предварительной проверки CLDC определяет следующую структуру Stackmap_attribute, которая определяет производный тип стековой карты, как изложено ниже:
StackMap_attribute
{
u2 attribute_name_index; u4 attribute_length; u2 .iumber_of_entries;
u4 byte_code_offset;
{
u2 number_of_locals;
cy types_of_locals[number_of_locals];
u2 number_of_stack_iteras;
ty types_of_stack_items[nuraber_of_stack_iterns];
} entries [number_of_entriesj;
}
Для получения дополнительной информации об описании и функционировании каждого из этих полей, пожалуйста, смотрите спецификацию Connected, Limited Device Configuration Specification.
Проектирование и кодирование
Проектирование и кодированиеПрежде чем вы приступите к самому циклу разработки, вы должны сначала создать структуру директорий, которая будет поддерживать разработку вашего набора MID-летов. Набор MID-летов - это комплект MID-летов, которые используют общие ресурсы приложений. Вы получите более подробную информацию об этих общих ресурсах MID-летов в следующих главах книги.
Я сначала создаю директорию под названием HelloWorld, что является названием примера нашего первого приложения, под директорией apps/, предназначенной для установки инструментария для работы с беспроводными устройствами. Эта директория является корневой для вашего нового проекта. Проект - это организованное объединение ресурсов - исходного кода, файлов ресурсов, откомпилированных файлов, - специфических для одного или более связанных приложений.
Корневой каталог проекта содержит подкаталоги, показанные в следующем примере кода:
$ pwd
/cygdrive/c/ J2rnewtk/apps/HelloWorld
3 Is -F
bin/ classes/ res/ src/ tmpclasses/
Есть причина для использования такой точной структуры каталогов, которую я объясню далее, когда вы узнаете, как использовать эмулятор Wireless Toolkit Emulator. Однако даже если вы не планируете использовать J2ME Wireless Toolkit, такая организационная структура является самой разумной для начала работы. В таблице 2.1 объяснено содержание и цель этих каталогов.
Размещение исходного кoдa в пpoeктe
Размещение исходного кoдa в пpoeктeТеперь пришло время поместить исходный файл приложения внутри проекта, как указано в панели результатов диагностики KToolbar. Когда вы создадите новый проект, KToolbar создаст соответствующие директории под структурой директорий установки, как вы уже видели, когда использовали интерфейс командной строки. Вспомните, что на моей системе эта директория располагается в /cygdrive/c/J2mewtk/apps.
Под этой директорией существует директория проекта HelloWorld. Ваш следующий шаг заключается в размещении исходного файла HelloWorld. Java вручную под директорией HelloWorld/src/. Конечно, если бы вы действительно создавали проект с нуля, вы бы сначала создали исходник с помощью своего любимого текстового редактора.
Создание файла дecкpиптopa приложения для набора МIDлетов
Создание файла дecкpиптopa приложения для набора МID-летовПрограммное обеспечение управления приложениями на устройстве, таком, как мобильный телефон, использует файл JAD для получения информации, необходимой для управления ресурсами во время выполнения MID-лета. Файл дескриптора приложения является необязательным, однако полезным. Вы можете использовать любой текстовой редактор для его создания, но вы должны дать файлу расширение . jad. Чтобы избежать путаницы, я рекомендую давать ему имя, которое характеризует весь набор MID-летов.
Создание файла JAR для набора МIDлетов
Создание файла JAR для набора МID-летовТеперь, когда вы создали файл манифеста, вы готовы к созданию файла JAR приложения. Используйте следующую команду jar:
$ jar craf bin/MANIFEST.MF bin/HelloWorld.jar -C classes/ . -C res .
$
Эта команда создаст файл JAR для вашего набора MID-летов HelloWorld. Листинг содержимого директории bin/ обнаруживает только что созданный файл HelloWorld. jar:
$ Is -i bin
total 2
-rw-r--r-- 1 vartan None 1393 HelloWorld.jar
-rw-r--r-- 1 vartan None 193 MANIFEST.MF
$
Листинг содержимого файла JAR, который вы только что создали, выдает следующую информацию:
$ jar tf bin/HelloWorld.jar
META-INF/
META-INF/MANIFEST.MF
classes/./
classes/./HelloWorid.class
HelloWorld.png
$
Как вы можете видеть, файл манифеста включается в файл JAR. Файл JAR содержит один файл .class для нашего приложения HelloWorld. Он также содержит файл формата .png (portable network graphics -переносимая сетевая графика), который является подходящим вариантом для использования в качестве значка приложения. Файл MANIFEST.MF, конечно, был создан вручную, как описано выше.
Создание файла манифеста JAR
Создание файла манифеста JARЕСЛИ вы хотите добавить файл Manifest к вашему заархивированному набору MID-летов, вам необходимо создать его прежде, чем вы создадите сам JAR-архив. Вы можете создать этот файл в любом текстовом редакторе. Потом создайте JAR-файл с помощью стандартной утилиты JAR J2SE. Утилита JAR включается в утилиты инструментария Wireless Toolkit.
Спецификация MIDP требует, чтобы в файле Manifest присутствовали определенные поля. Требуемые поля показаны в таблице 2.2.
Создание пpoeктa
Создание пpoeктaСвойства и функции Wireless Toolkit базируются на проектах. Проект представляет собой разработку набора из одного или более MID-летов. Завершение выполнения цикла разработки проекта выражается в создании файлов приложения JAR и JAD и файла манифеста, который описывает файл JAR.
KToolbar является основной утилитой Wireless Toolkit. На рисунке 2.1 показано главное окно KToolbar. Обратите внимание, что во время запуска он предлагает вам создать новый проект или открыть существующий и снова использовать исходный код, который вы уже видели в примерах с использованием командной строки.
Поддиректории проектов
Таблица 2.1. Поддиректории проектов, созданных с помощью J2ME Wireless Toolkit| Название поддиректории | Содержание директории |
| Bin | Файлы приложения: файл .jar, файл .jad, MANIFEST. MF |
| classes | Откомпилированные и предварительно проверенные файлы .class |
| Res | Файлы ресурсов приложения, такие, как файлы изображений .png в формате PNG |
| Src | Файлы исходного приложения |
| tmpclasses | Откомпилированные, непроверенные файлы .class |
Обязательные атрибуты файла MANIFEST MF
Таблица 2.2. Обязательные атрибуты файла MANIFEST.MF| Имя атрибута | Описание |
| MIDlet-Name | Название набора MID-летов |
| MIDlet-Versiorv | Номер версии набора MID-летов в форме |
| MIDlet-Vendor | Разработчик приложения (компания или частное лицо) |
| MIDlet- |
По одному на MID-лет в данном наборе, содержит разделяемый запятой список из текстового имени MID-лета, значка и имени класса п-ного MID-лета в наборе |
| MicroEdit ion-Profile | Профиль J2ME, необходимый для исполнения MID-лета |
| MicroEdition-Configuration | Конфигурация J2ME, необходимая для исполнения MID-лета |
MIDlet-l: HelloWorld, HelloWorld.png, HelloWorld
MIDlet-Narae: HelloWorld
MIDlet-Vendor: Vartan Piroumian
MIDlet-Version: 1.0
MicroEdition-Configuration: CLDC-1.0
MicroEdition-Profile: MIDP-1.0
Обратите внимание на имя атрибута MIDlet-1: в файле MANIFEST.MF. Файл манифеста различает различные MID-леты, нумеруя их от MIDlet-l до MIDlet-/!. Число 1 должно идентифицировать первый MID-лет.
У атрибута MIDlet-1 существует три значения. Первое - название набора MID-летов, который содержит данный MID-лет. Это значение может быть именем, воспринимающимся человеком. Второе значение является именем файла изображения PNG, который AMS использует как значок, представляющий этот MID-лет. Последнее значение является именем файла класса MID-лета, который определяет входную точку исполнения MID-лета.
Наверное, самыми важными атрибутами являются атрибуты MicroEdition-Configuration и MicroEdition-Profile. AMS использует эти значения для определения того, подходит ли MID-лет для данного устройства.
Спецификация MIDP позволяет также создавать необязательные поля в файле манифеста. В таблице 2.3 показаны необязательные поля файла манифеста.
Необязательные атрибуты файла MANIFEST MF
Таблица 2.3. Необязательные атрибуты файла MANIFEST.MF| Имя атрибута | Описание |
| MI Diet-Description | Описание набора MID-летов |
| MIDlet-Icon | Имя файла PNG, содержащегося в JAR |
| MIDlet-Info-URL | URL, который содержит дополнительную информацию об этом наборе MID-летов |
| MIDlet-Data-Size | Минимальное количество байт данных постоянного хранения, требуемое набором |
Обязательные атрибуты
| Имя атрибута | Описание |
| MIDlet-Jar-URL | URL файла JAR набора MID-летов |
| MIDlet-Jar-Size | Размер (в байтах) файла JAR |
| MI Diet-Name | Имя набора MID-летов |
| MIDlet-Vendor | Разработчик приложения (например, название компании или имя частного лица] |
| MIDlet-Version | Номер версии набора MID-летов в форме |
| MicroEdition-Configuration | Конфигурация J2ME, необходимая для исполнения MID-лета |
| Mi croEditi on- Profile | Профиль J2ME, необходимый для исполнения MID-лета |
Необязательные атрибуты
| Имя атрибута | Описание |
| MIDlet-Data-Size | Минимальное количество байт данных постоянного хранения, требуемое набором |
| MIDlet-Delete-Confirm | Указывает, должна ли AMS запрашивать подтверждение пользователя перед удалением MID-лета |
| MI Diet -De script ion | Описание набора MID-летов |
| MIDlet-Icon | Имя файла PNG, содержащегося в JAR |
| MIDlet-Info-URL | URL, который содержит дополнительную информацию об этом наборе MID-летов |
| MIDlet-Install-Notify | Указывает, должна ли AMS уведомлять пользователя перед установкой нового MID-лета |
Файл JAD для программы HelloWorld также находится в директории HelloWorld/bin/ и его содержимое выглядит так:
MIDlet-1: HelloWorld, HelloWorld.png, HelloWorld
MIDlet-Jar-Size: 1393
MIDlet-Jar-URL: HelloWorld.jar
MIDlet-Name: HelloWorld
MIDlet-Vendor: Vartan Piroumian
MIDlet-Version: 1.0
В частности, обратите внимание на поле атрибута MIDlet-Jar-Size. Когда вы используете инструменты командной строки, вы должны вручную редактировать файл JAD, чтобы обновлять значение атрибута MIDlet-Jar-Size каждый раз, когда вы создаете файл JAR, для точного отражения размера файла JAR. Листинг директории bin/ показывает, что ваш файл JAR занимает 1393 байта. Поэтому файл JAD должен точно отражать этот размер, что он и делает.
Заметьте, что некоторые из полей появляются как в файле манифеста, так и в файле JAD. Причина этого заключается в том, что спецификация MIDP требует их наличия в обоих полях. В частности, три атрибута - MIDlet-Name, MIDlet-Version и MIDlet-Vendor -заслуживают особого внимания. Они должны иметь одно и то же значение, если присутствуют как в файле JAD, так и в файле Manifest. Спецификация MIDP оговаривает, что файл JAR не должен загружаться, если эти три значения не являются идентичными в этих двух файлах.
Упаковка проекта
Упаковка проектаПосле того кай вы выполните компиляцию, вы должны упаковать приложение, что вы уже делали при работе с инструментами командной строки. На панели кнопок KToolbar нет кнопки Package (Упаковка). Вместо этого раскройте пункт меню Project (Проект) в меню KToolbar и выберите пункт меню Package (Упаковка), как показано на рисунке 2.8.
Упаковка
УпаковкаСледующим этапом после предварительной проверки является упаковка приложения. Упаковка набора MID-летов включает 2 объекта:
Архив JAR набора MID-летов может содержать несколько типов файлов, как показано в следующем списке:
Другой необязательный описательный файл, называемый файлом дескриптора приложения, содержит информацию о наборе MID-летов. Этот файл иногда называется дескриптором приложения Java (JAD). Каждый набор MID-летов может иметь связанный с ним файл описания.
Файл дескриптора приложения используется по двум причинам. Программное обеспечение управления приложениями устройства (AMS) использует информацию из этого файла для первоначальной проверки того, что все MID-леты в файле JAR соответствуют требованиям устройства, перед тем как оно загрузит полный файл JAR. AMS также использует эту информацию для управления MID-летом. AMS устройства отвечает за установку и удаление наборов MID-летов. Оно также обеспечивает MID-леты средой исполнения, требуемой спецификацией MIDP. Наконец, AMS управляет выполнением MID-летов, а именно запуском, приостановкой и закрытием всех MID-летов.
Наконец, сами MID-леты могут извлекать из конфигурации JAD-файла специфические атрибуты, которые представляют собой параметры MID-лета. Файл ресурсов приложения является основным механизмом для распаковки конфигураций MIDP-прило-жений.
Выберите пункт меню Package (Упаковка)
Рисунок 2.8. Выберите пункт меню Package (Упаковка) для упаковки вашего приложения. На этом этапе создаются файлы JAD и JAR приложения
Выполнение приложения
Выполнение приложенияВыполнение приложения означает имитирование среды исполнения реального мобильного устройства. Одним из прекрасных свойств эмулятора Wireless Toolkit является то, что он может имитировать несколько реальных устройств, а также некоторые устройства по умолчанию, которые представляют свойства некоторых устройств с самым низким среднем знаменателем.
Панель кнопок KToolbar содержит комбинированное окно, называемое Device (Устройство) под главной строкой меню. Вы можете выбрать одно из шести устройств в этом комбинированном окне. Выбранный пункт указывает эмулятору, какое устройство имитировать при запуске приложения. На рисунке 2.10 показан список устройств, который вы видите при выборе в комбинированном окне.
Wireless Toolkit может имитировать
Рисунок 2.10. Wireless Toolkit может имитировать пять устройств. Два из них являются реальными устройствами
После того как вы выберете устройство по вкусу, вы будете готовы к запуску -вашего приложения. Чтобы запустить ваше приложение в эмуляторе, просто нажмите на кнопку Run (Запуск) на панели кнопок KToolbar. Я закрываю эмулятор Default Color Phone. На рисунке 2.11 показано окно, которое появляется, имитируя среду реального устройства.
На рисунке 2.11 представлено главное окно программы управления приложениями, которое вы можете увидеть на реальном устройстве. Оно дает вам возможность выбрать MID-лет, который вы хотите выполнить. Обычно вы запускаете систему AMS из меню на вашем мобильном устройстве. На рисунке 2.12 показан дисплей после того, как вы выберете пункт HelloWorld, указанный в списке на дисплее. Это и есть окно, показываемое MID-летом.
Платформа программирования J2ME для портативных устройств
Диаграмма наследования компонентов
Системные свойстваCLDC/MIDP поддерживает системные свойства, которые являются парами "ключ-значение", представляющими информацию о платформе и среде, в которой выполняются приложения MIDP. Теоретически это тот же тип свойств, который вы найдете в J2SE. К сожалению, в CLDC/MIDP нет класса Java.util.Properties для облегчения вашей работы со свойствами.
Спецификация MIDP определяет только небольшой набор стандартных свойств, которые показаны в таблице 3.4. Реализации могут поддерживать дополнительные системные свойства определенных производителей, но необязательно. Вы должны знать о том, свойства какого производителя или платформы вы используете для того, чтобы предупреждать проблемы с мобильностью.
Как и приложения J2SE, приложения MIDP могут отыскивать системные свойства с помощью класса java.lang.System. Чтобы узнать значение свойства, используйте метод класса System
String getProperty(String key)
Этот метод извлекает нужные значения, связанные с ключами, чьи значения указываются в запросе.
Если доступно более одного MIDлета
Рисунок 3.3. Если доступно более одного MID-лета, AMS выводит меню, показывая вам их все. AMS, а не ваше приложение, создает кнопку Launch (Запуск). Вы должны нажать на нее, чтобы запустить выбранный МЮ-летЭтот MIDлет запускается с помощью
Рисунок 3.2. Добавьте новые MID-леты к набору с помощью закладки MIDIets (MID-леты) в окне Settings (Параметры)
На реальном устройстве AMS устройства показывает это меню. Например, телефоны Motorola и Siemens используют стандартные списки выбора, которые позволяют вам выбрать сначала AMS, затем набор MID-летов и, наконец, MID-лет. На чужих рынках (в Японии, например) телефоны могут иметь кнопку, помеченную "Web", которая запускает AMS и автоматически запускает Web-браузер, созданный на Java. Перечисленные MID-леты - это те, которые известны AMS.
Когда вы добавляете MID-лет к набору, вы сообщаете инструментарию, что вы хотите, чтобы новый MID-лет был доступен для выполнения. Когда вы создаете MID-лет, инструментарий размещает его файлы .class в файле JAR набора MID-летов и обновляет файлы манифеста и JAD. Этот порядок действий осуществляется в согласии со спецификацией J2ME, которая, как вы помните, требует, чтобы МГО-леты содержались в файле JAR.
Это MIDPверсия знакомой вам программы HelloWorld
Листинг 3.1. Это MIDP-версия знакомой вам программы HelloWorldimport javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.midlet.MIDlet;
/**
Создание программы "Hello world" в J2ME MIDP.
Обратите внимание, что класс должен быть открытым, для того чтобы программа
управления приложениями устройства могла создать его экземпляр.
*/
public class HelloWorld extends MIDlet
{
// Displayable. Этот компонент отображается на экране, private Form form;
// Display. Этот объект управляет всеми компонентами
// Displayable.
private Display display;
// Открытый конструктор no-arg необходим, даже хотя система
// вызывает startAppO! AMS вызывает конструктор
// no-arg класса для создания экземпляра класса.
// Либо создайте открытый конструктор no-arg, либо
// объявите об отсутствии конструкторов и позвольте
// компилятору создать открытый конструктор no-arg. public HelloWorldO
{
super () ; I
public void destroyApp(boolean destroy)
form = null;
notifyDestroyed();
}
public void pauseAppO
}
public void startAppf)
// Создайте элемент Displayable. form = new Form (."Hello, World");
// Добавьте строку в форму. String msg = "My first MIDlet!"; form.append(msg);
// Это приложение просто выводит на экран одну форму, созданную выше.
display = Display.getDisplay (this) ;:
display.setCurrent(form);
}
}
Во-первых, отметьте, что это приложение описывает класс, называемый HelloWorld, который дополняет javax.microedition.midlet.MIDlet. Все MID-леты должны дополнять этот класс.
Класс HelloWorld является основным классом вашего приложения. По этой причине он должен быть объявлен открытым (public). Более того, вы должны объявить открытый безаргументный (no-argument) конструктор или убедиться, что конструкторов нет, в этом случае компилятор определит безаргументный конструктор для вас. Те читатели, которые знакомы с апплетами Java, найдут сходство между моделями управления жизненным циклом апплета и MID-лета.
Когда вы выберете HelloWorld в главном окне AMS, AMS запустит ваше приложение и создаст экземпляр класса HelloWorld. Технически она запустит виртуальную машину (или убедится, что она запущена) и проинструктирует ее создать экземпляр вашего класса. В этом случае виртуальная машина называется безаргументным конструктором.
AMS затем вызовет метод startAppO. В листинге 3.1 методы startApp(), pauseApp() и destroyApp() подменяют абстрактные объявления класса MIDlet. Обратите внимание на то, что все кода инициализации входят в метод startApp (), а не в конструктор. Вы, естественно, можете добавить какой-либо код инициализации в ваш конструктор, он будет выполнен перед вызовом startApp(). Однако метод startApp() всегда вызывается в качестве входной точки вашего MID-лета.
А как насчет метода main ()? Определение языка Java требует, чтобы все приложения на Java имели метод main () со следующей сигнатурой:
public static void main(String [] args)
Если приложения на J2ME являются настоящими приложениями Java, а это требование я оговаривал ранее, тогда где-то должен быть главный метод, который является реальной точкой входа, используемой виртуальной машиной для запуска процесса выполнения приложения. В действительности существует такой метод. Это часть реализации MIDP (не приложения), и, обычно, программное обеспечение AMS вызывает его. Программа AMS обрабатывает запросы вызова приложения, например, порождая подпроцесс нити запроса запуска каждого MID-лета и контролируя MID-лет из этой нити. Реальные детали зависят от продукта. В J2ME Wireless Toolkit компании "Sun" класс com.sun.midp.Main определяет метод main().
Метод startApp() создает объект, называемый формой, и пересылает в конструктор строку, которая представляет из себя название формы. Форма - это экземпляр класса javax.microedition.lcdui.Form, который является разновидностью экрана, отображаемого на вашем дисплее. Она имеет такое название, поскольку ее функции в чем-то сходны с функциями формы HTML - она содержит один или более визуальных элементов, таких как строки.
Затем метод startApp() создает стандартный объект String и добавляет его в форму. Он затем получает ссылку на объект, называемый display, и устанавливает объект формы в качестве текущего отображаемого на дисплее объекта.
Когда этот код выполняется, вы видите экран, изображенный на рисунке 3.4. Когда вы нажимаете на кнопку с телефонной трубкой, которая сообщает устройству об отключении, AMS вызывает destroyApp(), который просто устраняет все ссылки на объект формы, созданные перед этим. Теперь наступает время сборки мусора. Затем AMS закрывает MID-лет.
Вы отвечаете за правильное расположение объектов, созданных вашими МЮ-летами. В случае с данным примером не должно иметь значения, задали ли вы то, что ссылки на формы изменяются до нуля, поскольку MID-лет был закрыт. Но, в общем, вам необходимо правильно управлять ссылками на объекты вашей программы, как и в любой программе Java.
MIDлеты имеют прямой
Листинг 3.2. MID-леты имеют прямой доступ ко всем четырем стандартным системным свойствам, определяемым спецификацией CLDCimport javax.microedition.Icdui.Display;
import javax.microedition.Icdui.Displayable;
import javax.microedition.Icdui.Form;
import javax.microedition.midlet.MIDlet;
/**
Создание программы "Hello world" в J2ME MIDP.
Заметьте, что класс должен быть открытым, для того чтобы программа
управления приложениями устройства могла создать его экземпляр.
*/
public class HelloWorld extends MIDlet
public void startApp()
{
// Создайте элемент Displayable. form = new Fo.rmC'Hello World");
// Добавьте строку в форму. String msg = "My first MIDlet!"; form.append(msg);
// Это приложение просто выводит на экран одну форму, созданную выше.
display = Display.getDisplay(this);
display.setCurrent(form);
printSystemPropertiesf) ;
/**
Вывести значения стандартных системных свойств
С помощью вызова System.getProperty().
*/
protected void printSystemProperties()
String conf;
String profiles; String platform; String encoding; String locale;
conf = System.getProperty("microedition.configuration") ;
System.out.println(conf);
profiles = System.getProperty("microedition.proflies");
System.out.println(profiles);
platform = System.getProperty("microedition.platform");
System.out.println(platform);
encoding = System.getProperty("microedition.encoding");
System.out.println(encoding);
locale = System.getProperty("microedition.locale");
System.out.println(locale);
System.out.println();
}
}
Обратите внимание на добавление вызова к методу printSystemProperties () в конце метода startApp(). Этот метод просто извлекает и отображает стандартные данные значений пяти стандартных системных свойств MIDP. Данные, которые программа записывает в стандартные результаты, показаны здесь:
CLDC-1 .0
MIDP-1.0
J2me
ISO-8859-1
enJJS
Четвертая строчка выходных данных просто воспроизводит набор знаков кодировки, который использует текущая реализация платформы CLDC/MIDP. Последняя строка выходных данных отражает текущую локализацию. Спецификации локализации включают две части: первая часть показывает языковые настройки, в то время как вторая часть показывает код страны. Международная организация по стандартизации (ISO) издает два стандарта, которые определяют набор приемлемых значений для языкового и странового кодов.
В простом примере, показанном в листинге 3.2, я показываю только, как вы можете извлечь эти значения. В последующих главах я покажу примеры того, как вы можете использовать эти значения для практической работы с приложениями.
Измененный метод теперь
Листинг 3.3. Измененный метод теперь также выдает свойства приложения. Программное обеспечение AMS устройства управляет свойствами приложения.public void startApp()
// Создайте элемент Displayable. form = new FormC'Hello, World")/'
// Добавьте строку в форму. String msg = "My-first MIDlet!"; form.append(msg);
// Это приложение просто выводит на экран одну форму,
// созданную выше.
display = Display.getDisplay (this) ;
display.setCurrentfform);
printSystemProperties () ; printAppProperties () ) ) ;
}
Метод, показанный в листинге 3.3, выдает значения стандартных свойств приложения MID-летa в стандартный результат. Листинг 3.4 показывает метод printAppProperties().
Атрибуты MIDлета или
Листинг 3.4. Атрибуты MID-лета, или свойства, отличаются от системных свойств. Вы можете описать неограниченное количество необязательных атрибутов MID-лета в дополнение к предварительно определенным, требуемым/ * *
Вывести свойства приложения с помощью вызова
MIDlet.getAppProperty ().
*/
protected void printAppProperties ()
(
System.out.println(getAppProperty("MI Diet-Name"));
System.out.println(getAppProperty("MIDlet-Jar-Size"));
System, out. println (getAppProperty ("MI Diet-Jar-URL ")) ;
System.out.println(getAppProperty("MIDlet-Vendor"));
}
Эта последняя версия программы HelloWorld теперь выводит следующие строки в дополнение к стандартному результату, который вы можете видеть в окне основной консоли Wireless Toolkit. Метод printAppProperties () выводит последние четыре строки результата.
CLDC-1.0
MIDP-1.0
J2me
ISO-8859-1
en_US
HelloWorld 6781
HelloWorid.jar Vartan Piroumian
Четыре атрибута, выбранные в листинге 3.4, являются стандартными свойствами приложений, доступными для всех MID-летов. Однако вспомните главу 2 и то, что в таблице 2.4 описаны некоторые дополнительные обязательные атрибуты MID-лета. Также спецификация MIDP определяет некоторые необязательные дополнительные атрибуты, в таблице 2.5 перечислены эти необязательные атрибуты. Ваши приложения имеют доступ к ним ко всем через механизм, продемонстрированный в листингах 3.3 и 3.4.
Кроме того, MID-леты могут описывать необязательные зависимые от приложения атрибуты. Вы можете описать так много связанных с приложением свойств, сколько хотите. Ваше приложение будет затем получать к ним доступ с помощью метода MIDlet.getAppProperty(), показанного в листингах 3.3 и 3.4. Эта возможность является своего рода конфигурированием или механизмом настройки MID-летов. Вы увидите некоторые примеры выборочного описания атрибутов и их использования в главе 9.
MIDлет может находиться в одном
Рисунок 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-лета.
Модель компонентов пользовательского интерфейса MIDP
Модель компонентов пользовательского интерфейса 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-лет, она делает следующее:
Здесь взаимодействуют три основных объекта: ваш экземпляр MIDlet, экземпляр Display, созданный AMS, и компонент Displayable, который вы хотите отобразить на экране. На рисунке 3.6 показана диаграмма связей между объектами.
Модель состояний MIDлета
Модель состояний MID-летаMID-леты переходят к различным состояниям в течение их жизненного цикла. Спецификация MIDP определяет модель перехода из режима в режим. В таблице 3.1 перечислены возможные состояния MID-лета и их соответствующие описания.
Программная cтpyктypa MIDлета
Программная cтpyктypa MID-летаТеперь, когда.вы изучили жизненный цикл приложения, наступило время взглянуть на исходный код простого MID-лета. Вы, возможно, уже догадались, что я собираюсь начать с показа наипростейшего MID-лета - MIDP-версии все той же злополучной программы "HelloWorld". В листинге 3.1 показан исходный код для первой версии MID-лета HelloWorld.
Реализации MIDP создают только
Рисунок 3.6. Реализации MIDP создают только один объект Display на один MID-лет. Ваш MID-лет является примером вашего основного класса, который дополняет класс MID-лета. Однако он может создавать много объектов Displayable
Важными понятиями являются следующие:
В частности, отметьте равнородные связи между типами 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.
Главное окно этого
Свойства приложения
Свойства приложенияВы узнали о наличии определенных атрибутов MID-лета, которые описываются в файле JAD каждого набора MID-летов. Вспомните, что всем MID-летам требуются атрибуты.
перечисляет требуемые
Таблица 2.4 перечисляет требуемые атрибуты MID-лета, которые находятся в файле дескриптора приложения. MID-лет может получать доступ к значениям этих атрибутов во время выполнения через программу управления приложениями. Когда AMS устанавливает набор MID-летов на устройстве, она размещает JAD-файл MID-лета в определенном месте под своим контролем. Когда MID-лет запускается, AMS считывает JAD-файл и строит структуру данных атрибутов приложения. MID-лет может считывать значение атрибута с помощью метода класса MIDletString getAppProperty(String key)
Параметр key является именем атрибута, например, MIDlet-Name. Возвращаемая строка является связанным значением, находящимся в файле JAD.
Листинг 3.3 демонстрирует, как MID-лет может извлекать атрибуты. Он модифицирует листинг 3.2, добавляя вызов к методу printAppProperties() в конце метода startApp(). Новый метод startApp() :
Состояния MIDлета
Таблица 3.1. Состояния MID-лета| Название состояния MID-лета | Описание |
| Paused (Приостановлен) | MID-лет не выполняется. Он не может начать работу до тех пор, пока не перейдет в активное состояние. |
| Active (Активен) | MID-лет либо готов к запуску, либо запущен. Процесс, который управляет MID-летом, может не быть в запущенном состоянии, но MID-лет все равно активен. |
| Destroyed (Прерван) | MID-лет не запущен и уже не может переходить в другие состояния. |
Программа управления приложениями сначала создает экземпляр класса вашего MID-лета, вызывая его конструктор no-argument. Затем она устанавливает его в приостановленное состояние. Прежде чем MID-лет сможет выполняться, AMS должна поместить MID-лет в активное состояние в первый раз. Она помещает MID-лет в активное состояние и затем вызывает метод MID-лета startApp ().
Методы классов 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-лету, что он активен |
Вы хотите, чтобы виртуальная машина продолжала работу, чтобы можно было запустить другие 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. Она запускает ее и закрывает, когда обнаруживает, что она не нужна или если ей нужно управлять системными ресурсами.
Методы класса формы
Таблица 3.3. Методы класса формы для добавления элементов в объект Form| Название метода класса формы | Описание |
| public int append (Item item) | К данной форме добавляется объект Item |
| public int append (String string) | К данной форме добавляется объект String |
| public int append (Image image) | К данной форме добавляется объект Image |
Тем не менее, в MIDP нет понятия диспетчеров расположения, которыми мог манипулировать программист на AWT или Swing. Спецификация MIDP рекомендует, чтобы реализации Form сопровождались компоновкой, но она не устанавливает обязательных правил. Реализации могут варьироваться в способе, которым они осуществляют компоновку формы.
Иерархия Item определяет визуальные компоненты. Вы должны, однако, различать эти компоненты, которые имеют визуальное представление и отображаемые компоненты, которые являются компонентами высшего уровня. Отражены могут быть конкретные подклассы Itern. Однако они не могут быть отображены независимо как компоненты высшего уровня Screen. Более того, они могут быть отображены только с помощью объекта Fo rm, но не другого типа Screen.
Стандартные системные свойства CLDC
Таблица 3.4. Стандартные системные свойства CLDC| Ключ свойства | Описание | Значение по умолчанию |
| mi с г oedit ion. con figuration | Название и версия поддерживаемой конфигурации | CLDO1.0 |
| microedit ion. encoding | Набор знаков кодировки по умолчанию, используемый платформой | IS08859-1 |
| micr oedit ion. locale | Название текущей местной среды платформы | нуль |
| microedition. platform | Название платформы или устройства | нуль |
| micr oedition. profiles | Названия всех поддерживаемых профилей | нуль |
Жизненный цикл выполнения приложения
Жизненный цикл выполнения приложенияЗдесь приведен пример этапов, включаемых в выполнение приложения:
Платформа программирования J2ME для портативных устройств
Добавление нового MIDлета к набору
Рисунок 4.3. Добавление нового MID-лета к набору приводит в резул: тате к тому, что AMS отображает меню, из которого вы выбираете приложение, которое хотите запустить
Эта диаграмма объекта показывает
Рисунок 4.2. Эта диаграмма объекта показывает, что в работающем приложении могут существовать многие отображаемые на экране объекты и более чем один может регистрировать тот же самый блок прослушивания. Однако Displayable может иметь только один командный блок прослушивания
В отличие от инструментария Swing MIDP не имеет общей модели блока прослушивания событий. Высокоуровневый API имеет только один тип командного блока прослушивания, называемого, что неудивительно, javax.microedition.lcdui.Command-Listener.
В листинге 4.1 показана вторая версия MID-лета HelloWorld. Она добавляет экранные клавиши к главному окну и устанавливает блок прослушивания команд для распознавания нажатия пользователем на экранную клавишу. MID-лет отвечает, показывая другой вид экрана, называемый уведомлением (alert), который является MIDP-эквивалентом всплывающего диалогового окна.
Листинг 4.1. В программу HelloWorld2 добавлена обработка команд
import javax.microedition.midlet.MIDleC;
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.ledui.Displayable;
import javax.microedition.lcdui.Form;
* /**
Вторая версия приложения HellcWorld.
Эта версия добавляет команду в компонент Displayable и устанавливает
* /
• блок прослушивания команд для активации команд и принятия какого-либо
действия в ответ на нее. Этот пример демонстрирует, как Displayable
определяет семантику выполнения команд.
*/
public class HelloWorld2 extends M.I Diet
// Display. Этот Объект управляет всеми компонентами Displayable.
private Display display;
// Displayable. Этот компонент отображается на экране, private-Form form;
private final String ALERT_LABEL = "Alert Me!"; private Alert alert;
// Две команды добавлены к отображаемым на экране объектам
// этого MID-лета. private Command showAlert; private Command sayHi;
// Экземпляр внутреннего класса, который определяет
// CommandListener для этого MID-лета.
private MyCommandListener cl = new MyCommandListener();
public HelloWorld2()
(
super();
public void destroyApp(boolean destro()
form = null; notifyDestroyed();
}
public void pauseApp()
)
public void startApp()
form = new Form("Hello World();
String msg = "My second MIDletl"; form.appendjmsg);
form.setCommandListener(cl) ;
showAlert = new Command(ALERT_LABEL, Command.SCREEN, 1);
form.addCommand(showAlert) ;
sayHi = new Command("Say Hi", Command.SCREEN, 1);
form.addCommand(sayHi);
display = Display.getDisplay(this) ; display.setCurrent(form);
}
private class MyCommandListener implements CommandListener
public void commandAction(Command c, Displayable d)
{
'alert = new Alert("Button pressed",
"The '" + ALERT_LABEL + "' button was pressed", null, AlertType.INFO); alert.setTimeout(Alert.FOREVER); display.setCurrent(alert, form);
}
}
}
Я создал вышеописанный МID-лет HelloWorld2 с помощью моего любимого текстового редактора. Затем я разместил исходный файл под управление установленного у меня инструментария Wireless Toolkit:
? pwd
/cygdrive/c/J2mewtk/apps/HelloWorld/src
$Is
HelloWorld.lava HelioWorld2.Java
$
J2ME Wireless Toolkit компилирует все файлы .Java в директории. Компиляция проекта HelloWorld компилирует обе версии HelloWorld. На рисунке 4.3 показан стартовый экран эмулятора, который появляется, когда я открываю проект HelloWorld. Обратите внимание, что теперь вы видите два MID-лета в списке. Используя клавиши стрелок, выберите HelloWorld2 и запустите его, нажав экранную кнопку Launch (Запуск).
На рисунке 4.4 показан главный экран HelloWorld2. Обратите внимание, что теперь справа появилась экранная клавиша под названием "Alert Me". На этом устройстве недостаточно места, чтобы показать полное сообщение "Alert Me!", которое установлено в исходном коде - так что восклицательный знак пропущен.
Это первая проблема транспортабельности, с которой вы столкнулись, и она имеет практическое значение. К счастью, эмулятор поддерживает четыре различных устройства. Вы можете выполнить ваши MID-леты с помощью любого из четырех эмуляторов устройств, поддерживаемых эмулятором J2ME Wireless Toolkit Emulator, чтобы посмотреть, как они выглядят на каждом. Таким образом вы можете обнаружить множество потенциальных проблем, связанных с транспортабельностью.
Эта диаграмма UML показывает связь
Рисунок 4.1. Эта диаграмма UML показывает связь между несколькими ключевыми классами, которые ответственны за создание, обнаружение и передачу командных событий вашему приложению
Обратите внимание, что эта диаграмма не является всесторонним UML-представлением каждого представителя, атрибута типов и так далее. На рисунке 4.2 показана диаграмма экземпляра объекта, которая отражает взаимодействие экземпляров этих классов в работающем приложении.
Нажатие на экранную клавишу "Alert
Экранная навигацияПример 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 является легкая, интуитивная навигация между окнами. Анализ задач пользователя является темой отдельной книги и лежит за пределами темы данной книги. Самым важным, однако, является умение думать с точки зрения пользователя. Делайте все простым. Не путайте ваших пользователей, прыгая с места на место, так что пользователь не сможет следовать навигации. Слишком легко пользователю потеряться при просмотре на маленьком экране без контекстной привязки ко всему приложению. И никогда не создавайте ваши экраны, приспосабливая их к внутренней организации вашего приложения, его структуре данных, классам и так далее. Наоборот, позвольте дизайну вашего приложения следовать расположению, дизайну, навигации, последовательности экранов и так далее.
Oбpaбoткa кoмaнд
Oбpaбoткa кoмaндВысокоуровневый API MIDP поддерживает обработку событий с помощью использования команд. Команда представляет из себя действие пользователя - например, что-то, что пользователь делает на экране, к примеру, нажимает функциональную клавишу. Событие - это проявление результата действия. События могут представлять собой вызов команды в ответ на действие пользователя.
Команда фиксирует семантическую информацию или отображение действия пользователя или события. Она не может, однако, определять поведение, которое вытекает из действия или события. Приложение определяет обработку - линию поведения, если хотите, -которая вытекает из появления некоторой команды.
Класс Command в пакете javax.microedition.lcdui описывает команды. Этот класс инкапсулирует информацию о:
Организация команд
Организация командВзглянув более внимательно на пример, приведенный в предыдущем разделе, вы можете догадаться, что на самом деле вы не можете контролировать то, где появляется каждая из меток Command на экранных клавишах. Как-никак, я не указывал левую или правую экранную клавишу для размещения обеих Command. Приложение HelloWorld2 добавило клавиши "Alert Me!" и "Say Hi", в таком порядке. Первая появилась на правой экранной клавише, вторая - на левой.
В действительности реализация управляет размещением меток Command на ваших отображаемых объектах Displayable в соответствии с некоторой политикой, зависящей от реализации. Вы можете познакомиться с различными политиками, просмотрев различные эмуляторы в беспроводном инструментарии. Вы сможете увидеть, что эмулятор Motorola размещает клавиши не так, как эмуляторы стандартных черно-белого и цветного телефонов.
Следующая версия нашей программы HelloWorld, HelloWorldS, добавляет третью команду к главному экрану. Вместо того чтобы приводить вновь весь MID-лет целиком, я просто покажу части, которые отличаются от предыдущего примера.
В масштабах класса HelloWorld3 определяет три объекта Command:
/**
Третья версия приложения HelloWorld.
Эта версия встраивается поверх HelloWorld2 с помощью добавления
нескольких команд к компоненту Displayable. Здесь демонстрируется,
что ComraandListener должен определять, какая из команд была
активирована на экране.
Вы также можете видеть, как реализация расставляет команды
на экранных клавишах и как она создает меню и упорядочивает команды
в меню в соответствии с типом команды.
*/
public class HelloWorld3 extends MIDlet,
{
Command showAlert= new Command("Alert Me!", Command.SCREEN, 1);;
Command sayHi = new Command("Say Hi", Command. SCREEN, I);-;
Command cancel = new Command("Cancel", Command.CANCEL, 1);
public HelloWorld3()
{
super ();
}
. . .
}
В методе startApp() эти объекты Command добавляются к главному экрану следующим образом:
form.addComraand(showAlert) ;
form.addCommand(sayHi) ;
form.addCommand(cancel);
Создание и запуск этой новой версии в эмуляторе J2ME Wireless Toolkit Emulator отражен в главном экране, показанном на рисунке 4.7.
Во-первых, обратите внимание, посмотрев на рисунок 4.7, что вы видите метку "Меню" на правой экранной клавише при запуске этого последнего MID-лета с помощью эмулятора стандартного черно-белого телефона. В программном коде определенно нигде нет определения меню.
Устройства имеют только две экранные клавиши, но мы вставили три команды в наш главный экран. Реализация обнаружила это и создала меню, которое содержит вторую, третью и другие команды. На рисунке 4.8 показан дисплей после того, как вы выбрали клавишу "Меню".
Запустите эту последнюю немодифицированную версию с помощью эмулятора Motorola iS5s, и вы увидите, что ключ "Меню" появится на левой экранной клавише, что отражено на рисунке 4.9. В действительности рисунки 4.8 и 4.9 демонстрируют, что конкретное поведение и политика размещения меню зависят от реализации.
показан основной экран HelloWorld2, как
Рисунок 4.4. Основной экран MID-лета HelloWorld2
Ha рисунке 4.5 показан основной экран HelloWorld2, как он появляется при имитировании устройства Motorola i85s. Обратите внимание, что в отличие телефона со стандартными настройками, он способен отображать полный текст команды на экранной клавише. Есть также вторая Command с меткой "Say Hi", которая появляется на экранной клавише слева.
Нажатие на экранную клавишу "Alert Me!" отображает экран уведомления, показанный на рисунке 4.6. Действие отображения этого экрана является поведением, которое HelloWorld2 определило для команды, связанной с выбором экранной клавиши.
Взглянув на исходный код HelloWorld2, вы увидите, что, за исключением нескольких изменяющихся объявлений, логическая схема, которая формирует эту возможность обработки команд, находится в методе startApp(). Приложение создает экземпляр CommandListener. HelloWorld2 определяет внутренний класс, MyCommandListener, для выполнения обязанностей блока прослушивания команд. Он реализует интерфейс CommandListener.
Вам не придется делать это таким способом. Вы можете, например, создать подкласс класса Form и заставить его реализовать CommandListener. Ваш подкласс является Form, который является разновидностью Screen, и поэтому он способен получать уведомления о вызове команды.
Размещение меток — команд зависит
Упорядочивание командВы, должно быть, удивлены, почему команда "Cancel" (Отмена) была помещена на экранную клавишу, даже несмотря на то, что бна была добавлена на экран последней. Интуитивно вы можете предположить, что она должна добавляться последней в меню. Вы бы предположили, конечно, что клавиша "Alert Me!", которая была добавлена первой, должна быть на экранной клавише.
Объяснение этого очевидного отклонения заключается в том, что команды организуются в соответствии с их типом. Вспомните из предыдущего раздела этой главы, что одной из трех частей информации, которые определяют Command, является тип команды. Класс Command определяет константы, которые представляют собой действующие типы. Вы видели перечисление этих констант в таблице 4.1.
Теперь я добавляю следующие объекты Command в пример HelloWorld3. На уровне классов я определяю следующие новые команды:
...
public class HelloWorid3 extends MIDlet
private Command exit = new Command("Exit", Command.EXIT, 2);
private Command help = new Command ("Help", Command.HELP, 3);
private Command item. = new Command ("Item", Command . ITEM, 4 ) ;
private Command ok = new Command("OK", Command.OK, 5);
private Command screen = new Command("Screen", Command.SCREEN, 6);
private Command stop = new Command("Stop", Command.STOP, 7);
...
}
Обратите внимание, что каждая из команд имеет определенный тип. Эти различия дают вам возможность видеть, как реализация размещает команды на экране в соответствии с их типом.
В методе startApp() я добавляю эти новые объекты команд к главному экрану. Новая версия startApp() выглядит таким образом:
public void startApp()
// Создайте элемент Displayable. form = new Form("Hello World");
// Добавьте элемент строки в форму. String msg = "My first MIDlet!"; form.append(msg);
// Добавьте MyCommandListener в Form, чтобы принимать
// события нажатия клавиш, которые должны порождать
// всплывание диалогового окна уведомления, form.setCommandListener(cl);
form.addCommand(showAlert); form.addCommand(sayHi);
form.addCommand(cancel) ;
form.addCommand(exit} ;
form.addCommand(help); form.addCommand(item);
form.addCommand(ok); form.addCommand(screen);
form.addCommand(stop);
// Это приложение просто отображает одну форму, созданную выше,
display = Display.getDisplay(this); display.setCurrentfform);
}
Когда вы запустите новую версию, первое, что вы должны заметить, это то, что команда "Cancel" ("Отмена") замещена командой "Exit" ("Выход") на экранной клавише, как показано на рисунке 4.10. Активация меню показывает, что клавиша "Cancel" ("Отмена") все еще на самом деле здесь, но теперь она в меню.
Реализация MIDP определяет политику
Рисунок 4.10. Реализация MIDP определяет политику размещения команд в соответствии с их типом
Размещение команд осуществляется в соответствии с их типом. Конкретная же политика, однако, зависит от реализации.
Сценарий oбработки команд
Сценарий oбработки командСценарий обработки команд в MIDP является теоретически сходным с другими ин-струментариями графического пользовательского интерфейса. Блок прослушивания команд (command listener) является объектом, который получает уведомления о наличии команд. Блоки прослушивания команд регистрируются для получения уведомления о командах.
Некоторые внешние действия, такие, как нажатие пользователем на кнопку, отражаются в реализации MIDP, обнаруживая событие и связывая его с отображаемым в настоящее время экраном. Это инкапсулирует событие в объект Command. Зарегистрированный блок прослушивания команд получает уведомление о событии. Блок прослушивания затем предпринимает какое-либо действие, которое отражает поведение команды.
Команды могут быть связаны только с элементами Displayable. To есть вы можете добавлять или удалять объекты Command в и из объекта Displayable с помощью следующих методов класса Displayable:
public void addCommand(Command crad)
public void removeCoramand(Command cmd)
Объект блока прослушивания команд должен прикрепляться к Displayable для получения уведомления о команде с помощью вызова следующего метода в объекте
Displayable:
void setCommandListener(CommandListener cl)
Только один блок прослушивания команд разрешен на один Displayable. Реализация MIDP поставляет команды только в текущий Displayable. Это ограничение пришлось ввести с учетом реалистичных ожиданий от производительности современных реализаций MIDP. MIDP определяет модель одной нити для обработки событий. Поддержка многочисленных блоков прослушивания команд потребовала бы модели со множеством нитей обработки событий.
На рисунке 4.1 показана диаграмма UML связей между классами Displayable и Command и интерфейсом CommandListener.
Семантика команд
Семантика командВзгляните вновь на команду "Exit" ("Выход"). Объект Command определяется с помощью метки "Exit" ("Выход"). Но это не делает команду командой выхода! Я указал тип Command.EXIT в вызове конструктора. Это указание атрибута типа делает команду командой "Exit" ("Выход"). Если бы я задал ее тип, как, скажем, Command. SCREEN, эта команда не появилась бы на экранной клавише. Вы можете попробовать проделать это самостоятельно.
Реализация выбирает такую политику размещения команд, которая пытается максимально повысить удобство и простоту использования устройства. По-видимому, хорошей идеей является расположение клавиши "Exit" в легко доступном месте, поскольку это та клавиша, которая используется для навигации между окнами. Тем самым мы еще раз возвращаемся к мысли о том, что дружественная к пользователю навигация является наиболее важным моментом при работе на устройствах с маленькими экранами и более ограниченными механизмами пользовательского ввода.
Наконец, несколько слов можно сказать о приоритетности команд. Обратите внимание, что организация команд, то есть размещение в соответствии с их типом, очень отличается от расстановки приоритетов поставки событий. Схема размещения ничего не делает с атрибутом приоритета Command, одним из трех атрибутов объектов Command. Приоритетность команд диктует приоритетность, которую реализация выдает командам при упорядочении их поставки в блок прослушивания.
Я определил различные приоритеты для каждой команды в примере HelloWorldS. Вы можете убедиться, что приоритетность не влияет на размещение команд. Если вы немного поэкспериментируете с меню, вы сможете выяснить политику размещения команд реализации каждого устройства, предоставляемого эмулятором. Вы также можете изменить приоритетность команд в исходном коде и увидеть, как это влияет на_их размещение.
В действительности расстановка приоритетов команд не является столь важной, когда вы работаете с высокоуровневыми API MIDP. Тем не менее, важно знать об этом понятии. Обычно пользователь не способен делать только одну вещь за раз, так что не будет лишним добавить высокоприоритетные события в приложение.
Типы команд
| Константа типа команды | Описание |
| 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 | Остановка некоторой выполняемой в настоящее время операции |
Платформа программирования J2ME для портативных устройств
DateField
DateFieldНа главном экране демонстрационного приложения UlComponent (смотри http://www.phptr.com/) вторым элементом списка является демонстрационная версия класса DateField. На рисунке 5.1 показано, что DateField является разновидностью Item; как таковой, он должен быть частью Form для того, чтобы быть отображаемым. В листинге 5.5 показан исходный код файла DateFieldDemo.java.
Другие экранные типы
Другие экранные типыВы видели все компоненты MIDP за исключением одного: TextBox. В отличие от TextField TextBox является многострочной редактируемой текстовой областью. Взгляните еще раз на наглядную иерархию наследования, показанную на рисунке 5.1, и вы увидите, что TextBox является видом Screen, а не Item.
Поскольку TextBox является Displayable, вы должны создать объект MID-лета для демонстрации его использования, вы не можете разместить его в другом Screen или Form, как вы могли поступить с компонентами, происходящими от Item. На рисунке 5.11 показан экран TextBoxDemo.
Другие компоненты Item
Другие компоненты ItemВ предыдущих примерах описывались некоторые компоненты, которые создают основу всех приложений MIDP. В оставшейся части главы вы увидите остальные компоненты пользовательского интерфейса.
Платформа программирования J2ME для портативных устройств
GaugeКласс Gauge также является производным от Item. Запуск GaugeDemo из основного экрана создает дисплей, показанный на рисунке 5.8.
Главный экран демонстрационной
Листинг 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 определяет три константы:
Есть еще один тип информации, которая может быть собрана из этого неявного List. Ранее я говорил, что событие команды посылается в Displayable в ответ на нажатие пользователем кнопки Select на устройстве. Тип этой команды, однако, отличается от любого из типов, которые определяет класс Command.
Класс List определяет особый объект Command, List.SELECT_COMMAND. Активация списка IMPLICIT генерирует эту особую команду и посылает ее в блок прослушивания команд без какой-либо явной операции выбора, осуществляемой пользователем. Истинная цель этой команды заключается в том, чтобы дать возможность методу блока прослушивания commandAction() распознать активацию операции выбора устройства. В листинге 5.3 показано, как метод UIComponentDemo.commandAction() использует эту специальную константу.
Иерархия Компонентов пользовательского интерфейса MIDP
Иерархия Компонентов пользовательского интерфейса 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.
Imageltem
ImageltemНесколько компонентов пользовательского интерфейса MIDP поддерживают отображение изображений. На рисунке 5.10 показано изображение, отображенное в форме. В листинге 5.9 показан исходный код для программы, которая отображает рисунок 5.10.
элемента TextBox пользовательского интерфейса MIDP.
Листинг 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 показан экран после выбора текстового окна для редактирования.
Экранная навигация
Экранная навигацияНа данный момент вы познакомились со следующими компонентами пользовательского интерфейса:
Однако такое поведение в MIDP не является автоматическим. Только один Displayable видим в любой момент времени и реализация не отслеживает какой-либо информации об отображаемых экранах.
Переход "вперед" легок. Как демонстрируется в приложениях, вы просто создаете следующий Displayable и делаете запрос на его отображение. Но переход "назад" немного сложнее. Вам придется убедиться, что у вас есть действующая ссылка на экранный объект, к которому вы хотите возвратиться.
Обратите внимание, что каждый класс, который вы видели до сих пор в демонстрационной программе, поддерживает ссылку на экземпляр, созданный приложением. В UIComponentDemo.java, например, это следующее объявление элемента:
projected static Displayable instance;
Это объявление имеет следующий сопутствующий метод:
public static Displayable getlnstance()
{
return instance;
}
Данйый метод объявляется с модификатором static, так что на него можно легко создать Ссылку из любого места в приложении без создания экземпляра класса, содержащего этот метод - таким образом можно избежать неправильного цикла работы приложения.
Приложение AlertDemo предоставляет экранную клавишу Back (Назад) на экране Build Alert (Создание уведомления), показанном на рисунке 5.3. Если вы нажмете эту клавишу, вы вернетесь обратно в главное окно. Посмотрите вновь на метод commandActionO данной программы, которая показана в листинге 5.4.
Экраны и экранные элементы
Экраны и экранные элементыПервый пример в этой главе показывает вам основную разницу между двумя типами компонентов пользовательского интерфейса MIDP: компонентами Displayable и компонентами Item. Иерархия наследования, изображенная на рисунке 5.1, ясно отображает Эти две категории. Иерархия Displayable заключает в себе экраны, которые вы отображаете. Иерархия Item классифицирует элементы, которые могут быть собраны в один экран. Следующие примеры демонстрируют использование различных компонентов пользовательского интерфейса MIDP. Мы объясняем их использование по мере ознакомления с каждым.
В листинге 5.1 показан файл под названием UIComponentDemo.java, который определяет исходный код новой программы, демонстрирующий использование элементов MIDP. Этот файл использует код в других файлах, которые вместе составляют полную демонстрационную программу компонента пользовательского интерфейса.
Конкретный интерфейс предоставляемый
Рисунок 5.12. Конкретный интерфейс, предоставляемый для редактирования текстового окна, зависит от реализации
Исходный код UlComponentDemo
Листинг 5.1. Исходный код UlComponentDemoimport 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.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.4. Блок прослушивания команд должен находить ссылку на экземпляр любого экрана приложения, юкоторому он хочет вернутьсяpublic void commandAction(Command c, Displayable d)
{
UIComponentDemo jiemo = UIComponentDemo.get Instance ();
Display display = Display.getDisplay(demo);
int timeSec;
int cimeMillis;
if (c == go)
}
// Уведомления не принимаются приложением, определяющим Commands.
String title = elements[type.getSelectedlndex()];
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.getlnstancef));
}if (c == back)
}
UIComponentDemo.getInstance().display!);
}
Если команда является командой Back (Назад), этот метод показывает предыдущий экран, пересылая экземпляр List, созданный в UIComponentDemo.java, в метод Display.setCurrent(). Если UIComponentDemo.getlnstance() не был объявлен static, получить ссылку на объект List будет сложно.
В соответствии с этой идиомой метод AlertDemo.getlnstance () возвращает ссылку на экземпляр, к которому дисплей должен вернуться, после того как уведомление будет закрыто. В таком случае может быть использована ссылка this. Но метод getlnstance() может стать доступным, если приложение будет позже усовершенствовано. Тем не менее, важным моментом является использование идиомы, которая делает ссылки на экраны легко доступными.
Первыми двумя строками метода commandAction () являются следующие:
UIComponentDemo demo = UIComponentDemo.get Instance ();
Display display = Display.getDisplay(demo);
Эти строчки используют ту же идиому для легкого получения ссылки на MID-лет. Класс UIComponentDemo определяет этот статический метод, который освобождает вас от вынужденного кодирования следующей строки каждый раз, когда вам понадобится создать ссылку на дисплей:
Display.getDisplay(UIComponentDemo.getMIDiet() ) ;
Конечно, это не единственный способ осуществлять экранную навигацию. Другой метод заключается в поддержке набора ссылок на объекты Displayable. Вы можете поместить объект Displayable в набор, когда вы сделаете его текущим отображаемым объектом. Чтобы перейти назад к предыдущему экрану, вытолкните его из стека и установите его текущим Displayable.
Безотносительно к методу, который вы выбрали, смысл в том, что ваша программа должна "знать" следующий экран для отображения. Иногда вы захотите вернуться назад к предыдущему экрану. В других случаях вы можете захотеть перейти на два или более экрана назад или к некоторому произвольному экрану. Вы можете даже использовать комбинацию вышеописанных подходов для выполнения нужной вам навигации.
Поскольку экраны являются
Листинг 5.5. Поскольку экраны являются отображаемыми, метод getlnstanceO должен возвращать экранный объект некоторого вида. Этот возвращает экземпляр Formimport 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), которые позволяют вам контролировать то, какая информация отображается.
Исходный код демонстрационной программы Ticker
Листинг 5.8. Исходный код демонстрационной программы Tickerimport javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.raicroedition.lcdui.Ticker;
import javax.raicroedition.lcdui.Form;
/**
Этот класс демонстрирует использование класса
Ticker пользовательского интерфейса MIDP.
@see javax.microedition.lcdui.Gauge
*/
public class TickerDerno extends Form
implements CommandListener
}
private String str = "This text keeps scrolling until the demo stops...";
private Ticker ticker = new Ticker(str);
private Command back = new Command("Back", Command.BACK, 1);
private static Displayable instance;
/**
Конструктор.
*/
public TickerDemo()
{
super("Ticker demo");
instance = this;
addCommand(back);
setTicker(ticker) ; setCommandListener(this);
{
...
}
Однако вы можете связать один и тот же объект Ticker с несколькими экранами. Реализация отображает Ticker на некоторой постоянной части дисплея, в данном случае наверху дисплея.
Взглянув на рисунок 5.1 еще раз, вы заметите, что Ticker не является Item. Он является производным непосредственно от Java.lang.Object, что подсказывает вам, почему Ticker может быть привязан к дисплею, а не к экрану. Его не нужно извлекать из Item, поскольку он на самом деле не является чем-то, что размещено в Form.
Конструктор создает
Листинг 5.9. Конструктор создает объект изображения и пересылает его компоненту пользовательского интерфейса для отображения. Обратите внимание, что указание пути для изображения относительно к директории ресурсов этого проекта при установке с помощью инструментария J2ME Wireless Toolkitimport javax.microedition.lcdui.Command;
import javax.microedition.Icdui.ComraandListener;
import javax.microedition.Icdui.Displayable;
import javax.microedition.Icdui.Form;
import javax.microedition.Icdui.Image;
import javax.microedition.Icdui.Imageltem;
import Java.io.lOException;
/**
Этот класс демонстрирует использование класса
Imageltem пользовательского интерфейса MIDP.
Усмотри javax.microedition.Icdui.Imageltem
*/
public class ImageltemDemo extends Form implements CommandListener
{
private Imageltem imageltem;
/**
Конструктор.
@сбрасывает lOException, если указанный ресурс изображения не может быть найден.
public ImageltemDemo() throws lOException
*/
super("Imageltem Demo");
String path = "/bottle80x80.png";
Image image = Image.createlmage(path);
imageltem = new Imageltem)"Ship in a bottle", image,
Imageltem.LAYOUT_CENTER,
"Image not found");
append(imageltem);
addCommand(back);
setCommandListener(this) ;
instance = this;
}
...
}
В листинге 5.9 демонстрируется использование класса Imageltem компонента пользовательского интерфейса MIDP. Imageltem является подклассом Item, так что он должен быть размещен в Form, как было продемонстрировано в листинге.
Прежде чем вы сможете отобразить изображение, вы должны создать объект изображения. Класс javax.microedition.lcdui.Image определяет изображения. Чтобы создать экземпляр Image, укажите имя пути к файлу изображения. Файлы изображений должны храниться в формате Portable Network Graphics (PNG). J2ME поддерживает работу с изображениями только в этом формате.
Обратите внимание, что в листинге 5.9 имя пути файла изображения связано с директорией res/ директории проекта UlComponents. Директория res/ содержит все файлы ресурсов, включая файлы изображений. Если вы разместите свои изображения где-либо еще, они не будут найдены и ваша программа сбросит lOException, когда попытается открыть файл.
В листинге 5.9 конструктор создает Imageltem с помощью только что созданного объекта Image. Параметрами конструктора являются строка заголовка, которая отображается над изображением, объект изображения, указание размещения изображения и текстовая строка, которая будет показана в случае, если изображение не может быть отображено по какой-либо причине.
Класс Imageltem является единственным классом, который предоставляет контроль расположения изображений, но некоторые из компонентов пользовательского интерфейса MIDP также используют изображения. В таблице 5.5 перечислен полный набор компонентов интерфейса пользователя MIDP, которые используют изображения.
Несколько компонентов пользовательского
Рисунок 5.10. Несколько компонентов пользовательского интерфейса MIDP поддерживают отображение изображений. Здесь форма содержит компонент Image Item, который отображает изображение
Реализация предоставляет этот
StringltemКласс Stringltem определяет двухсоставный компонент дисплея. Объекты Stringltem содержат метку и какой-либо неизменяемый текст. На рисунке 5.7 показан экран, отображаемый классом StringltemDemo, который вы можете запустить из окна, в котором указаны основные компоненты пользовательского интерфейса.
Строковые элементы состоят из
Рисунок 5.7. Строковые элементы состоят из двух частей: текстовая метка и текстовое значение
В листинге 5.6 показаны имеющие отношение к этому классу части кода StringltemDemo. Вы можете соотнести текст в двух параметрах аргумента конструктора с текстом, отображаемым на дисплее. Это очень простой компонент интерфейса пользователя.
Листинг 5.6. Строковые элементы являются формами
import javax.raicroedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.Stringltem;
/**
Это? класс демонстрирует использование класса
Stringltem пользовательского интерфейса MIDP.
@see javax.microedition.lcdui.Stringltem
*/
public class StringltemDemo extends Form implements CommandListener
private Command back = new Command("Back", Command.BACK, 1);
private static Displayable instance;
private Stringltem si = new Stringltem("Stringltem's title",
"Immutable item text");
/**
Конструктор.
"/
public StringltemDemo()
super("Stringltem Demo"); append(si); addCoramand(back);
setCommandListener(this);
}
instance = this;
}
...
}
Объекты Stringltem предоставляют вам удобный способ связать метку со значением. Вы можете вложить String в Form вместо использования объекта Stringltem, но Stringltem имеет преимущество, выражающееся в том, что его реализация гарантирует, что строки метки и значения останутся на дисплее вместе.
Существуют интерактивные и неинтерактивные
Листинг 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)
Описание всех компонентов
| Имя класса компонента, 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 | Отображение тикера | Высокоуровневый |
Константы класса AlertType
Таблица 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.
Типы ограничений устанавливаемые
| Константа ограничения | Описание |
| ANY | Любые буквенно-цифровые знаки |
| EMAILADDR | Только синтаксически правильный e-mail |
| NUMERIC | Только цифровые знаки |
| PASSWORD | Знаки не отображаются на дисплее |
| PHONENUMBER | Только цифровые знаки, реализация предоставляет задание формата |
| URL | Только синтаксически правильный LJRL |
Константы DateField
Таблица 5.4. Константы DateField для управления отображением информации о дате и времени| Константа DateField | Описание |
| public static int DATE | Отображает только дату |
| public static int DATE TIME | Отображает дату и время |
| public static int TIME | Отображает только время |
Вызов конструктора DateField может определять временную зону, которая не поддерживается вашей реализацией MIDP. Если временная зона, которую вы указали в конструкторе, не поддерживается вашей реализацией MIDP, ваша программа все равно будет выполняться без ошибки или предупреждения, но временная зона объекта DateField будет представлять собой какую-либо зону, поддерживаемую реализацией, но не ту, которую вы запрашивали. И время, отображаемое на экране, будет отражать временную зону, используемую объектом DateField, вместо временной зоны, которую вы указали в вызове конструктора.
Объекты DateField являются редактируемыми. Чтобы отредактировать их,
На главном экране DateFieldDemo, показанном на рисунке 5.4, вы можете прокрутить до поля времени и, нажав, выбрать его. Дисплей затем покажет экран, изображенный на рисунке 5.6.
Компоненты пользовательского
Таблица 5.5. Компоненты пользовательского интерфейса MIDP, которые используют изображения| Компонент пользовательского интерфейса MIDP | Описание |
| Alert | Изображение отображается вместе с текстом |
| ChoiceGroup | Изображение отображается слева от текста каждого элемента |
| List | Изображение отображается слева от текста элемента |
| Imageltem | Предоставляет контроль размещения самого объекта изображения |
Ticker
TickerТикер (Ticker) является объектом, предоставляющим прокручиваемый текст наверху дисплея. TickerDemo в листинге 5.8 создает дисплей, показанный на рисунке 5.9.
Тикер размещается на дисплее но
Рисунок 5.9. Тикер размещается на дисплее, но не на экране. Реализация определяет место для тикера независимо от какого-либо экрана, позволяя использовать его множеству различных экранов
Ticker связан с дисплеем, но не с экраном. Вы размещаете Ticker на экране с помощью метода Screen.setTicker (Ticker t), как показано в коде листинга 5.8.
Платформа программирования J2ME для портативных устройств
Canvas может отображать изображение
Листинг 6.10. Чтобы отобразить изображение, Canvas просто "рисует" объект изображения с помощью процедуры рисования изображения объекта Graphicsimport 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.
Изображения автоматически дважды буферизируются.
Эта программа демонстрирует, что вы ничего не должны делать для получения
поведения двойной буферизации при отображении изображений.
Однако вам все равно придется провести двойную буферизацию
операции, которая рисует фон Canvas, до рисования изображения.
*/
public class DoubleSufferlmageDemo extends Canvas
implements CommandListener
{
// Константа, которая представляет белый цвет.
private static final int WHITE = OxFF " 16 I OxFF " 8 I OxFF;
private static Command back = new Command ("Back", Command.BACK, 1);
private GraphicsDemo gDemo = GraphicsDemo.getlnstance();
private Display display = Display .getDisplay (gDerno) ;
// Ссылка на Image, которое отображает этот объект. Image image;
// Переменная, используемая для определения того,' осуществляет
// ли реализация автоматическую двойную буферизацию.
// Принимает значение "true", если реализация осуществляет
// автоматическую двойную буферизацию,
"false" в ином случае, private boolean autoDoubleBuffered = true;
/**
Конструктор No-arg.
*/
public DoubleBufferlmageDemo()
{
super();
if (!isDoubleBuffered())
{
autoDoubleBuffered = false;
}
// Создайте изображение PNG. Изображение "нарисовано" в
// изменяемом объекте Image, который имеет свой собственный
// внеэкранный Graphics. Мы сейчас создаем изображение в
// конструкторе, вместо метода paint (),
//так что оно создается только один раз. try
}
image = Image.createlraage("/bottle80x80.png" );
}
catch (lOException ioe)
{
System.out.println(ioe.getMessage()); ioe.printStackTracef);
}
addCommand(back); setCommandListener(this); display.setCurrent (this);
}
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.fillRecc(clipX, clipY, clipW, clipH);
g.setColor (color);
/**
Рисует изображение на видимом Canvas этого объекта.
*/ public void paint(Graphics g)
Graphics originalG = null; int width = getWidth () ;
int height = getHeight () ;
if (image == null)
{
return; 1
// Мы все равно нуждаемся в двойной буферизации операций
// рисования, которые очищают графику Canvas, if (!autoDoubleBuffered)
{
// Сохраняет первоначальный графический контекст и использует
// внеэкранный Graphics из Image для очистки отсекаемого
// прямоугольника. originalG = g; g = image.getGraphics ();
paintClipRect (g);
}
else 1
// Нарисуйте фон с первоначальным Graphics, переданным в него. paintClipRect(g);
{
// Нам не нужна двойная буферизация вызова отображения Image.
// Вызов этого метода рисует изображение во
// внеэкранном Graphics объекта Image, копируя затем его
// содержимое в контекст Graphics устройства неявно.
g.drawlmage(image, 0, 0, Graphics.TOP I Graphics.LEFT);
public void commandAction(Command c, Displayable d)
{
if (c == back)
GraphicsDemo.getInstance().display!);
}
}
}
Процедура довольно прямолинейна. Вы должны сначала создать объект изображения, что вы сделали, когда переслали изображение в компонент высокоуровневого пользовательского интерфейса MIDP. Программа вызывает Image.createlmage(String name) для создания объекта Image. Этот метод определяет местоположение файла изображения, чье имя пути указано относительно директории res/ проекта.
Затем вы пересылаете изображение в объект Graphics, указывая точку привязки и местоположение (х, у) точки привязки. После этого программа просто вызывает метод Graphics.drawlmage() для отображения изображения. Реализация MIDP пересылает объект Graphics в метод приложения paint (Graphics g). Он представляет физический графический контекст устройства. То есть выполнение Graphics.drawlmage() в контексте Graphics, пересланного в ваш метод Canvas, paint (Graphics g), выражается в результате в визуализации на дисплее устройства.
Класс Image имеет четыре версии перегрузки метода createlmage(). В таблице 6.7 показаны все четыре версии. Вы уже видели третью версию, эта версия единственная, которая производит изменяемый объект изображения. Это вам необходимо для записи во внеэкранном контексте Graphics объекта Image.
Canvas все еще может выполнять
Клавишные событияКласс Canvas 1 подменяет метод keyReleased() в Canvas. Поскольку объект регистрируется как блок прослушивания событий, он получает клавишные события в ответ на действия пользователя, связанные с клавиатурой.
Нажатие на любую клавишу клавишной панели приводит к формированию двух клавишных событий: событие нажатия клавиши и событие отпускания клавиши. Эта программа выводит информацию о событиях отпускания клавиши. Информация о клавишном событии включает название клавиши, код клавиши и, возможно, связанное с ним обозначение игрового действия.
Название клавиши является String, которая представляет собой удобное для чтения представление клавиши, обычно сходное (если не такое же) с текстом, физически напечатанным на клавише устройства. Код клавиши является целым числом, чье значение однозначно представляет каждую клавишу. Для стандартных клавиш ITU-T, а именно от 0 до 9, * и #, код клавиши является значением уникода символа.
Программы должны использовать предписанные константы класса Canvas вместо значений уникода нажатой клавиши при проверке того, какая клавиша была нажата. Такой подход делает вашу программу более транспортабельной. Класс Canvas определяет константы для каждого из кодов клавиш, показанные в таблице 6.2.
Чтобы нарисовать текст укажите
Листинг 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.8. После перемещения координаты, указанные процедурам рисования Graphics, не изменяются, поскольку они всегда связаны с началом координат контекста Graphics, а не дисплеяimport javax.microedition.Icdui.Canvas;
import javax.microedition.Icdui.Command;
import javax.microedition.Icdui.CommandListener;
import javax.microedition.Icdui.Display;
import javax.microedition.Icdui.Displayable;
import javax.microedition.Icdui.Graphics;
/**
Демонстрирует преобразование контекста Graphics в Canvas.
(Усмотри javax.microedition.lcdui. Graphics
*/ public class TranslationDemo extends Canvas
implements CommandListener
{
private final int WHITE = OxFF " 16 I OxFF " 8 | OxFF;
private GraphicsDemo gDemo = GraphicsDemo.getlnstance ();
private Display display = Display.getDisplay(gDemo);
private static Command back = new Command("Back", Command.BACK, 1);
private static Command go = new Command("Go", Command.SCREEN, 1);
private static final int ORIGINAL_STATE = 1;
private static final int TRANSLATED_STATE = -1;
// Координата х начального рисунка, private int x = 20;
// Координата у начального рисунка, private int у = 20;
// Величина переноса в направлении х. private int deltaX = 30;
// Величина переноса в направлении у. private int deltaY = 30;
// Задает переменную, которая сообщает программе, рисовать ли на экране
// в первоначальной позиции или в преобразованной позиции,
private int state = ORIGINAL_STATE;
/**
Конструктор.
*/
public TranslationDemo()
{
super () ;
addCommand(back);
addCommand(go);
setCommandListener (this);
display.setCurrent(this);
}
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) ;
}
public void paint(Graphics g)
{
int w = 50;
int h = 50;
paintClipRect(g); g.fillRect(x, y, w, h);
}
// Переключает режим рисования. Этот метод вызывается во время
// обработки команды "Go", которая переключает перемещение.
private void toggleState()
{
state = -state;
}
// Переключает преобразование. Перерисовывает заново Canvas.
private void toggleTranslation()
}
if (state == ORIGINAL_STATE)
x = x + deltaX; у = у т deltaY;
}
else
{
x = x - deltaX;
у = у - deltaY; 1 toggleState();
// Запрашивает у реализации вызов метода paint() для восстановления
// Canvas. Это выражается в генерировании внутреннего события
// рисования, которое обрабатывается реализацией, repaint () ;
*/
public void commandAction(Command c, Displayable d)
{
if (с == back)
GraphicsDemo.getInstanced.display!);
}
else if (c == go)
{
toggleTranslation() ;
}
}
}
Как вы узнали в предыдущем разделе, вы можете рисовать за пределами границ объекта Graphics, однако такое рисование не будет формировать изображение на экране. Но после выполнения внеэкранного рисования вы можете преобразовать Graphics для того, чтобы отобразить предыдущий внеэкранный рисунок.
Двойная буферизация
Двойная буферизацияТермин двойная буферизация относится к технике буферизации графического контекста перед его отображением. Эта идиома требует, чтобы вы использовали два графических контекста - или буфера - отсюда ее название.
Вы сначала рисуете графические данные во вторичном графическом контексте, а затем копируете его содержимое в графический контекст, представленный дисплеем устройства. Этот вторичный графический контекст называется внеэкранным буфером. Внеэкранный буфер не отображает на дисплее.
Смысл этой технологии заключается в ограниченности производительности. Операции по рисованию могут в результате привести к быстрым обновлениям дисплея, заставляя пользователя воспринимать мерцание. Чтобы избежать мерцания, вы должны сначала выполнить ваше рисование во внеэкранной графической среде, а затем скопировать весь внеэкранный графический контекст в оригинальную графику устройства. Операция копирования обычно быстрее, чем множество операций по рисованию, требуемых даже относительно сложными Canvas, так что это будет сделано практически без заметного мерцания.
В листинге 6.9 демонстрируется использование двойной буферизации. Код выполняет несколько простых функций рисования во внеэкранном буфере, затем копирует содержимое этого буфера в саму графическую среду, которая представляет дисплей устройства. Хотя процедуры рисования в этом примере относительно просты, реальное приложение может выполнять намного более сложное рисование, действительно подтверждая необходимость двойной буферизации.
Графическая модель
Графическая модельКласс 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.
Игровые действия
Игровые действияВ дополнение к константам, которые вы уже видели, класс 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.
Как и другие геометрические фигуры
Листинг 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.
Kaк рисуются компоненты
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 предоставляет операции по рисованию и заливке следующих типов геометрических фигур:
Линии. На рисунке 6.4 показаны линии, нарисованные в Canvas.
Класс Graphics
Класс GraphicsКласс Graphics поддерживает следующие абстракции:
public int isColorO
public int numColors()
так что вы можете получить информацию о поддержке данным устройством цвета и количестве предоставляемых цветов или поддержке какого-либо числа уровней шкалы серого цвета для устройств, не поддерживающих цвет.
Первостепенной абстракцией, определяемой классом Graphics, является представление о Canvas, как о двухмерной сетке точек или пикселей. На рисунке 6.3 представлено схематичное изображение этой области для рисования. Графический контекст определяет эту координатную плоскость (х, у), в которой координаты лежат между пикселями, практически так же, как и курсор вашего любимого текстового редактора всегда лежит между двумя символами.
Левая половина предаавляет состояние
Отображение изображения с помощью CanvasВы уже узнали в главе 5, что некоторые компоненты высокоуровневого пользовательского интерфейса MIDP умеют отображать изображения, например, как часть элемента в ChoiceGroup. Объекты Canvas также могут отображать изображения. Кроме рисования базовых геометрических фигур, объект Canvas может "рисовать" изображения с помощью того же контекста Graphics, который он использует для низкоуровневых функций рисования. MIDP поддерживает только формат изображений PNG.
На рисунке 6.12 показано изображение, отображаемое в Canvas. В листинге 6.10 показана исходная программа, создающая изображение, показанное на рисунке 6.12. Структура программы сходна с другими демонстрационными программами Canvas, приведенными в этой главе.
Демонстрационной программе
Листинг 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, вы должны создать подкласс Canvasimport 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.7. Вы должны стереть все недействительные пиксели, прежде чем рисовать свой компонент. Используйте отсекаемый прямоугольник графического объекта вашего компонента, чтобы определить прямоугольную область, которая содержит все поврежденные пиксели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);
}
Проблема этого метода заключается в том, что он использует отсекаемый прямоугольник объекта Graphics. Отсекаемый прямоугольник является прямоугольной областью, которая содержит все недействительные пиксели экрана. Отсекаемый прямоугольник определяется его отклонением (х, у) от начала координат объекта Graphics, а также его шириной и высотой.
Вы можете получить отсекаемый прямоугольник, вызвав следующие методы Graphics:
int getClipHeight ()
int getClipWidth ()
int getClipX()
int getClipY()
При вызове метода paint (Graphics g) отсекаемый прямоугольник всегда представляет область, которая содержит все поврежденные пиксели дисплея. В случаях, подобных примерам, приведенным в этой главе, где вы заменяете отображение экрана новым, отсекаемый прямоугольник представляет всю область дисплея устройства.
Самый легкий способ "стереть" недействительные пиксели - это перерисовать каждый пиксель в отсекаемом прямоугольнике с помощью цвета фона экрана, таким образом гарантировав, что вы стерли все поврежденные пиксели. Затем вы выполняете операции по рисованию, которые определяются вашим Canvas, с помощью другого цвета.
Обратите внимание, что в листинге 6.7 метод получает и сохраняет текущий цвет дисплея, который представляет цвет ручки, используемый для всех операций рисования. По умолчанию цвет обычно черный в большинстве реализаций. Код затем устанавливает текущий цвет на белый (который обычно является цветом фона) и заполняет отсекаемый прямоугольник белыми пикселями, эффективно "стирая" все поврежденные пиксели. В конце код восстанавливает первоначальный цвет объекта Graphics. Последующие операции по рисованию визуализируют пиксели некоторого цвета, отличающегося от белого, на белом фоне.
В некоторых случаях отсекаемый прямоугольник может представлять некоторую часть дисплея. В этих случаях только некоторая часть дисплея была повреждена. Ваше приложение может выбрать простую перерисовку всего экрана, но ему необязательно делать это. Оно может просто также перерисовать лишь поврежденную область.
Поврежденная область, которая нуждается в перерисовке, является пересечением области, используемой вашим Canvas, и отсекаемым прямоугольником. Вы можете определить, какие из пикселей вашего дисплея относятся к этой области, наложив отсекаемый прямоугольник на область, которую, как вы знаете, ваш Canvas использует для отображения. Метод
void clipRect(int x, int у, int width, int height)
устанавливает отсекаемый прямоугольник как пересечение текущего отсекаемого прямоугольника и прямоугольника, определенного аргументами, - области, используемой вашим Canvas. Ваше приложение может затем вычислить, какие из пикселей относятся к этому новому отсекаемому прямоугольнику, и перерисовать их.
Вызов clipRect () всегда создает отсекаемый прямоугольник меньшего размера. Вы можете также установить любой размер отсекаемого прямоугольника с помощью следующего вызова:
setClipfint x, int у, int width, int height)
Ваш Canvas нуждается в перерисовке только тех пикселей, которые подпадают под область пересечения, поскольку отсекаемый прямоугольник гарантирует включение всех поврежденных пикселей. Конечно, вычисление этой области может быть более сложным, чем простая перерисовка всего Canvas. Но закрашивание только отсекаемого прямоугольника полезно для приложений, которые используют сложную или отнимающую много времени обработку при вычислении того, какие пиксели нужно закрашивать.
Другим стандартным применением отсечения является разработка игр. Стандартное приложение является каркасом, в котором вы хотите переносить фантом, являющийся небольшим изображением или значком. Используя область отсечения, как показано в листинге 6.7, вы рисуете фон области, где фантом расположен в настоящее время, а затем вы рисуете фантом в его новой позиции.
На самом деле на реальном устройстве, которое не поддерживает двойной буферизации, реализация, показанная в листинге 6.7, может производить довольно заметное и разрушительное мерцание экрана при его обновлении. Вы, вероятно, не заметите никакой вспышки лри использовании эмулятора из-за скорости вашего компьютера. В разделе "Двойная буферизация" далее в этой главе вам будет показано, как справиться с этой проблемой.
Рисование - это процесс изменения состояния объекта Graphics. Визуализация - это процесс отображения закрашенных пикселей на экране. Вы никогда не сможете формировать изображение за пределами отсекаемого прямоугольника. Координаты, переданные процедурам рисования, всегда являются интерпретированными по отношению к первоначальному отсекаемому прямоугольнику. Операции по рисованию, которые лежат вне границ отсекаемого прямоугольника, не влияют на визуализацию, они не появляются на экране. Отрицательные значения координат х и у относятся к пикселям, лежащим за пределами отсекаемого прямоугольника.
Хотя вы никогда не сможете формировать изображение за пределами отсекаемого прямоугольника, вы можете рисовать где угодно, даже за пределами отсекаемого прямоугольника. Вы можете даже рисовать за границами обьекта Graphics. Вы можете реализовать панорамирование или перемещение изображения, изменяя координаты х и у начала координат при рисовании.
Двойная буферизация
Листинг 6.9. Двойная буферизация использует два графических контекста. Единственный способ получить второй графический контекст в МЮР - через класс Imageimport 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 () для получения внеэкранного графического контекста. Приложение может рисовать в этом контексте. Оно затем связывает этот внеэкранный графический контекст с самим устройством.
Объекты Canvas отображаются на
Oбработка команд и событийВ компоненте Canvas вы можете добавлять и удалять высокоуровневые команды и устанавливать один блок прослушивания команд на Canvas, как и в других отображаемых компонентах. Canvas также может внедрять CommandListener и регистрироваться как свой собственный блок прослушивания.
Однако, в дополнение к обработке высокоуровневых команд, класс Canvas также обрабатывает низкоуровневые команды. Компоненты Canvas сами являются источниками низкоуровневых событий клавиш и указателя, которые генерируются действиями пользователя по вводу с клавиатуры и перемещением указателя на устройстве. Они также являются своими собственными блоками прослушивания низкоуровневых событий. Класс Canvas определяет интерфейс для обработки низкоуровневых событий как часть своего собственного API, другого интерфейса блока прослушивания не реализуется.
Реализация MIDP передает информацию о событии низкого уровня объекту Canvas, вызывая соответствующий метод в объекте Canvas. В таблице 6.1 перечислены возможные методы.
Отсечение областей для рисования
Отсечение областей для рисованияКогда ваше приложение вызывает метод Display.setCurrent(), он запрашивает реализацию об отображении вашего Displayable. Для объектов Canvas реализация делает ваш компонент текущим отображаемым и вызывает метод вашего класса paint (Graphics g). Реализация генерирует внутреннее событие рисования, которое пересылается в текущий отображаемый элемент. В этом кроется причина того, что метод paint() указан в таблице 6.1 как часть обработки событий API, определяемой Canvas.
Во время отображения некоторая группа пикселей дисплея может быть недействительной или поврежденной. Недействительный или поврежденный пиксель - это тот, который видим в результате предыдущей операции рисования, но не должен быть визуализирован в качестве части текущей операции рисования. Дисплей может быть поврежден другим MID-летом или даже "внешним" приложением - например, приложением по передаче сообщений, которое обновляет дисплей для отображения получения сообщения SMS вашим мобильным телефоном.
Перед тем как приступить к самому рисованию, ваш Canvas обязан стереть все пиксели, появившиеся на экране, которые не должны быть частью его внешнего вида. Вы восстанавливаете экран, обновляя недействительные пиксели.
Вы, несомненно, уже обратили внимание на наличие метода paintClipRect (Graphics g) в листинге 6.3. В листинге 6.7 повторяется этот метод. Это первый код, вызываемый методом paint (Graphics g) каждого приложения. Его цель состоит в удалении всех пикселей, которые были нарисованы предыдущей операцией рисования.
Преобразование
ПреобразованиеКак вы уже знаете, точка (х, у) указывает функции рисования место, расположенное относительно точки (0, 0). Точка (0, 0) является началом координат Graphics. Когда вы впервые получите ссылку на Graphics вашего Canvas, его начало координат, точка (О, О), всегда представляет верхний левый угол дисплея устройства.
Преобразование Graphics означает перенос его начала координат. После перемещения начало координат Graphics представляет некоторую точку, отличную от левого верхнего пикселя. Вы переводите начало координат Graphics с помощью метода
void translate(int x, int у)
Аргументы являются координатами точки, которая станет новым началом координат объекта Graphics. Точка (0, 0) теперь является этим новым началом координат. Все операции по рисованию теперь относятся к этому новому началу координат. На рисунке 6.9 показан экран, созданный кодом, описанным в листинге 6.8. Он просто рисует заполненный квадрат в Canvas.
При нажатии на кнопку Go начало координат Graphics переносится, а затем заполненный квадрат перерисовывается. На рисунке 6.10 показан обновленный дисплей после того, как кнопка Go была нажата в первый раз. Обратите внимание, что координаты, переданные вызовам методов рисования в методе paint (Graphics g) не изменились. Причина этого кроется в том, что эти координаты всегда связаны с началом координат Graphics, а не с верхним левым углом области дисплея устройства. Операции по рисованию всегда указываются относительно начала координат Graphics, безотносительно к точке места назначения, которое она представляет.
Нажатием на кнопку Go вы на самом деле переключаете перемещение. Нажатие на кнопку во второй раз перемещает начало координат назад к верхнему левому углу дисплея.
Прямоугольники как и все геометрические
Листинг 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.
Методы уведомления
| Название метода | Описание |
| 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 на перерисовку |
В листингах 6.1 и 6.2 представлена простая схема обработки команд и событий в Canvas. Код в листинге 6.1 является кодом MID-лета для демонстрационной программы, большая часть которой выглядит знакомо. Код в листинге 6.2, однако, создает подкласс Canvas - Displayable, который согласно коду, показанному в листинге 6.1, размещается на экране.
Константы класса Canvas
| Константа класса 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 | Представляет клавишу # клавишной панели |
Константы класса 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 | Представляет клавишу панели со стрелкой запуска (выбора] на мобильных устройствах |
Методы класса 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.5 описаны представляющие их константы класса Graphics. Они описывают public static final int.
Графические константы
| Константа привязки | Описание |
| static int LEFT | Размещает левый край у координаты х |
| static int HCENTER | Размещает горизонтальный центр у координаты х |
| static int RIGHT | Размещает правый край у координаты х |
| static int TOP | Размещает верх у координаты у |
| static int BASELINE | Размещает нижнюю строку текста у координаты у |
| static int BOTTOM | Размещает низ ограничивающего прямоугольника у координаты у |
| static int VCENTER | Только для изображений, размещает вертикальный центр изображения у координаты у |
На рисунке 6.8 показан некоторый текст, отображаемый на Canvas, а в листинге 6.6 показан метод paint (Graphics g) исходного кода, который его отображает.
Графические константы
| Константа атрибута | Описание |
| 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 | Значение атрибута размера |
В отличие от 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 шрифт был изменен до создания различных текстовых строк или символов для достижения желаемого эффекта.
Методы класса Image
| Название метода изображения | Описание |
| static Image createlmage (byte [] imageData, int imageOffset, int imageLength) | Создает изменяемое изображение из указанных данных изображения, беря изображения начиная с указанных смещения и длины |
| static Image createlmage (Image source) | Создает изменяемую копию указанного изображения |
| static Image createlmage (int width, int height) | Создает новое изменяемое изображение с указанной шириной и длиной |
| static Image createlmage (String name) |
Создает изменяемый объект изображения из изображения с путем к ресурсам, указанным в файле JAR набора МЮ-летов |
В листинге 6.10 демонстрируется отображение реального изображения PNG. Вместо рисования изображений - рисунков, хранящихся как изображения в формате PNG, - вы можете нарисовать любую "картинку", которую вы сможете создать с помощью низкоуровневых процедур графического рисования, предоставляемых в классе Graphics. Вы можете рисовать геометрические фигуры или отдельные пиксели, заполнять части дисплея и так далее, чтобы создать изображение - рисунок - по, своему желанию.
Двойная буферизация изображений. Изображения подвергаются двойной буферизации неявно. Поэтому вам никогда не придется самостоятельно выполнять двойную буферизацию. Пример, описанный в листинге 6.10, раскрывает причину этого.
Метод paint () создает объект Image из файла ресурса, который представляет изображение PNG для отображения. Но этот объект Image уже имеет связанный с ним контекст Graphics, являющийся внеэкранным Graphics. Поэтому, когда метод paint () выполняет следующий оператор, он копирует содержимое контекста Graphics объекта Image - фактические биты, которые составляют изображение, - в графический контекст дисплея:
g.drawlmage (image, О, О, Graphics.TOP I Graphics.LEFT);
Таким образом, двойная буферизация изображений осуществляется автоматически.
Хотя при рисовании изображения двойная буферизация осуществляется автоматически, очистка отсекаемого прямоугольника, то есть рисование фона Canvas, - нет. Посмотрите внимательнее на метод paint (Graphics д)в листинге 6.10, и вы увидите, что он все еще проверяет, не осуществляет ли реализация автоматическую двойную буферизацию. Если нет, метод paint (Graphics g) использует внеэкранный графический контекст для очистки отсекаемого прямоугольника.
Этот код немного отличается от кода, описанного в листинге 6.9, в этом коде нет явной ссылки на внеэкранный Graphics. Причина этого заключается в том, что объект Image уже предоставил внеэкранную графику. Метод paint (Graphics g) может просто использовать ее как внеэкранный Graphics, необходимый для очистки отсекаемого прямоугольника.
Вы можете рисовать линии в Canvas
Листинг 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 показаны несколько прилегающих прямоугольников.
Платформа программирования J2ME для портативных устройств
Блоки прослушивания записей
Блоки прослушивания записейПриложения имеют способность получать уведомления при добавлении записи, ее удалении или изменении в хранилище записей. Класс RecordStore позволяет вам добавлять и удалять блоки прослушивания записей из определенного хранилища данных с помощью методов, перечисленных в таблице 7.2. Блок прослушивания записей является любым классом, реализующим интерфейс RecordListener, определенный в пакете javax.microedition.rms. Он объявляет три метода, показанных в таблице 7.3.
Cпиcки
CпиcкиСуществует на самом деле два способа извлечения записей из хранилища данных:
byte [] getRecord(int recordld)
Этот метод, очевидно, требует, чтобы вы знали уникальный ID записи, которую вы хотите извлечь. К сожалению, это означает, что вам, возможно, придется хранить ID где-нибудь в легкодоступном месте после того, как он будет выдан вам методом addRecord (). Это не всегда удобно или практично при большом количестве записей.
Самый легкий способ найти записи, которые вам нужны, - это использовать списки, которые поддерживаются классом RecordStore. Список весьма удобен при извлечении записей, если вы не знаете ID записей, которые вам нужны. Вы можете создать список записей, хранящихся в хранилище записей, а затем исследовать его, выбрав одну или несколько записей, которые вам нужны.
Класс RecordStore определяет метод
RecordEnumeration
enumerateRecords(RecordFilter filter,
RecordComparator comparator,
boolean keepUpdated)
который выдает список записей в хранилище записей. В листинге 7.2 показан исходный код RecordList.Java. Этот класс создает и отображает список всех записей адресной книги. Обратите внимание, что для того, чтобы извлекать записи, ID записей указывать не нужно.
Фильтры записей
Фильтры записейСледующий пример не осуществляет поиска определенных записей. Однако существует способ, при котором вы можете использовать списки для извлечения некоторого подмножества записей хранилища. Вы можете использовать списки для вывода записей, которые удовлетворяют некоторым критериям, которые вы указали.
Первый аргумент в методе enuraerateRecords() указывает фильтр записей. Фильтр является объектом, определяющим семантику соответствия записи набору критериев, которые определяют, должна ли запись включаться в набор списка.
Фильтр записей является классом, реализующим интерфейс RecordFilter, который определяется в пакете javax.microedition.rms. Этот интерфейс определяет единственный метод boolean matches (byte [] candidate). Ваш подкласс RecordFilter задает этот метод и устанавливает критерии фильтрации записей, указанных в списке всех записей хранилища записей. Метод enumerateRecords() активизирует вашу реализацию на каждой записи, извлеченной из хранилища записей.
В листинге 7.3 показан код класса SearchScreen. Java. Он ищет записи, которые начинаются с подстроки, введенной пользователем, или эквивалентные указанной пользователем строке.
Компараторы записей
Компараторы записейВы, несомненно, заметили, что второй аргумент, пересланный в enumerateRecords () в предыдущих примерах, был равен нулю. Этот второй параметр является "заполнителем" для компаратора записей. Компаратор записей - это объект, который сравнивает две записи для определения их упорядочивания или сортировки. Компараторы предоставляют приложениям возможность выполнять различную сортировку.
Как и фильтры, компараторы определяют семантику функции сравнения. Компаратор записей является реализацией интерфейса RecordComparator, который определяет единственный метод
int ccmparefbyte [] recordl, byte [] record2)
Компаратор также определяет три константы, описанные в таблице 7.1, которые ваша реализация должна использовать как текущие выводимые значения данного метода.
Класс AddressBook
Листинг 7.1. Класс AddressBook позволяет приложению получать доступ к хранилищу записейimport javax.microedition.rms.RecordComparator;
import javax.microedition.rms.RecordEnumeration;
import javax.microedition.rms.RecordFilter;
import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;
import javax.microedition.rms.RecordStoreNotOpenException;
import Java.io.ByteArrayInputStream/
import java.io.ByteArrayOutputStream;
import Java.io.DatalnputStream;
import java.io.DataOutputStream;
import Java.io.lOException;
/**
Этот класс внедряет простую адресную книгу с целью демонстрации.
В нем хранятся записи, состоящие из полей имени String и номера телефона String.
Этот класс определяет два внутренних класса,
один является блоком сравнения записей, а второй фильтром записей,
используемым при извлечении записей.
*/
public class AddressBook
private static final String RECORD_STORE_NAME = "address-book";
private RecordStore recordStore;
public AddressBook () throws RecordStoreException
super!);
recordStore = RecordStore.openRecordStore(RECORD_STORE_NAME, true);
{
void close() throws RecordStoreException
{
try
{
recordStore.closeRecordStore();
}
catch (RecordStoreNotOpenException rsno)
{
}
}
/*
Получает хранилище записей, используемое этим объектом.
@возвращаем ссылку на RecordStore, используемый э.тим объектом.
public RecordStore getRecordStore()
}
return recordStore;
/**
Добавляет указанную запись в хранилище записей данной адресной книги.
@param name имя входа было добавлено.
@parara phone телефонный номер для входа был добавлен.
@сбрасывает RecordStoreException, если есть проблемы с добавлением записи.
public void addRecord(String name, String phone)
throws RecordStoreException
}
ByteArrayOutputStreara baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
try
dos.writeUTF(name);
dos.writeUTF(phone);
}
catch (lOException ioe)
{
ioe.printStackTracef);
)
int id =
recordstore.addRecord(baos.toByteArray(), 0,
baos.toByteArrayO .lengthy-System, out. println ("Record id = " + id);
}
/**
RecordEnumerator, упорядочивающий записи в
лексикографическом порядке по полям
имен записей.
*/
RecordEnumeration getMatchesByNarae(String matchKey)
throws RecordStoreNotOpenException
(
MacchAllNaraesFilter filter =
new MatchAllNamesFilter(matchKey);
AlphabeticalOrdering comparator =
new AlphabeticalOrdering();
return recordStore.enuraerateRecords(filter,
comparator, false);
}
/**
RecordFilter, устанавливающий совпадение, если имя варианта
(первое поле в записи варианта)!) точно соответствует имени
элемента списка или 2) если строка имени элемента списка
начинается с имени варианта. Возвращает значение true, ее
установлено соответствие, false - в ином случае.
*/
class MatchAllNamesFilter implements RecordFilter
{
String requestString;
public MatchAllNamesFilter(String matchKey) ;
requestString = matchKey;
}
public boolean matches(byte [] candidate)
{
ByteArraylnputStream bais =
new ByteArraylnputStream(candidate);
DatalnputStream dis = new DatalnputStream(bais);
Siring name = null;
try
}
name = dis.readUTF();
if (name.indexOf(requestString) == 0)
return true;
else
return false;
}
catch (lOException ioe)
{
ioe.printStackTrace!);
return true;
}
}
/**
Этот внутренний класс реализует RecordCornparator, чья политика
Заключается в выполнении сортировки по алфавиту.
*/
class AlphabeticalOrdering implements RecordCoraparator
}
Конструктор.
public AlphabeticalOrdering ()
(
)
public int compare(byte [] reel, byte [] rec2)
{
ByteArraylnputStream baisl =
new ByteArraylnputStream(recl);
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 (lOException ioe)
ioe.printStackTrace();
}
if (namel == null II name2 == null) return 0;
int result = namel.compareTo(name2);
if (result < 0)
return RecordCornparator. PRECEDES;
else if (result == 0)
return RecordCoraparator.EQUIVALENT;
else
return RecordComparator.FOLLOWS;
}
}
/**
Удаляет все записи из хранилища данных.
В текущих реализациях самый быстрый способ удаления всех записей
заключается в удалении хранилища данных и повторном его создании,
вместо удаления каждой записи по очереди!
void deleteAHRecords ()
}
try
RecordEnumeration re =
recordStore.enumerateRecords(null, null, false);
while (re.hasNextElement())
*/
int id = re.nextRecordld();
recordStore.deleteRecord(id);
}
}
catch (RecordStoreException rse)
{
rse.printStackTracel);
} }
/**
Получает статистику хранилища данных, используемого данной адресной книгой.
/**
возвращает String статистических данных.
*/
public String getStatistics ()
{
int numRecords = 0;
int space = 0;
StringBuffer stats = new StringBuffer("Records:
*/
try
{
numRecords = recordStore.getNumRecords ();
space = recordStore.getSizeAvailable();
)
catch (RecordStoreException rse)
(
rse.printStackTrace();
}
stats.append(String.valueOf(nuraRecords));
stats.append("\n\n") ;
stats.append("Available bytes: ");
stats.append(String.valueOf(space));
return stats . toString();
}
}
Обратите внимание, что класс AddressBook определяет член типа RecordStore. Это экземпляр действительного хранилища записей, используемого приложением. Класс RecordStore является единственным открыто объявляемым классом в пакете RMS. Он определяет абстракцию хранилища записей.
Конструктор AddressBook сбрасывает RecordStoreException, поскольку метод openRecordStore() может сбрасывать три исключения, которые происходят от него. Пакет javax.microedition.rras определяет пять исключений. На рисунке 7.2 показана иерархия наследования, которая содержит типы исключений RMS.
Списки дают вам возможность
Листинг 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.
Поиск имен которые
Листинг 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 и используют внутренний класс, соответствующий осуществляемому поиску.
Этот компаратор записей
Листинг 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 при извлечении списка всех записей.
Модель хранения данных RMS
Модель хранения данных RMSRMS поддерживает создание множества хранилищ записей, показанных на рисунке 7.1, и управление ими. Хранилище записей - это база данных, основным понятием которой является запись. Каждое хранилище записей содержит ноль или больше записей. Название хранилища записей чувствительно к регистру и может состоять максимум из 32 знаков уникода. Хранилище записей создается МШ-летом.
Пакет RMS определяет несколько
Работа с данными byte [ ]Как уже упоминалось выше, приложение в этом примере работает с записями, которые содержат имя и номер телефона. Пользователь вводит как имена, так и телефонные номера как объекты String, поскольку экран ввода данных использует экземпляры класса TextField, описанный ранее в главе 5. Соответственно, метод addRecord () получает эти значения String и преобразует их в байты.
Так или иначе, эти значения должны быть преобразованы в один массив байтов перед добавлением в хранилище данных. Причина того, что вы должны выполнить это преобразование, заключается в том, что API RecordStore хранит записи только в виде однобайтового массива.
Метод addRecord () использует стандартную идиому ввода-вывода Java при создании DatalnputStream, который поддерживает запись встроенных типов Java в выходном потоке. Получающийся в результате байтовый массив затем добавляется в объект RecordStore.
Метод RecordStore.addRecord() возвращает int, которая представляет значение ID только что созданной записи. Ваше приложение может сохранить данный ID и использовать его при последующем извлечении записи. Но существует более интересный способ извлечения записей.
Поддержка постоянного хранения устройством
Поддержка постоянного хранения устройствомКаждое соответствующее требованиям MIDP устройство поддерживает выделенную область памяти для постоянного хранения данных приложения. Данные MID-лета, хранящиеся там, постоянно существуют при множестве инициализаций приложения, которое их использует. Как физическое местоположение, так и размер хранилища данных зависят от устройства.
RMS API извлекает подробную информацию об области хранения устройства и доступе к этой информации, а также предоставляет единообразный механизм для создания, уничтожения и изменения данных. Это гарантирует переносимость MID-летов на различные устройства.
Пример приложения
Пример приложенияВ остальной части этой главы описываются частные подробности RMS с помощью следующего примера, использующего базовые свойства RMS. Этот пример является простой адресной книгой, которая хранит имена и номера телефонов.
Многие из примеров имеют дело с созданием организации и структуры приложений MIDP. Большинство протекающих операций RMS ограничены одним классом. В этом примере вы можете видеть, как включать использование постоянного хранения в приложение, которое вы, вероятно, найдете на настоящем мобильном устройстве.
Конечно, вы можете создать и исполнить исходный код, приведенный в этой главе, для получения представления о том, как приложение продвигается вперед по различным экранам. Я оставляю это на ваше усмотрение вместо того, чтобы показывать вам здесь изображения всех этих экранов.
Следующие файлы включены в адресную книгу, описанную в данном примере:
В листинге 7.1 показан полный исходный код класса AddressBook.java. Этот класс извлекает подробную информацию о вызовах RMS API из остальной части МID-лета. При инициализации MID-лета он создает экземпляр класса AddressBook, который, в свою очередь, открывает хранилище записей с именем addressbook.
Различные свойства хранилищ записей
Различные свойства хранилищ записейКласс RecordStore определяет несколько других свойств, которые полезны для приложений. В таблице 7.4 перечислены некоторые из других методов класса RecordStore и кратко описано их использование.
RMS состоит из одного или нескольких
ЗаписиЗапись является массивом байтов типа byte []. RMS не поддерживает описание или форматирование полей записи. Ваше приложение должно определять элементы данных записи и их формат.
Читатель записи поэтому должен знать формат, который использовался при ее создании. Поскольку запись является просто массивом байтов, приложения должны преобразовывать данные из произвольных типов в байты при создании записей, а затем преобразовывать их из байтов в типы при чтении данных.
Константы RecordComparator
| Константа | Описание |
| public static int EQUIVALENT | Две записи эквивалентны в соответствии с семантикой сравнения |
| public static int FOLLOWS | Запись 1 "больше", чем запись 2, в соответствии с семантикой сравнения |
| public static int PRECEDES | Запись 1 "меньше", чем запись 2, в соответствии с семантикой сравнения |
В листинге 7.4 демонстрируется использование компаратора записей. Он определяет новый внутренний класс класса AddressBook, который вы видели в листинге 7.1. Новый внутренний класс AlphabeticalOrdering реализует RecordComparator. Его метод сравнения извлекает поле имени из каждого параметра байтового массива и сравнивает их лексикографически (по словам).
Методы поддержки блока
| Название метода RecordStore | Описание |
| Void addRecordListener (RecordListener listener) | Делает указанный объект блоком прослушивания для данного хранилища записей |
| Void removeRecordListener (RecordListener listener) | Удаляет указанный блок прослушивания как блок прослушивания данного хранилища записей |
Методы интерфейса RecordListener
| Название метода RecordListener | Описание |
| void recordAdded (RecordStore recordStore, int recordld) |
Уведомляет блок прослушивания записей о том, что запись была добавлена в указанное хранилище записей с указанным ID |
| void recordChanged (RecordStore recordStore, int recordld) | Уведомляет блок прослушивания записей о том, что запись с указанным ID была изменена в хранилище записей |
| void recordDeleted(RecordStore recordStore, int recordld) | Уведомляет блок прослушивания записей о том, что запись с указанным ID была удалена из хранилища записей |
Методы класса RecordStore
| Название метода | Описание |
| void- closeRecordStore ( ) | Закрывает хранилище записей |
| static void deleteRecordStore ( ) | Удаляет хранилище записей |
| long getLastModif ied ( ) | Выдает время последней модификации |
| String getNameO | Выдает название хранилища записей |
| int getNumRecords () | Выдает число записей в хранилище |
| byte [] getRecordfint recordld) | Извлекает запись по Ю |
| byte [] getRecord(int recordld, byte [] buffer, int offset) | Получает запись и помещает ее в предоставленный буфер |
| byte [] getRecordSize (int recordld) | Получает размер указанной записи |
| int getSizef) | Выдает размер места (в байтах), которое занимает хранилище записей |
| int getSizeAvailable ( ) | Выдает число оставшихся байтов, на которое хранилище записей может вырасти |
| int getVersionf) | Выдает номер версии хранилища записей |
| static String [] listRecordStores () | Выдает список всех хранилищ записей, доступных набору MID-летов |
| static RecordStore openRecordStore (String name, boolean createlfNecessary) | Открывает указанное хранилище записей, создавая его, если оно не существует |
Платформа программирования J2ME для портативных устройств
Блоки соединения и соединения
Блоки соединения и соединенияНа рисунке 8.1 представлено схематичное изображение этапов, входящих в процесс создания и использования соединения. Эти этапы, которые мы перечислим позже, соотносятся с условным обозначением, показанным на рисунке 8.1.
Объект соединения содержит входной и выходной потоки для считывания и записи данных для ресурса, соответственно. На рисунке 8.1 схематично представлены взаимосвязи между соединением и его двумя потоками.
Cтpyктypa общих соединений MIDP
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:
Дейтаграммные соединения и дейтаграммы
Дейтаграммные соединения и дейтаграммыИнтерфейс 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, протоколы дейтаграмм не определяют атрибуты, которые отражают природу полезной нагрузки, которую они транспортируют.
Программа ConnectionDemo
Листинг 8.1. Программа ConnectionDemo определяет MID-лет, который отображает мета-информацию протокола HTTP, а именно значения полей заголовка HTTP. Программа использует команду HEAD для получения лишь мета информации, а не всей страницыimport javax.microedition.midlet.MI Diet;
import javax.microedition.lcdui.Display;
Этот класс определяет MID-лет для демонстрационной программы, которая
запрашивает у пользователя URI, затем создает соединение HTTP с исходным
сервером и извлекает ресурс. Программа использует
объект Form, для того чтобы дать пользователю возможность ввести URI.
*/
public class ConnectionDemo extends MID-лет
}
private static ConnectionDemo instance;
private URIEntry urlForm; public ConnectionDemo()
super();
instance = this; }
/**
Возвращает один экземпляр класса.
Вызов этого метода до создания объекта возвращает нулевой указатель.
@возвращаем экземпляр данного класса,
public static ConnectionDemo getlnstance ()
return instance;
}
public void startApp()
Display display;
URIEntry urlForm = URIEntry.getlnstance();
display = Display.getDisplay(this);
display.setCurrentlurlForm);
}
public void pauseApp()
}
}
void quit ()
destroyApp(true);
notifyDestroyedf) ;
}
public void destroyApp(boolean destroy)
{
instance = null;
/**
Устанавливает данный объект в качестве текущего отображаемого
объекта MID- лета .
*/
public void display()
Display.getDisplay(this).setCurrent(urlForm);
}
}
Класс URIEntry описывает
Листинг 8.2. Класс URIEntry описывает форму, которая приглашает пользователя ввести URIimport: javax.micrcedition.midlet.MIDlet;
import javax.microedition.Icdui.Command;
import javax.microedition.Icdui.CommandListener;
import javax.raicroedition.Icdui.Display;
import javax.microedition.Icdui.Displayable;
import javax.microedition.Icdui.Form;
import javax.microedition.Icdui.TextField;
/**
Этот класс задает Form, приглашающую пользователя ввести URI,
с которым должно быть установлено соединение HTTP.
Пользователь вводит URI и нажимает командную кнопку "Go".
Экземпляр данного класса затем создает экземпляр класса ResourceDisplay,
который выполняет обязанности извлечения ресурса HTTP и его отображения.
*/
public class URIEntry extends Form implements CommandListener
}
private static Command go =
new Command("Go", Command.SCREEN, 1);
private static Command exit =
new CommandCExit", Command. EXIT, 1) ;
private static URIEntry instance;
// URI, введенный пользователем, private TextField uri;
// Нить, контролирующая выполнение объекта
// ResourceDisplay. private Thread thread;
/**
Конструктор.
@param title заголовок Form.
*/
private URIEntry(String title)
}
super(title);
instance = this;
uri = new TextField. ("Connect to:",
null, 70,
TextField.URL);
uri.setStringf'http://") ; append (uri) ;
addCommand(go);
addCommand(exit);
setCommandListener(this);
}
/**
Выдает один экземпляр данного класса.
^возвращение экземпляра данного класса.
*/
public static URIEntry getlnstance ()
}
if (instance == null)
{
instance = new URIEntry("Enter URL");
}
return instance;
}
/**
Устанавливает этот объект в качестве текущего отображаемого
объекта MID-лета.
*/
public void display()
MIDlet га = ConnectionDemo.getInstance();
Display.getDisplay(m).setCurrent(this);
}
public void commandAction(Command c, Displayable d)
}
if (c == go)
}
// Этот экран отображает метаинформацию ресурса,
// указанного с помощью URI.
ResourceDisplay view =
new ResourceDisplay(uri.getString());
MIDlet m = ConnectionDemo.getInstar.ee ();
Display.getDisplay(m).setCurrent(view);
thread = new Thread(view);
thread.start();
}
else if (c == e\it)
}
ConnectionDemo.getlnstance().quit();
}
}
}
Класс ResourceDisplay
Листинг 8.3. Класс ResourceDisplay определяет форму, которая отображает ресурс. Он использует объект helper для получения этого ресурсаimport javdx.microedition.lcdui.Command;
import javax.microedition.Icdui.CommandListener;
import javax.microedition.Icdui.Form;
import javax.microedition.Icdui.Displayable;
/**
Данный класс задает Form, которая отображает метаинформацию,
описывающую HTTP-ресурс. Она контролируется отдельной нитью,
поэтому она реализует Runnable.
Этот объект Form использует объект helper для коммуникации с HTTP-ресурсом
через Connection. Он затем забирает данные соединения
из объекта helper для отображения на экране для пользователя.
public class ResourceDisplay extends Form
implements CommandListener, Runnable
{
private static Command back =
new Command("Back", Command.BACK, 1);
private static Displayable instance;
// Объект helper создает соединение с ресурсом на исходном
// сервере и извлекает метаинформацию ресурса.
// private HttpResource resource;
Конструктор.
Sparam uri URI ресурса для извлечения по запросу HTTP протокола.
*/
public ResourceDisplay(String uri)
{
super("Http Info");
instance = this;
resource = new HttpResource(uri);
addCommand(back);
setCommandListener(this);
}
/**
Запускает выполнение данного объекта:
запускает объект helper HttpResource.
@смотри . rtpResource
*/
public void run()
{
resource.run();
append(resource.getResourceMetalnfo());
}
/**
Возвращает один экземпляр данного класса.
Вызов этого метода перед созданием объекта возвращает нулевой указатель.
@возвращаем экземпляр данного класса.
*/
public static Displayable getlnstance ()
{
return instance;
{
public void commandAction(Command c, Displayable d)
{
if (c == back)
{
URI Entry, get Instanced .display();
}
}
}
Класс HttpResource
Листинг 8.4. Класс HttpResource определяет объект, который на самом деле извлекает сетевой ресурсimport Java.io.InputStream;
import Java.io.lOException;
import javax.microedition.io.Connect ion;
import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;
import javax.microedition.Icdui.Displayable;
/**
Данный класс определяет объект helper, используемый классом
ResourceDisplay. Он создает соединение с ресурсом HTTP,
посылает запрос и получает ответ. Он размещает ответную
метаинформацию в буфере. Этот класс предоставляет метод,
который дает возможность другому объекту получать эту информацию
как объект String асинхронно. Этот класс также записывает результат
диагностики в стандартный вывод с целью демонстрации.
Результат появится в окне эмулятора J2MEWTK.
Обратите внимание, что этот класс реализует Runnable.
Он может использоваться программой для выполнения
работы асинхронно, контролируемый нитью, отличной от
основной нити приложения. В данной демонстрации соединения
отдельная нить не порождает подпроцесс контролирования
данного экземпляра, поскольку экземпляр ResourceDisplay,
который использует данный экземпляр, уже контролирует отдельная нить.
**/
public class HttpResource implements Runnable
private static Displayable instance;
// URI, представляющий выбранный ресурс.
private String uri;
// Буфер для поддержки информации ресурса.
private StringBuffer contents = new StringBuffer();
// Соединение с ресурсом. private Connection conn;
// Ссылка на HTTP-соединение, private HttpConnection httpConn;
// Входной поток соединения, private InputStream is;
// Значение поля атрибута статуса HTTP. private int status = -1;
/**
Конструктор.
@pararc uri URI, указывающий выбранный ресурс.
*/
public HttpResource (String uri)
{
super ();
this.uri = uri;
}
private String userAgentID ()
{
StringBuffer buf = new StringBuffer();
String config =
System.get Property("microedition.configuration");
String profile =
System.get Property("microedition.profiles");
buf.append("Configuration/");
buf.append(config);
buf.append!" Profile/");
buf.append(profile);
return buf . toStrir.g () ; )
/**
Запускает данный объект. Соединяется с URI,
посылает запрос, получает отклик и анализирует ответное сообщение.
*/
public void run()
System.out.println("Connection class name = " + conn.getClass().getName ());
connect () ; parse () ;
System.out.println(gecResourceMetalnfo() ) ;
try conn.close();
}
catch (lOException ioe) System.out.println(ioe.getMessage()) ;
ioe.printStackTrace();
}
}
/**
Соединяется с исходным сервером, который поддерживает URI.
Если произошло исключение во время соединения, этот метод
Перехватит его и не выдаст указания на ошибку, за исключением
Записи в стандартном результате диагностики.
*/
protected void connect!)
}
try
}
while (true)
{
// Соединение находится в состоянии "установка". conn = Connector.open(uri);
httpConn = (HttpConnection) conn;
httpConn.setRequestProperty("method", HttpConnection.HEAD);
httpConn.setRequestProperty("User-Agent", userAgentID());
// Соединение находится в состоянии "установлено". if (resourceRelocated())
{
uri = httpConn.getHeaderField("location");
// Соединение находится в состоянии "отключено" после
// вызова close().
conn.close();
}
else
}
breaX;
*/
if (serverError())
{
conn.close () ; return;
}
// Соединение находится в состоянии "установлено", is = httpConn.openlnputStream ();
System.out.println("Input stream class name = " + is.getClassO .get Name () ) ;
int responseCode = httpCcnn.getResponseCode ();
printResponseCode (responseCode) ; catch (lOExceptior. ioe)
{
contents.append(ioe.getMessage());
System.out.println(ioe.getMessage());
ioe.printStackTrace() ;
}
}
private boolean resourceRelocated()
{
boolean relocated = false; try
}
status = httpConn.getResponseCode();
if (status == HttpConnection.HTTP_MOVED_TEMP II
status == HttpConnection.HTTP_MOVED_PERM II
status == HttpConnection.HTTP_TEMP_REDIRECT)
{
relocated = true;
}
}
catch (lOException ioe)
}
System.out.println(ioe.getMessage() ) ;
ioe.printStackTrace() ;
}
return relocated;
}
private boolean serverError ()
{
boolean error = false;
try
{
status = httpConn.getResponseCode();
if ((status == HttpConnection.HTTP_NOT_IMPLEMENTED)
If (status == HttpConnection.HTTP_VERSION)
If (status == HttpConnection.HTTP_INTERNAL_ERROR)
If (status = = HttpConnection.HTTP_GATEWAY_TIMEOUT)
If (status == HttpConnection.HTTP_BAD_GATEWAY))
}
error = true; } }
catch (lOException ioe)
{
error = true;
System.out.println(ioe.getMessage()) ;
ioe.printStackTrace() ;
}
return error;
}
private void parse()
(
if (httpConn == null) return;
String protocol = httpConn.getProtocol();
contents.append("Protocol: " t protocol + "\n");
String type = httpConn.getType();
content's . append ("Type : " + type + "\n");
String encoding = httpConn.getEncoding ();
contents.append("Encoding: " + encoding + "\n");
long length = httpConn.getLength ();
contents.append("Length: " + length + "\n");
String uri = httpConn.getURL();
contents.append("URL: " + uri + "\n");
String host = httpConn.getHost();
contents.append("Host: " + host + "\n");
String query = httpConn.getQuery();
contents.append("Query: " + query + "\n");
String requestMethod = httpConn.getRequestMethod();
contents.append ("Method: " + requestMethod + "\n");
}
private void printResponseCode(int code)
{
System.out.print("Response code :
**/
switch (code) case HttpConnection.HTTP_ACCEPTED:
Systern.out.print In("HTTP_ACCEPTED");
break;
case HttpConnection.HTTP_BAD_GATEWAY:
Systern.out.print In("HTTP_BAD_GATEWAY");
break;
case HttpConnection.HTTP_BAD_METHOD:
Systern.out.print In("HTTP_BAD_METHOD") ; break;
'case HttpConnection.HTTP_BAD_REQUEST:
Systern.out.print In("HTTP~BAD_REQUEST");
break;
case HttpCo-.nection.HTTP_CONFLICT:
System.out.println("HTTP_CONFLICT");
break;
case HttpConnection.HTTP_CREATED:
System.out.print In("HTTP_CREATED");
break;
case HttpConnection.HTTP_FORBIDDEN:
System.out.print In("HTTP_BAD_FORBIDDEN");
break;
case HttpConnection.HTTP_GATEWAY_TIMEOUT:
System.out.print In("HTTP_GATEWAY_TIMEOUT");
break;
case HttpConnection.HTTP_GONE:
Systern.out.print In("HTTP_GONE");
break;
case HttpConnection.HTTP_NO_CONTENT:
System.out.println("HTTP_NO_CONTENT");
break;
case HttpConnection.HTTP_NOT_ACCEPTABLE:
Systern.out.print In("HTTP_NOT_ACCEPTABLE");
break;
case HttpConnection.HTTP_NOT_FOUND:
System.out.print In("HTTP~NOT_FOUND");
break;
case HttpConnection.HTTP_OK:
System.out.println("HTTP_OK");
break;
case HttpConnection.HTTP_PROXY_AUTH:
Systern.out.print In("HTTP_PROXY_AUTH");
break;
case HttpConnection.HTTP_UNAVAILABLE:
Systern.out.print In("HTTP_UNAVAILABLE");
break;
case HttpConnection.HTTP_VERSION:
System.out.print In("HTTP_VERSION");
break; default:
System.out.println ();
;. }
/**
Получает метахнформацию ресурса.
@выдает метаянформацию, возвращенную
исходным сервером в ответном сообщении.
*/
public String getResourceMetalnfо()
}
return contents.toString();
}
}
Четыре класса представлены в примере, показанном в листингах 8.1 - 8.4:
Программа действует следующим образом. Пользователь вводит URI в текстовое поле объекта URIEntry. Объект URIEntry создает экземпляр класса ResourceDisplay при получении команды до, введенной пользователем, что означает: "Иди и принеси указанный ресурс". Это происходит в основной нити обработки событий. Объект URIEntry затем создает отдельную нить для контролирования остальной части выполнения экземпляра ResourceDisplay.
Экземпляр ResourceDisplay создает экземпляр класса HttpResource для выполнения работы по извлечению ресурса. Эта работа осуществляется асинхронно в новой созданной нити. Новая нить контролирует следующие этапы:
Это использование нитей является важной идиомой. Цель приложений - избежать выполнения продолжительной обработки команд в методе commandAction(). Эта обработка может блокировать работу на недопустимо длинные периоды времени, как, например, при ожидании ответа с сервера HTTP. Важно, чтобы каждый CommandListener получал данные РЗ своего метода commandActionO "как можно быстрее". Например, в программе, показанной в листинге 8.1, вызов Connector.open() блокирует работу, пока не получит ответ или пока не выйдет время. Временной интервал по умолчанию составляет около 15 секунд в эмуляторе J2MEWTK. Вероятно, реализация MIDP не может быть блокированной от выполнения какой-либо обработки событий так долго.
Класс HttpResource определяет API, который поддерживает получение ресурсов в отдельной нити. Он реализует Runnable и определяет его обработку в методе run(). В нашем примере эта возможность на самом деле не используется, поскольку вторая нить начинает выполнение с методом run() класса ResourceDisplay, который затем вызывает метод HttpRespource.run(). Класс HttpResource может быть использован, однако, в другом приложении, и его реализация Runnable отражает его поддержку многонитевого исполнения.
Объекты соединений. Как вы знаете, различные интерфейсы в структуре общих соединений представляют различные типы соединений. Однако это конкретные реализации данных интерфейсов, которые на самом деле предоставляют соединению его свойства и возможности. Сейчас самое подходящее время более внимательно взглянуть на реализации, стоящие за этими интерфейсами.
Я ссылался на класс Connector как на производящий соединение. Более точно, метод Connector.open() реализует фабричный метод образца проектирования. Для получения более подробной информации по данному и другим образцам проектирования смотрите "Образцы проектирования" (Design Patterns) от "Gamma et al.". Вы пересылаете в класс Connector сформированный в общем виде адрес некоторого ресурса, с которым вы хотите установить соединение. Этот URI указывает схему - тип желаемого соединения - но, с другой стороны, извлекает подробную информацию о соединении, связанную с протоколом. Производитель соединения пересылает обратно объект, чей класс реализует протокол, представленный полем схемы запроса соединения.
Класс этого объекта реализует интерфейс, который определяет тип установленного соединения. Тип внедряемого класса абстрактен, поскольку вы ссылаетесь на объект с помощью ссылки на тип интерфейса. Например, объект соединения, выдаваемый в листинге 8.4, реализует интерфейс HttpConnection. Взгляните на следующие строчки кода, расположенные в методе HttpResource.connect ().
Connection conn;
HttpConnection httpConn;
. . .
conn = Connector.open(uri);
httpConn = {HttpConnection) conn;
. . . .
Первый оператор выдает объект соединения. URI указывает схему http. Текущий объект соединения больше чем просто Connection, это HttpConnection. Поэтому вы можете не рискуя создать ссылку на объект, чей тип - HttpConnection. Вы можете сделать это, потому что фабричный метод выдает объект, чей класс реализует HttpConnection, a не просто Connection. Этот объект отличается от объекта, который был бы выдан при других значениях поля схемы в вызове Connector.ореn().
Первый оператор, показанный в следующей выдержке из метода HttpResource.run(), выдает полностью определенное имя конкретного класса, который реализует интерфейс HttpConnection:
public void run ()
System.out.println("Connection class name = " +
conn.getClass() . get Name () ) ;
connect () ;
parse () ;
...
}
Если вы запустите эту программу на эмуляторе Sun J2ME Wireless Toolkit, вы увидите, что в следующих выходных данных выводится имя класса, который является частью реализации J2ME Sun, которая используется эмулятором Sun J2ME Wireless Toolkit:
com.sun.midp.io.j2me.http.Protocol
Если вы запустите программу, показанную в листингах 8.1 - 8.4, на эмуляторе другого производителя, вы увидите другое имя класса. Таким образом опознается реализация данного производителя интерфейса HttpConnection. Все определяемые протоколом классы зависят от реализации.
Модель состояний соединения HTTP. Соединения HTTP могут находиться в одном из трех состояний в течение их жизненного цикла. Эта модель состояний отражает природу запроса-отклика протокола HTTP. Это следующие три состояния:
Соединение переходит в состояние "установлено", когда вызваны любые из методов, перечисленных в таблице 8.7. Состояние установленного соединения представляет собой период между временем, когда запрос был послан на сервер, и временем, когда либо клиент, либо сервер прервали соединение. Вы можете видеть, что все методы, показанные в таблице 8.7, работают с извлечением данных из ответного сообщения. Чтобы извлечь данные, соединение с сервером должно быть действующим, чтобы клиент получил ответное сообщение.
Дейтаграммы посылаются
Листинг 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. Таким образом, вы эффективно переписываете данные любой предыдущей операции, гарантируя, что вы не смешаете байты двух отдельных сообщений.
Сервер порождает новую
Листинг 8.6. Сервер порождает новую нить для создания объекта со стороны сервера, который взаимодействует с каждым клиентом. Клиент и сервер должны определять семантику своих сообщенийimport javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;
import Java.io.lOException;
/**
Данный класс реализует службу, которая прослушивает запросы
клиентских соединений на известном сокете.
Он открывает соединение на предварительно определенном номере порта.
А затем блокирует обработку на данном порте,
ожидая клиентского запроса соединения.
Когда запрос появляется, он принимает его и открывает новое
соединение сокета. Эти два этапа выражаются в реализации,
уведомляющей реализацию клиента о новом соединении сокета.
Этот сервер затем порождает компонент и передает его новому
объекту соединения. Компонент запускает отдельную нить. Компонент
теперь свободен для взаимодействия с клиентом асинхронно
от продолжающейся работы сервера.
public class ServerSocket imlements Runnable
{
// Порт по умолчанию, на котором установлен известный
// сокет. public static final String DEFAULT_PORT = "9876";
// Порт, на котором установлен известный
// сокет. protected String wellKnownPort;
// URI, который данный сервер использует для открытия своего
// известного сокета. protected String uri;
// Соединение с известным сокетом.
protected StreamConnectionNotifier wellKnownConn;
// Соединение сокета, которое соединяется с клиентом,
protected StreamConnection clientConn;
/**
Конструктор для подклассов.
*/
protected ServerSocket()
super ();
/**
Конструктор.
@param port Известный порт, на котором устанавливается
этот объект как блок прослушивания.
*/
public ServerSocket (String port)
}
thisl);
if (port == null)
{
wellKnownPort = DEFAULT_PORT;
}
else
}
wellKnownPort = port;
}
setURI(port);
{
protected void setURI(String port)
{
StringBuffer buf = new StringBuffer("socket://:");
buf.append(port);
uri = buf.toString();
}
/**
Запустите данный сервер. Этот метод должен быть
вызван явно после создания данного объекта. Он запускает
прослушивание запросов клиентов на известном сокете.
Оператор вызова должен запустить это выполнение в отдельной нити.
*/
public void run()
{
while (true)
{
try
{
// Откройте соединение известного сокета для данной
// "службы". wellKnownConn = (StreamConnectionNotifier)
Connector.open(uri);
//Прослушиваем запросы соединения. Данный вызов
// блокирует работу до тех пор, пока не будет получен
// запрос на соединение.
clientConn = wellKnownConn.acceptAndOpen()
// Создадим экземпляр агента"сервера, объект, который
// представляет службу для клиента. Каждый экземпляр
// взаимодействует с одним клиентом.
// Порождаем нить для взаимодействия с
// клиентом, создавшим запрос на соединение.
ServerAgent agent = new ServerAgent(clientConn);
Thread thread = new Thread (agent);
} catch (lOException ioe)
( System.out.printlnfioe.getMessage!));
ioe.printStackTrace();
break;
)
}
}
}
Агент сервера является
Листинг 8.7. Агент сервера является объектом, который взаимодействует с клиентом независимо от демона сервера. Он запускает свою собственную нить, позволяя другим экземплярам одновременно взаимодействовать со своими клиентамиimport javax .microedition. io._StreamConnectior.;
/**
Данный класс определяет компоненту, которую сервер
создает для взаимодействия с клиентом.
Он действует как "агент" от имени сервера для того, чтобы сервер
был свободен для прослушивания только новых запросов соединения.
Экземпляры данного класса являются частью сервера.
*/
public class ServerAgent implements Runnable
private StreamConnection conn;
/**
Конструктор.
@param с Объект соединения, который представляет
соединение с клиентом. Класс ServerSocket создает и пересылает
его в данный конструктор.
*/
public ServerAgent(StreamConnection c)
super ();
conn = с;
}
/**
Выполняется агент данного сервера. Начинается диалог с клиентом. Этот метод должен быть вызван явно после того, как создан данный объект.
public void run()
}
// Взаимодействует с клиентом. Реализует поведение,
// которое определяет данную службу.
}
}
Клиент имеет отдельно
Листинг 8.8. Клиент имеет отдельно соединение с агентом сервера. Модель состояния взаимодействий, а также синтаксис и семантика взаимодействий определяются сервером, но клиенты должны им подчинятьсяimport javax.microedition.midlet.MI Diet;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.Connector;
import Java.io.lOException;
/**
Данный класс реализует клиента, который соединяется с сервером.
Для создания экземпляра данного класса вы должны указать сервер
(имя сервера DNS) и известный порт службы, с которой вы хотите
установить соединение.
*/
public class ClientSocket implements Runnable
{
public static final String P.ROTOCOL = "socket";
/'/ Порт известного сокета сервера, private String serverPort;
// Имя сервера, с которым соединяемся, private String serverHostName;
// URI известного серверного сокета. private String serverURI;
// Соединение с.сервером.
private StreamConnection streamConn;
protected ClientSocket()
}
super();
}
/**
Открытый конструктор. Вы должны указать имя сервера DNS
и номер порта службы. @param server - имя DNS машины,
с которой вы хотите соединиться.
@param port - Номер порта сервера, с которым вы хотите соединиться.
*/
public ClientSocket(String server, String port)
throws TOException
(
this();
serverHostName = server; serverPort = port;
serverURI = buildServerURI ();
open () ;
}
/**
Конструктор.
@param uri - полностью сформированный URI службы,
запрос на соединение с которой вы хотите создать.
@сбрасывает InvalidArgumentException при несформированном URI.
*/
public ClientSocket(String uri) throws lOException
{
this ();
serverURI = uri;
}
Открывает соединение. После того как создан данный объект,
соединение с сервером еще не открыто. Вы должны открыть его явно.
Это делает модель использования более гибкой для клиентов.
@ сбрасывает lOException, если соединение не может быть
открыто по некоторым причинам.
*/
public void open() throws lOException
streamConn = (StreamConnection) Connector.open(serverURI);
/**
Закрывает соединение с сервером.
*/
public void closed try streamConn. closed ; }
catch (lOException ioe)
}
ioe.printStackTraced ;
{
{
/**
Выполняет клиентское взаимодействие.
Запускает посылку клиентом запросов на сервер.
Этот метод должен быть вызван после того, как метод opend установит соединение.
*/
public void run ()
{
// Начинаем взаимодействие с сервером.
// Посылаем запросы, читаем ответы
....
private String buildServerURI ()
}
StringBuffex uri = new StringBuffer(PROTOCOL);
uri.append ("://");
uri.append(serverHostName);
uri.append(":");
uri.append(serverPort);
return uri.toString ();
}
}
Использование соединений сокета в приложениях MIDP. Естественно, тот факт, что интерфейс StreamConnectionNotif ier определен как часть пакета IOMIDP, предполагает, что он должен использоваться приложениями, запускаемыми на устройствах MIDP. Это означает, что MID-лет может поддерживать открытое соединение с известным соке-том для использования клиентами. Клиенты, однако, могут находиться в другом месте.
На самом деле клиенты должны быть удалены от сервера. Назначение сокета сервера на мобильном устройстве заключается в том, чтобы обрабатывать входящие запросы соединения от удаленных клиентов. Использование сокетов для взаимодействий на одном и том же устройстве определенно неэффективно. Хотя это возможно, существуют более удобные модели.
Удаленный клиент может работать на другом мобильном устройстве или на удаленном узле. Потенциально любой из этих типов клиентов может находиться в одной и той же сети как устройство, которое поддерживает сокет сервера, или они могут находиться отдельно от сети транспортировщика. Характеристики сети транспортировщика, в которой ваше приложение работает, определяют набор клиентов, которые могут соединиться с вашим мобильным устройством.
Сети транспортировщика используют протокол сетевого уровня как часть набора протоколов своей сети. Каждое устройство получает уникальный сетевой адрес, в то время как оно соединяется с сетью. Для того чтобы клиенты соединялись с вашим устройством - и вашим серверным сокетом, - они должны быть способны определять сетевой адрес вашего устройства. Конфигурация и реализация сети транспортировщика могут не раскрывать адресов соединенных с ней мобильных устройств внутренне или внешне, таким образом делая соединение клиентов с желаемым мобильным устройством невозможным.
Многие сети транспортировщиков используют некоторого рода динамические сетевые адреса, присваиваемые мобильным устройствам. Если это так, клиентам, желающим соединиться, придется определять адрес мобильного устройства динамично. Если не предоставляется никакого поискового механизма, клиенты не смогут запросить соединение с устройством.
Независимо от того, являются ли адреса мобильного устройства статическими или динамическими, сеть транспортировщика может задействовать какую-либо схему трансляции сетевого адреса (network address translation (NAT)) для изменения или преобразования адреса мобильного устройства. Мотив использования схемы NAT может быть связан с ограничениями места или безопасности. Определенные сетевые протоколы могут не иметь достаточного адресного места для обработки всех цифр сетевых устройств. Если это так, и если транспортировщик желает узнать адреса своих устройств, ему придется предоставить какой-либо реестр для отображения динамических адресов и механизм поиска. В противном случае ваше серверное приложение не будет доступно.
По причинам безопасности транспортировщики могут не захотеть выставлять адреса мобильных устройств своих пользователей на всеобщее обозрение. Если это так, ваше приложение может быть доступно только приложениям, работающим на системах транспортировщика. Более того, доступ может быть ограничен до привилегированных приложений, даже в сети транспортировщика. И даже в сети каждому устройству придется иметь способ объявления своего сетевого адреса другим устройствам для соединения с ним. В большинстве современных поколений беспроводных сетей мобильные устройства не осведомлены о наличии или адресе друг друга. Это может измениться в сетях поколения 3G, которые должны распространиться более широко в ближайшие несколько лет.
Беспроводные сети 3G двигаются в сторону принятия IPv6 как своих протоколов сетевого уровня. В IPv6 существует множество адресов, что делает возможным назначение уникального IP-адреса каждому мобильному устройству в мире. Если каждое устройство имеет уникальный статический IP-адрес, любое приложение, которое знает адрес вашего устройства, может соединиться с известным портом вашего устройства.
Опять же, однако, политики безопасности и конфигурации, применяющиеся транспортировщиком, могут повлиять на возможности, в действительности доступные приложениям.
Модель организации сетей в MIDP
Модель организации сетей в 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 является извлечение подробной информации о неоднородной природе, сложности и реализации большого количества различных беспроводных сетевых сред. Достижение этой цели требует изоляции разработчиков приложений от воздействия характеристик сети.
Потоковые соединения
Потоковые соединенияИнтерфейс StreamConnection происходит непосредственно из интерфейсов InputConnection и OutputConnection. Он наследует методы двух интерфейсов, описанных ранее в таблицах 8.1 и 8.2.
Интерфейс StreamConnection представляет соединение как поток данных в наиболее абстрактном смысле слова - как последовательность байтов. Это пустой интерфейс, он не привносит нового поведения в дополнение к любому из его двух вышестоящих интерфейсов. Тем не менее, его присутствие в иерархии необходимо для целей, лежащих за пределами обязанностей интерфейсов InputConnection и OutputConnection. Он работает как заполнитель, представляющий любой тип соединения, чьи данные могут быть обработаны как поток байтов.
Интерфейс StreamConnection извлекает подробную информацию о механизме соединения - протоколе, используемом в реализации определенного типа соединения, а также его синтаксисе и семантике. Например, J2ME Wireless Toolkit предоставляет две реализации StreamConnection - одну для соединения с портами связи, а другую для соединения с сокетами клиентов стиля Unix. Интерфейс StreamConnection определяет оба этих типа соединения как необработанные потоки данных без определения синтаксиса или семантики протокола. Реализации, однако, совершенно отличны. В данном разделе вы увидите, как настраивать соединение с коммуникационным портом. Затем вы узнаете, как настраивать соединение сокета.
Соединения с коммуникационными портами, как и все другие соединения, должны быть установлены путем передачи URI в Connector.open(). Вы должны указать адрес порта связи, который вы хотите открыть. Поле схемы должно иметь значение соггап, которое определяет соединение как потоковое соединение для коммуникационных портов. Полная форма адреса следующая:
address := <схема>:<уэел>;
<параметры> cheme := "coram"
unit :=
parameters := <зависящие от устройства параметры конфигурации>
Например, вы могли открыть соединение с коммуникационным портом с помощью следующего оператора:
StreamConnection conn = Connector.open("comm:0;baudrate=9600");
Полный набор параметров, которые приемлемы, зависит от родной системы программного обеспечения драйвера устройства, и, в конечном счете, конечно, на устройстве, с которым соединение было установлено.
Производящий соединения блок создает
Классы и интерфейсы cтpyктypы общих соединенийПакет javax.microedition.io определяет один класс и набор интерфейсов, которые представляют различные типы содержимого соединений. Класс Connector является единственным конкретным элементом в структуре общих соединений. Вы должны использовать его для получения текущих соединений с ресурсами. Он действительно содержит фабричный метод, который создает различные типы структур соединений для поддержки различных протоколов.
Иерархия интерфейсов в структуре общих соединений определяет абстракции, которые характеризуют различные типы соединений, поддерживаемых блоком создания соединений. Эти интерфейсы предоставляют методы, которые облегчают приложениям управление общими типами соединений.
На рисунке 8.2 показана иерархия наследования интерфейсов MIDP, которые являются частью общей структуры соединений.
Различия между организацией сетей В J2ME и J2SE
Различия между организацией сетей В J2ME и J2SEВ предыдущих разделах данной главы описывался полный набор сетевых свойств I MIDP. Пакет java.io MIDP определяет все эти свойства. В MIDP нет пакета java.net, как в J2SE.
Вы также должны знать, что пакет java.io MIDP поддерживает подмножество при- 1 вычных байтовых и символьных классов входных и выходных потоков, представленных в J2SE. В частности, классы BufferedReader, LineNumberReader и StringReader пакета java.io J2SE отсутствуют в пакете java.io MIDP.
Хотя базовая инфраструктура, связанная с сокетными соединениями, существует в реализациях MIDP, в MIDP все еще отсутствует поддержка нескольких механизмов распределенных коммуникаций, которые доступны в платформе J2SE. В MIDP отсутствуют следующие объекты уровня приложений:
Как вы хорошо знаете, тематика данной книги сконцентрирована на MIDP платформы J2ME. Тем не менее, полезно сказать несколько слов о CDC и организации сетевой работы. CDC предлагает более мощную поддержку сетей и коммуникаций, чем CLDC/MIDP. Например, стандартные комиссии определили профиль RMI. Были разработаны и другие определения профиля. Если вы действительно нуждаетесь в этих возможностях, вы должны подумать о том, какие устройства будут использовать ваше приложение, и является ли более подходящей конфигурацией для вашего приложения CDC или CLDC.
Очень возможно, что через несколько лет персональные мобильные устройства станут достаточно мощными для поддержки других профилей, таких, как профиль RMI. Но эта ситуация будет через несколько лет, а вы должны создавать приложения с расчетом на современные ожидания.
Соединения coкeтa
Соединения coкeтaСоединения сокета являются последним типом соединений, явно представленных сетевой инфраструктурой MIDP. Реализации MIDP, поддерживающие сокеты, реализуют традиционные сокеты стиля UNIX. Стоит напомнить еще раз, что от реализаций не требуется поддерживать какой-либо механизм соединения, кроме HTTP 1.1. Многие из них не хотят поддерживать соединения сокета.
Интерфейс StreamConnectionNotifier представляет известный сокет сервера. Интерфейс StreamConnection, который вы видели ранее, представляет сокет клиента.
Сокет - это сетевой механизм транспортного уровня, который обычно реализует пару протоколов TCP/IP в фиксированных межсетевых средах. Сокет сервера является программой, предоставляющей сетевую службу для соединений через сокеты.
Сокеты не требуют создания абсолютно никакой структуры полезной нагрузки, которую они транспортируют. Как и дейтаграммы, они просто транспортируют последовательность байтов. Служба определяет формат, синтаксис и семантику транспортируемых данных, составляющих сообщения. Клиенты должны соблюдать эти правила, для того чтобы использовать службу.
Соединения сокета находятся на транспортном уровне. Их поддержка осуществляется автоматически, если сокеты реализованы с помощью соединений TCP. TCP является ориентированным на соединения протоколом транспортного уровня, предназначенным для хранения данных в течение нескольких пересылок между клиентом и сервером.
Однако сокеты не всегда реализуются с помощью TCP/IP. Тем не менее, поскольку TCP/IP является стандартной парой протоколов Интернета транспортного и сетевого уровня, системы, которые реализуют сокеты с помощью других механизмов, должны связываться с Интернет-узлами с помощью шлюза. Это требование действует как в среде фиксированных сетей, так и в беспроводном Интернете.
В настоящее время TCP/IP не поддерживается многими беспроводными сетями. Тем не менее, беспроводные сети все равно могут поддерживать соединения сокета. Они могут подчиняться интерфейсу сокета и создавать такие же связанные с соединением абстракции, что и TCP/IP, используя другие протоколы - даже собственные. Однако если транспортировщик использует нестандартный набор протоколов, они будут иметь шлюз, который свяжет их беспроводную сеть с внешним миром.
Протоколы уровня приложений могут быть определены поверх протоколов транспортного уровня, если это необходимо. Реализация протокола уровня приложений использует любой доступный механизм транспортировки. Например, HTTP является протоколом уровня приложений. Создатели приложения MIDP могут выбирать, не создать ли протокол уровня приложений непосредственно поверх механизма сокета, если таковой поддерживается. Если сокеты не поддерживаются, сообщения протокола уровня приложений могут быть туннелированы с помощью HTTP. Протокол уровня приложений ответственен за определение своего собственного состояния, которое отличается от состояния протокола транспортного уровня.
Модель соединения сокета. Соединения сокета устанавливаются также, как и другие типы соединений, клиенты используют метод Connector.open() и указывают URI базирующейся на сокетах службы, с которой они хотят соединиться. Однако со стороны сервера модель соединения немного отличается из-за определяемой соединениями природы сокетов. Эта модель необходима для серверов, чтобы иметь возможность обеспечивать многочисленные одновременные соединения клиентов.
Это стандартная идиома, которую вы должны использовать для того, чтобы работать с сокетами сервера. Она та же, что и модель соединения сокета стиля Unix. Следующие этапы описывают сценарий соединения:
Соединения содержимого соединений
Соединения содержимого соединенийИнтерфейс ContentConnection дополняет интерфейс StreamConnection. Он уточняет понятие потокового соединения. Он определяет соединения, включающие содержимое, вместо представления их как простого потока необработанных байтов или потока, чья структура должна быть отмечена как приоритетная (priori).
Конечно, все потоки содержат некоторого рода "содержимое", основная цель сообщений протокола заключается в транспортировке полезной нагрузки данными. Идея, лежащая в основе интерфейса ContentConnection, заключается в том, что он представляет соединения, которые могут описать свое содержимое некоторым образом, обычно с помощью наличия атрибутов метаинформации, определенных протоколом. Интерфейс ContentConnection предоставляет подробную информацию об извлечении этой информации из потока, так что вам не придется знать синтаксис или семантику протокола реализации.
Интерфейс ContentConnection представляет собой общие характеристики семейства протоколов уровня приложений, которые обычно определяют атрибуты, описывающие транспортируемые ими данные. Более точно, ContentConnection определяет несколько базовых атрибутов, которые являются общими для всех таких соединений содержимого соединений. В таблице 8.3 перечислены три метода, определяемые ContentConnection. Вы можете видеть, как они применяются по отношению к семейству протоколов уровня приложений.
Методы интерфейса InputConnection
| Имя метода InputConnection | Описание |
| DatalnputStream openDatalnputStream ( ) | Открывает и возвращает DatalnputStream, который соединяется с сетевым ресурсом, связанным с этим соединением |
| InputStream openlnputStreamf) | Открывает и возвращает InputStream, который соединяется с сетевым ресурсом, связанным с данным соединением |
Интерфейс OutputConnection является еще одним подинтерфейсом Connection. Он работает с исходящими потоками и также определяет содержимое своих потоков как байтовые данные. Его методы показаны в таблице 8.2. Вы должны использовать этот интерфейс при записи байтовых данных в удаленный ресурс.
С помощью этих двух интерфейсов вы можете затем интерпретировать входящий или выходящий поток данных ресурса как последовательность необработанных байтов, анализируя их с помощью методов интерфейсов Datalnput или DataOutput. Конечно, вы должны знать формат данных, посылаемых устройством, или формат, ожидаемый устройством, соответственно. Другими словами, не существует абстракции данных, которая устраняет необходимость знать синтаксис и семантику данных в InputConnection или OutputConnection.
Методы интерфейса 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) | Устанавливает длину полезной нагрузки дейтаграммы |
Методы интерфейса 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) | Просматривает п байтов из входного потока |
Методы интерфейса 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 кое в чем отличается от платформы J2SE в своей поддержке дейтаграммных соединений. J2SE имеет пакет java.net. Например, ее класс, DatagramPacket определяет дейтаграмму. Класс DatagramSocket реализует протокол передачи дейтаграмм с помощью соединений сокета.
Эти классы не существуют в CLDC/MIDP. В действительности пакет java.net недоступен в CLDC/MIDP. С другой стороны, CDC содержит пакет java.net, который содержит эти классы.
В листинге 8.5 демонстрируются вышеописанные понятия. Код, описанный в этом листинге, является дейтаграммным клиентом, который соединяется с определенной дейтаграммной службой. Важными шагами, выполняемыми программой, являются следующие:
Методы интерфейса StreamConnectionNotifier
| Метод StreamConnectionNotifier | Описание |
| StreamConnection acceptAndOpen () | Возвращает новый потоковый обьект, связанный с новым сокетом и соединенный с запрашивающим клиентом |
StreamConnection conn =
Connector.open("socket://server.foo.com:98765");
Клиенты должны включать имя сервера, поддерживающего службу; номер порта представляет известный сокет сервера. Клиенты, которые хотят соединиться со службой на локальной машине, могут использовать обозначение localhost для сервера, как показано в следующем вызове:
StreamConnection conn =
Connector.open("socket://localhost:98765");
Оба вызова StreamConnectionNotifier.acceptAndOpen(} сервера и Connector.open() клиента создают объект StreamConnection. Вы уже видели класс StreamConnection при нашем обсуждении коммуникационных портов.
Вы можете быть удивлены, почему структура общих соединений использует интерфейс StreamConnection для представления сокетных соединений, а также для соединений с коммуникационными портами. Причина этого заключается в том, что данное общее название, как оно само и предполагает, представляет оба типа соединений как потоки байтов. Более того, оно может представлять любой другой тип поточно-ориентированного соединения, даже если оно использует другой протокол.
Нигде не оговаривается, какие виды протоколов может представлять интерфейс StreamConnection. Интерфейс извлекает подробную информацию о реализации протокола из приложения. Приложения не осведомлены о платформно-определяемых классах, которые реализуют интерфейсы. Хотя реализации интерфейса структуры общих соединений могут варьироваться, они должны поддерживать указанное поведение и семантику интерфейса.
Важно заметить, что не все реализации поддерживают серверные сокеты. И, из тех, что делают это, некоторые в настоящее время работают не совсем правильно. Если поддержка серверных сокетов недоступна на вашей реализации, но вы по некоторой причине должны использовать сокеты, вам придется разработать схему, по которой клиент сможет соединяться с "сервером". Сервер не сможет поддерживать модель известного сокета, ему придется определить другую модель, которая позволит клиентам получить средство установления соединения.
В листингах 8.6 - 8.8 демонстрируется набор классов, которые составляют структуру сокетных коммуникаций в MIDP. Смысл заключается в том, что эти классы будут использоваться приложением, которое нуждается в сокетных коммуникациях. Эти примеры составляют не больше чем основу, которая формирует базовую структуру поддержки сокетных взаимодействий. Они не являются функционирующими приложениями.
Некоторые данные были проигнорированы в этом коде. Например, сама сетевая служба не определена, нет определения синтаксиса или семантики сообщения протокола уровня приложений. Кроме того, код не обращается к очистке рабочих нитей со стороны сервера. Следующие классы являются классами, составляющими данный пример:
Методы интерфейса OutputConnection
| Имя метода OutputConnection | Описание |
| DataOutputStream openDataOutputStream () | Открывает и возвращает DataOutputStream, который соединяется с сетевым ресурсом, связанным с этим соединением. |
| OutputStream openOutputStream() | Открывает и возвращает OutputStream, который соединяется с сетевым ресурсом, связанным с этим соединением. |
Методы интерфейса ContentConnection
| Имя метода ContentConnection | Описание |
| String getEncoding () | Выдает значение поля, показывающего набор символов шифрования, используемых для представления содержимого сообщения |
| long getLength() | Выдает длину сообщения |
| String getType() | Выдает тип содержимого |
Неудивительно, что интерфейс ContentConnection имеет один подинтерфейс, HttpConnection, который представляет соединения, использующие протокол HTTP. Интерфейс HttpConnection определяется MIDP, а не CLDC. HTTP является протоколом содержимого соединений уровня приложений. Вы, несомненно, понимаете, что три метода интерфейса ContentConnection, перечисленные в таблице 8.3, применимы к HTTP.
Интерфейс HttpConnection расширяет эту абстракцию до более конкретного описания атрибутов соединений протокола HTTP. Он поддерживает передачу запросов и получение откликов, а также возможность извлекать и анализировать поля HTTP как для сообщения запроса, так и для ответа. Он также предусматривает возможность получения информации о самом соединении. В таблице 8.4 перечислены методы интерфейса HttpConnection.
Методы интерфейса HttpConnection
| Название метода HttpConnection | Описание |
| long getDate ( ) | Выдает значение поля заголовка даты |
| long getExpiration () | Выдает значение поля заголовка Expires |
| String getFilef) | Выдает значение поля URL данного соединения |
| String getHeaderFieldfint n) | Выдает значение части пронумерованного поля заголовка ключ-значение |
| String getHeaderField (String name) | Выдает значение поля заголовка с указанным именем ключа. В качестве аргумента приемлемо любое действительное имя поля HTTP |
| long getHeaderFieldDate (String name, long def) | Выдает значение (анализируемое как дата) поля заголовка с указанным ключом |
| int getHeaderFieldlnt (String name, int def) | Выдает значение (анализируемое как целое] названного поля заголовка |
| String getHeaderFieldKey (int n) | Выдает часть ключа пронумерованного поля заголовка |
| String getHostf) | Выдает часть HOST URL данного соединения |
| long getLastModif ied() | Выдает значение поля LastModified URL. |
| int getPortf) | Выдает значение поля порта URL данного соединения |
| String getProtocol () | Выдает имя протокола URL |
| String getQueryO | Выдает область запроса URL, часть после первого "?" в URL |
| String getReff) | Выдает часть ссылки URL |
| String getRequestMethod () | Выдает метод текущего запроса |
| String getRequestProperty (String key) | Выдает значение указанного свойства общего запроса |
| int getResponseCode() | Выдает код состояния отклика v HTTP |
| String getResponseMessage ( ) | Выдает сообщение отклика HTTP, связанное с кодом состояния отклика |
| String getURLO | Выдает строковую форму URL |
| void setRequestMethod (String method) |
Устанавливает метод для URL; приемлемыми значениями являютсяGET, POST И HEAD |
| void setRequestProperty (String key, String value) | Устанавливает значение указанного свойства общего запроса |
Определения констант интерфейса HttpConnection
| Константа HttpConnection | Описание |
| static String GET | Представляет метод запроса GET |
| static String HEAD | Представляет метод запроса HEAD |
| static int HTTP_ACCEPTED | HTTP статус 202 |
| static int HTTP_BAD_GATEWAY | HTTP статус 502 |
| static int HTTP_BAD_METHOD | HTTP статус 405 |
| static int HTTP_BAD_REQUEST | HTTP статус 400 |
| static int HTTP_CLIENT_TIMEOUT | HTTP статус 408 |
| static int HTTP_CONFLICT | HTTP статус 409 |
| static int HTTP_CREATED | HTTP статус 201 |
| static int HTTP_ENTITY_TOO_LARGE | HTTP статус 413 |
| static int HTTP_EXPECT_FAILED | HTTP статус 41 7 |
| static int HTTP_FORBIDDEN | HTTP статус 403 |
| static int HTTP_GATEWAY_TIMEOUT | HTTP статус 504 |
| static int HTTP_GONE | HTTP статус 410 |
| static int HTTP_INTERNAL_ERROR | HTTP статус 500 |
| static int HTTP_LENGTH_REQUIRED | HTTP статус 41 1 |
| static int HTTP_MOVED_PERM | HTTP статус 301 |
| static int HTTP_MOVED_TEMP | HTTP статус 302 |
| static int HTTP_MULT_CHOICE | HTTP статус 300 |
| static int HTTP_NO_CONTENT | HTTP статус 204 |
| static int HTTP_NOT_ACCEPTABLE | HTTP статус 406 |
| static int HTTP_NOT_AUTHORITATIVE | HTTP статус 203 |
| static int HTTP_NOT_FOUND | HTTP статус 404 |
| static int HTTP_NOT_IMPLEMENTED | HTTP статус 501 |
| static int HTTP_NOT_MODIFIED | HTTP статус 304 |
| static int HTTP_OK | HTTP статус 200 |
| static int HTTP_PARTIAL | HTTP статус 20В |
| static int HTTP_PAYMENT_REQUIRED | HTTP статус 402 |
| static int HTTP_PRECON_FAILED | HTTP статус 412 |
| static int HTTP_PROXY_AUTH | HTTP статус 407 |
| static int HTTP_REQ_TOO_LONG | HTTP статус 414 |
| static int HTTP_RESET | HTTP статус 205 |
| static int HTTP_SEE_OTHER | HTTP статус 303 |
| static int HTTP_TEMP_REDIRECT | HTTP статус 307 |
| static int HTTP_UNAUTHORIZED | HTTP статус 401 |
| static int HTTP_UNAVAILABLE | HTTP статус 503 |
| static int HTTP_UNSUPPORTED_RANGE | HTTP статус 416 |
| static int HTTP_UNSUPPORTED_TYPE | HTTP статус 41 5 |
| static int HTTP_USE_PROXY | HTTP статус 305 |
| static int HTTP_VERSION | HTTP статус 505 |
| static String_HTTP_POST | Представляет метод запроса POST |
В листингах с 8.1 по 8.4 показан исходный код для простой программы, которая демонстрирует, как пользователь мобильного устройства может запрашивать ресурс HTTP с удаленного сервера. Вы можете найти, что эта программа не работает при выполнении за пределами вашего корпоративного брандмауэра, в зависимости от конфигураций сети вашей компании, брандмауэра и прокси-сервера. Вы можете быть ограничены до посещения URI ресурсов, расположенных в пределах вашей корпоративной сети.
Протокол HTTP определяет семантику, связанную с тем, что клиентам необходимо запрашивать ресурсы через прокси-сервер. Браузер может изменять URI пользователя, основываясь на настройках его прокси, и посылать измененный запрос на прокси-сервер, который перенаправляет его на исходный сервер. Программа не делает таких изменений URI, и поэтому она не может пересылать URI, как ожидается вашим прокси-сервером. Если вы не знаете, как браузер изменяет URI, у вас могут возникнуть сложности при доступе к URI, являющимся внешним по отношению к вашей корпоративной сети. Результат выразится в том, что программа, показанная в листинге 8.1, сбросит lOException.
Программа, показанная в листинге 8.1, отображает только метаинформацию о запрошенных ресурсах и не отображает сам ресурс. Она лишь запрашивает информацию заголовка для каждого ресурса, используя метод HEAD HTTP. Написание программы, которая отображала бы произвольное содержимое, было бы равноценно написанию целого браузера, что, очевидно, лежит за пределами темы данной книги. К счастью, некоторые компании предлагают HTTP-браузеры, которые работают на устройствах MIDP, так что вам не придется проделывать эту огромную работу.
Методы интерфейса
| Название метода HttpConnection | Описание |
| void setRequestMethod (String method) | Устанавливает метод запроса HTTP, либо HEAD, либо POST, либо GET |
| void setRequestProperty (String key, String value) | Включает в запрос указанное поле заголовка со значением, установленным на value |
Методы интерфейса
| Название метода HttpConnection | Описание |
| InputStream openlnputStream () | Открывает и выдает ссылку на InputStream (происходит от InputConnection) |
| OutputStream openOutputStream() | Открывает и выдает OutputStream для соединения (происходит из OutputConnection) |
| DatalnputStream openData!nputStream( ) | Открывает и выдает ссылку на DatalnputStream (происходит из InputConnection) |
| DataOutputStream openDataOutputStream() | Открывает и выдает ссылку на DataOutputStream (происходит изOutputConnection) |
| long getDate() | Получает значение поля заголовка date |
| String getEncoding () | Получает строку, которая описывает шифрование содержимого в ответе (происходит от ContentConnection] |
| long getExpiration ( ) | Получает значение поля заголовка expires |
| String getHeaderField (String name) | Получает значение указанного поля заголовка |
| long getHeaderFieldDate (String name, long def) | Получает значение указанного поля заголовка. Значение анализируется как число |
| String getHeaderFieldlnt (String name, int def) | Получает значение указанного поля заголовка. Значение анализируется как число |
| String getHeaderFieldKey (int n) | Получает указанное поле заголовка. Аргумент представляет собой индекс поля заголовка |
| long getLastModif ied ( ) | Получает значение поля заголовка last-modified |
| long getLength() | Извлекает длину поля заголовка. |
| int getResponseCode ( ) | Получает код состояния отклика HTTP |
| String getResponseMessage ( ) | Получает ответное сообщение HTTP |
| String getType() | Получает тип содержимого, предоставляемого сервером (происходит из ContentConnection) |
Если соединение находится в состоянии "установлено", вы можете больше не активизировать методы, показанные в таблице 8.6. Вы не можете переустановить параметры запроса, что означает, что вы не можете снова использовать объект соединения для доступа к нескольким различным URI. Вы вынуждены создавать экземпляр нового соединения, пересылая новый URI в вызов Connector.ореп(). Кстати, либо клиент может прервать соединение после получения отклика, либо удаленный сервер может разорвать соединение послелосылки этого отклика.
Обратите внимание, что в листинге 8.4 порядок, в котором поля заголовков вставляются в сообщения запроса или извлекаются из ответного сообщения сервера, несущественен. Класс соединения имеет дело с абстракциями создания правильно сформированных сообщений HTTP и анализа ответов HTTP.
Методы интерфейса
| Название метода HttpConnection | Описание |
| void close () | Прерывает соединение (происходит из интерфейса Connection) |
| String getFile() | Получает поле |
| String getHostO | Получает поле |
| int getPortO | Получает поле |
| String getProtocol () | Получает поле |
| :" i ing getQuery () | Получает строку запроса URL данного соединения |
| String getRequestMethodf) | Получает текущий метод запроса (GET, POST и так далее) |
| String getRequestProperty (String key) | Получает значение свойства указанного общего запроса данного соединения |
| String getRef() | Получает поле URL данного соединения |
| String getURL() | Получает полный URL данного соединения как строковое значение |
Соединения HTTP могут транспортировать множество различных видов содержимого, такого, как HTML и XML. Кроме того, HTTP может использоваться как упаковщик для туннелирования других данных протокола уровня приложений. Вы, таким образом, имеете удобный механизм передачи данных для приложений клиент-сервер.
HTTP широко используется серверами как механизм передачи множества различных служб. Службы могут быть реализованы с помощью любой из множества технологий, независимо от того, что они используют HTTP в качестве механизма передачи. Службы могут быть реализованы с помощью сервлетов Java, Java Server Pages (JSP), Pearl scripts, CGI и так далее.
Модель сервлетов является особенно мощной, поскольку сервлеты написаны на Java и легко стыкуются с другими технологиями Java enterprise, они также без проблем взаимодействуют с клиентскими технологиями. Кроме того, сервлетные системы поддерживаются стандартными Web-серверами и могут без труда создавать выводимые данные в различных форматах. В главе 11 вы узнаете, как порталы беспроводного Интернета используют эти технологии для построения служб для мобильных устройств.
Методы интерфейса 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) | Посылает указанную дейтаграмму |
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.
Платформа программирования J2ME для портативных устройств
Загружает определенные
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 }
I18NDemo midlet; 37 38 // Уведомление
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 (или манифеста) должны быть кодированы с помощью символьной кодировки, которая поддерживает нужный язык. Существует два способа выполнения этого:
Если вы выбрали первый из только что описанных подходов (кодирование с помощью символьной кодировки, предназначенной для языка региональной настройки), вам понадобится найти текстовой редактор, который поддерживает методы ввода китайского языка и записывает символы в соответствующую кодировку. Либо вы можете использовать второй подход и вводить последовательности переключения уникода 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 не поддерживает вставку комментариев, которые могут помочь людям понять использование атрибута в приложении.
Acпекты интернационализации
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 (двадцать тысяч и сорок пять сотых)
Интернационализированная программа должна отображать формат дат, времени и чисел в соответствии с требованиями местной специфики среды исполнения. Программы не выбирают эти отформатированные значения из некоторой базы данных, они высчитывают их динамически тем же образом, как и динамически сортируют строки.
Поддержка календаря и временных зон. Календари, хотя связаны с датами и временем, определяют другие свойства и функциональные возможности. Различие заключается в том, что календари выполняют вычисления, которые включают даты и время, в то время как объекты даты и времени поддерживают форматирование и отображают эти значения.
Cтроковая сортировка
Cтроковая сортировкаMIDP не поддерживает строковую сортировку. Если ваши приложения нуждаются в выполнении лексикографической сортировки какого-либо вида, вам придется спроектировать и реализовать ваш собственный механизм для ее выполнения. Хотя подобная поддержка существует в J2SE, .классы затрачивают слишком много ресурсов для сред устройств MIDP в настоящее время.
Cтруктуры интернационализации
Cтруктуры интернационализацииВ основном проблема всех разработок интернационализации заключается в поиске механизма, который дает возможность приложениям извлекать правильную версию локализованных ресурсов при работе. В отличие от J2SE, MIDP не имеет реального API или классов, которые поддерживают общее извлечение локализованных ресурсов. Здесь нет класса ResourceBundle или каких-либо его подклассов. Приложения MIDP должны создавать свои собственные механизмы для определения и извлечения локализованного ресурса. В реальности, наиболее жизнеспособными подходами являются:
Форматирование дат времени и чисел
Форматирование дат, времени и чисел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 на мобильных устройствах. Возможно, что вскоре все транспортировщики будут иметь такие системы. Инициализация приложений является темой главы 10.
Скорее всего, эти системы будут предоставлять способ для устройств сообщения информации о среде их исполнения и получения от сервера нужных им ресурсов. Например, AMS устройства может указывать контекст региональной настройки устройства и загружать из инициализирующей системы только локализованные ресурсы для данной региональной настройки.
Данное взаимодействие между AMS устройства и инициализирующим сервером предотвращает необходимость установки локализованных ресурсов для многочисленных региональных настроек на устройстве. Оно также обеспечивает способ для AMS, с помощью которого можно показывать пользователю, поддерживается ли региональная настройка, до запуска приложения. Тем не менее, разработчики могут найти, что легче упаковать откомпилированные локализованные файлы классов вместе с остальным кодом приложения. Проблемы разработки приложения, его установки, доставки и восстановления должны рассматриваться как часть каждой разработки.
Использование атрибутов МIDлета
Использование атрибутов МID-лета для определения локализованных ресурсовКак вы знаете, вы можете размещать определяемые пользователем атрибуты в файле JAD вашего приложения. Это означает, что вы можете использовать файл JAD для определения атрибутов MID-лета, которые представляют локализованные ресурсы, используемые вашим приложением.
В данном подходе программы больше не вставляют ресурсы (например, текстовую строку) в приложение. Вместо этого программы размещают ресурсы в файле JAD. Программа ищет ресурс, извлекая значение некоторого атрибута. Программист определяет имена атрибутов так, чтобы они содержали компонент, который представляет контекст региональной настройки. Таким образом программы могут извлекать версию ресурса, который совместим с их контекстом региональной настройки среды исполнения.
Для демонстрации данного подхода я вновь использовал демонстрационную программу HelloWorld из главы 3. Приложение переименовано на IISNDemo для отличия его от оригинальной версии.
В листинге 9.1 показан файл дескриптора приложения, используемый программой IISNDemo. Несколько новых атрибутов были добавлены в файл 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.
Использование текстовых файлов
Использование текстовых файлов приложения для определения локализованных ресурсовВторой подход к локализации использует программно определяемые текстовые файлы, которые содержат локализуемые атрибуты. Приложение с помощью данной схемы может, например, определить один файл локализованных атрибутов для каждой региональной настройки. Схема имен может соответствовать обозначению региональной настройки, например, en_US.txt для английской, fr_FR.txt для французской, ja_JP.txt для японской и так далее. В листинге 9.4 показан один пример такого файла, содержащего пары "ключ-значение" локализованных строк.
Файл 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.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, с конечной региональной настройкой. С помощью всего лишь нескольких операторов приложение получает доступ ко всем локализованным ресурсам.
Класс I18NDemo3 создает
Листинг 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.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, но это может быть неважно.
Измененный класс HelloWorld
Листинг 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.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.
Класс I18NDemo2 использует
Листинг 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
Класс HelloForm2 теперь
Листинг 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.
Класс ResourceBundle
Листинг 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);
}
Класс ListResourceBundle
Листинг 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."Imageimport 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 для создания экземпляра статических нестроковых объектов при загрузке класса. Наша программа должна использовать статический инициализатор, потому что каждый класс локализованного ресурса создает определяемое региональной настройкой изображение.
З Класс HelloForm определяет
Листинг 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-лета.
Поддержка интернационализации в MIDP
Поддержка интернационализации в MIDPВ реальности решение интернационализации на любой платформе ограничивается ресурсами и механизмами программного интерфейса приложения, доступными приложениям. Легкость, с которой программист может реализовать поддержку интернационализации - и полнота этой поддержки - зависят в значительной степени от уровня явной поддержки основных областей разработки интернационализации. Платформа MIDP предоставляет следующие элементы для поддержки разработки интернационализации:
Классы Calendar и TimeZone абстрактны. Реализации MIDP должны предоставлять, по крайней мере, один конкретный подкласс каждого из них. Хотя и не интернационализированные, их реализации будут совместимы с региональными настройками, поддерживаемыми реализацией MIDP. Например, в регионе Соединенных Штатов реализация будет, скорее всего, поддерживать грегорианский календарь.
Большинство реализаций MIDP поддерживает только одну региональную настройку. В действительности спецификация MIDP требует от реализации поддержки лишь одной региональной настройки и одной временной зоны - время по Гринвичу (GMT).
Спецификация MIDP требует от реализаций поддержки только одной региональной настройки. Реализации MIDP в настоящее время не определяют достаточной поддержки для интернационализации и локализации, которые требуют слишком много ресурсов.
В реальности разработчики приложений, надеющиеся поставить приложения для нескольких платформ, должны встроить поддержку региональных настроек вместо той, что поддерживается реализацией производителя. Чтобы внедрить поддержку настоящих интернационализации и локализации, разработчики должны создавать программные интерфейсы уровня приложений, которые поддерживают многочисленные временные зоны, календари и так далее.
Производители могут предоставлять некоторую часть этой поддержки в форме родных программных интерфейсов приложений и библиотек Java, которые находятся поверх их реализаций MIDP. Разработчики должны знать о том, какой уровень интернационализации и локализации предоставляется, если они вообще предоставляются, и соответственно планировать создание своих приложений.
Поддержка реализации одной временной зоны и одного календаря может быть достаточной в большинстве случаев. Производители могут, однако, предоставить реализации дополнительные ресурсы календаря и временных зон. Мотив такого рода дополнительной поддержки заключается в поддержке многих языков.
Производители устройств обычно поддерживают региональную настройку, связанную с целевым рынком устройства. Реализация, которая поддерживает только одну региональную настройку, не будет интернациональной. Хотя этот подход, вероятно, работает на большинстве рынков сегодня, все может измениться в скором будущем. Поддержка одной региональной настройки не поддерживает многоязыковой работы. Приложениям может понадобиться выполнять работу с сообщениями в одной региональной настройке, задавать денежный формат в другой и так далее.
Читатели, знакомые с поддержкой интернационализации платформы J2SE, заметят, что в MIDP довольно заметно не хватает всесторонней поддержки интернационализации (смотри пакет Java.text J2SE). Причина опять же заключается в ограниченности среды мобильных устройств. MIDP не имеет пакета Java.text. В действительности MIDP не поддерживает API для таких свойств интернационализации, как пакеты ресурсов, форматирование сообщений, числовое форматирование, чувствительные к региональным настройкам форматы дат и времени и так далее. В MIDP также отсутствуют новые свойства интернационализации пакета Java.text JDK версии 1.4, такие, как сортировка, двунаправленная текстовая поддержка, аннотации, атрибуты и так далее.
Поддержка календаря и временных зон
Поддержка календаря и временных зонОпять же в отличие от платформы J2SE в MIDP есть только ограниченная поддержка календаря. Класс java.util.Calendar абстрактен. Поэтому каждая платформа MIDP будет предоставлять, по крайней мере, одну конкретную реализацию. Скорее всего, она не будет интернационализирована.
Конкретный подкласс платформы Calendar, вероятнее всего, реализует один определенный календарь, такой, как грегорианский или лунный. Он может соответствовать контекстам региональных настроек, в которых вы размещаете ваши приложения, а может и не соответствовать. Метод Calendar.getlnstance(TimeZone zone) выдает объект Calendar, который использует указанную временную зону и региональную настройку платформы, установленную по умолчанию. Обратите внимание, что этот фабричный метод не делает класс Calendar полностью интернационализированным классом. Он все еще не выдает соответствующий календарь, основываясь на региональном контексте. Например, если вы укажете китайское стандартное время (Chinese Standard Time), вы не получите объект, который представляет лунный календарь, используемый в Китае, во всех реализациях MIDP. Это означает, что вам нужно знать, какой календарь поддерживается вашей платформой, и согласуется ли он с региональной настройкой, поддерживаемой реализацией.
Понятия
ПонятияИнтернационализация - это действия программного обеспечения по соблюдению географического, лингвистического и культурного контекста, определяемого средой исполнения. Термин интернационализация иногда сокращается как "i18n", потому что 18 букв в этом слове между буквами "i" и "n" опускаются.
Последовательности переключения
Листинг 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, вы должны убедиться, что используете уникод для записи строковых литералов в файл. В противном случае вашему приложению придется выполнять преобразование посимвольной кодировки.
Работа с сообщениями
Работа с сообщениямиК сожалению, в MIDP также отсутствует явная поддержка организации сообщений. В отличие от J2SE, MIDP не предлагает API для работы с сообщениями и здесь нет класса MessageFormat. При разработке решения организации работы с сообщениями в приложении MIDP, разработчики должны ра матривать следующие вопросы:
Разработка решения интернационализации приложения MIDP
Разработка решения интернационализации приложения MIDPВ этом разделе представлены три подхода к разработке поддержки интернационализации и локализации приложений MIDP. Эти подходы были отобраны на основе доступных в MIDP API, которые могут предоставить некоторый уровень поддержки интернационализации, или тех, что включены в MIDP для работы с интернационализацией.
Региональные настройки и локализация
Региональные настройки и локализацияРегиональная настройка представляет собой определенный географический, лингвистический и культурный контекст. Она описывает контекст, в котором работают интернационализированные приложения. Термин локализация относится к практике предоставления приложению возможности работать в контексте определенной региональной настройки. Слово локализация иногда сокращается как "11Оn", поскольку 10 букв опускаются между буквами "l" и "n" в этом слове. Разработчик локализует приложение для одной или нескольких региональных настроек после его интернационализации.
Язык обычно является наиважнейшим фактором различия региональных настроек. Различия в использовании языка включают такие характеристики, как орфография, капитализация, пунктуация, идиоматические выражения и даже написание. В действительности географический контекст обычно отделяет регионы, которые по-разному используют язык, использование языка, как правило, связано с определенным географическим контекстом. По этой причине языковой и географический контекст являются основной информацией, описываемой в региональной настройке. Например, Франция, Швейцария и Канада представляют три географические зоны, в которых французский язык используется по-разному. Китай и Тайвань представляют собой различные регионы, в которых на китайском языке говорят и пишут по-разному, и где существуют различия в идиоматических выражениях.
Географический контекст региональной настройки может представлять собой область меньше, чем страна, как, например, провинция, регион или даже такая маленькая область, как город. Например, Гонконг и Гуанчжоу, оба они расположены в Китае, в них обоих говорят на кантонском диалекте китайского языка, но совершенно по-разному, а также различным образом пишут китайские идеограммы. Подобные различия существуют во многих языках, включая использование английского и испанского языков по всему миру.
Региональная настройка предоставляет контекст более, чем просто для языковой информации. Региональная настройка также предполагает использование определенной временной зоны, конкретных способов задания форматов дат, времени и числовых значений, а также использование определенного календаря (грегорианского, лунного и так далее).
Локализация приложения под определенную региональную настройку включает предоставление данных, требуемых программе для правильного функционирования в данной местной специфике. Локализованные данные включают перевод текста, который просматривают пользователи, определенные политики сортировки, выбор определенного формата даты и времени, использование правильных числовых и денежных форматов и так далее.
Многоязыковое приложение - это приложение, которое может работать, используя несколько контекстов региональной настройки одновременно. Например, программа может отображать текст на одном языке, а форматы дат и времени показывать в соответствии с политикой другой региональной настройки. Ну а денежные и числовые значения могут быть отображены в соответствии с политиками третьей локальной настройки.
В действительности существует множество других деталей, необходимых для создания полностью интернационализированного и локализированного пользовательского интерфейса. Комплексные усилия включают обращение к культурным соглашениям - например, избегать использования агрессивных значков, использование цветов, представляющих символику, которую понимает местный пользователь и так далее. Многие из этих вопросов, однако, связаны с проектированием. Разработка решений 118п лежит за пределами возможностей данной главы. В программных интерфейсах интернационализации не существует определенных механизмов, которые были бы связаны со многими из этих задач. Создание высококвалифицированных интернационализированных разработок является искусством, как и многие другие области в разработке программного обеспечения.
Символьные кoдиpoвки
Символьные кoдиpoвкиСимвольная кодировка - это соответствие между каждым элементом письменного языка и двоичным кодированием, которое однозначно представляет его. Индивидуальная связь, которая представляет языковой элемент и его двоичную шифровку, называется точкой шифрования. Чтобы правильно представить текст пользователям - в манере, соответствующей региональной специфике пользователя - требуются приложения для работы с символьной кодировкой, которые могут правильно представлять язык, связанный с региональной настройкой исполнения приложения.
ASCII является примером символьной кодировки. Поскольку символьная кодировка ASCII соответствует только английскому алфавиту, нам необходимы другие символьные кодировки - иногда называемые наборами символов - для работы с другими языками. Как вы знаете, Java внутренне использует символьную кодировку уникода для представления всех символьных данных. Данные, считываемые программой, могут быть представлены в уникоде, а могут и не быть. Если они не представлены, данные могут быть преобразованы перед импортом в приложение.
Кроме должного внутреннего представления данных с помощью символьных кодировок, приложения должны представлять данные правильно пользователю. Это требует использования шрифта, который представляет элементы, используемые в языке. Компьютерная платформа отвечает за предоставление приложениям поддержки шрифтов. Платформа создает соответствия между символьными кодировками и шрифтами, чтобы приложениям не приходилось этого делать. Эти соответствия определяют связи между точками кода и глифами. Глиф - это объект, визуализирующий то, что представляет собой элемент языка, как, например, буква или идеограмма.
Конструкторы и методы 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.
Кроме того, вам необходимо учитывать создание портативной библиотеки классов обработки ввода-вывода, которую вы можете использовать и для других приложений. Будет лишней тратой времени на проектирование повторно реализовать данную инфраструктуру вновь и вновь.
За исключением этих недостатков этот второй подход в значительной степени сходен с первым, который использует файл JAD для хранения локализованных ресурсов. Как и подход с файлом JAD, этот подход может обрабатывать нестроковые ресурсы, определяя атрибуты, чьи значения являются именами чувствительных к региональным настройкам классов.
Платформа программирования J2ME для портативных устройств
Аутентификация пользователей
Аутентификация пользователейАутентификация пользователей - это просто процесс подтверждения через агента пользователя того, что пользователь является тем, кем себя заявляет. Аутентификация пользователя требует, чтобы агент пользователя передавал информацию о пользователе диспетчеру инициализации. Обычно агент пользователя передает эту информацию диспетчеру инициализации в форме HTTP-заголовков HTTP-запроса браузера устройства. Запрос скорее всего также использует поле заголовка HTTP-запроса агента пользователя для идентификации агента пользователя.
Существуют некоторые беспроводные системы, которые не хранят много пользовательской информации на мобильных устройствах. В таких случаях сервер должен связывать пользовательскую информацию с информацией устройства - MSN устройства, например. Как описывалось ранее, системы инициализации могут взаимодействовать с другими системами транспортировщика, такими, как серверы аутентификации, серверами облегченного протокола службы каталогов (Lightweight Directory Access Protocol (LDAP)) или серверами шлюза беспроводного Интернета (WIG) для получения остальной необходимой пользовательской информации.
Генерирование события оплаты
Генерирование события оплатыПользователи должны оплачивать использование услуг. Счет - это список всех расходов, который представляется потребителю к оплате. Событие выдачи счета - это уведомление о необходимости произведения оплаты.
Успешная загрузка может представлять собой событие, подлежащее оплате за программное обеспечение, которое необходимо купить за плату. При выполнении и уведомлении об успешной загрузке приложения пользователем система инициализации генерирует событие выдачи счета. Событие выдачи счета пересылается системой организации счетов. Система организации счетов обычно представляет собой независимую систему, управляемую транспортировщиком, с которым взаимодействует система инициализации.
Системы инициализации поддерживают различные модели формирования счетов. Разнообразные модели генерируют различные типы информации о событиях выдачи счетов, которая представляет собой разные схемы оплаты. В следующем списке представлены некоторые возможные схемы оплаты:
Системы инициализации используют различные схемы для пересылки информации о событии выдачи счета системам организации счетов. Одна из схем заключается в пересылке каждого события при его осуществлении. Другой метод - сбор групп событий выдачи счетов и пересылки их как пакета для групповой обработки системой организации счетов. Групповая обработка счетов может выполняться периодически тем же образом, как обычно поставщики услуг осуществляют работу со счетами.
Обновление приложения
Обновление приложенияОбновление приложения - это процесс обновления приложения, которое уже находится на устройстве, на более новую версию. В соответствии с приложением "Инициированная пользователем беспроводная инициализация" (Over the Air User Initiated
Provisioning) спецификации MIDP для поддерживания обновлений уже установленных на устройства приложений требуется система инициализации ОТА. Вы найдете ссылку на этот документ в разделе "Ссылки" в конце данной книги.
Программное обеспечение пользовательского агента устройства и программное обеспечение диспетчера инициализации использует обязательный атрибут MIDlet-Version файла JAD приложения для согласования обновления приложения. Кроме того, дескриптор приложения должен однозначно идентифицировать набор МГО-летов, который должен быть загружен, так чтобы устройство могло определять, должно ли оно выполнять обновление или новую установку. Разработчики должны убедиться, что они поставляют правильную информацию о версии MID-лета и идентификации набора MID-летов.
Подготовка приложений к системам инициализации
Подготовка приложений к системам инициализацииПодготовка приложений к использованию в системах инициализации заключается в предоставлении всех обязательных файлов приложения и обеспечении того, что они содержат информацию, требуемую в процессе инициализации. Основная задача этой подготовки - этв правильное создание файла дескриптора приложения (JAD) и файла приложения JAR.
Файл JAD является основным механизмом предоставления определяемой приложением информации как клиенту, так и серверу. Файл JAD может сопровождать каждый файл JAR приложения. Система инициализации извлекает и использует информацию из этого файла во время различных этапов процесса инициализации. Файл JAD может быть
сохранен как часть файла JAR приложения, или он может быть сохранен отдельно для более легкого извлечения. Одно основное преимущество поставки файла JAD отдельно от файла JAR заключается в том, что диспетчер инициализации может получать атрибуты приложения без открытия файла JAR. В таблице 10.1 перечислены все атрибуты MID-лета, относящиеся к инициализации.
Подтверждение пoкyпки и соблюдение обязательных условий
Подтверждение пoкyпки и соблюдение обязательных условийПодтверждение покупки - это процесс удостоверения в том, что пользователи оплатили приобретенное программное обеспечение, которое требует платы за покупку, и что они оплатили только программное обеспечение, которое они приобрели. Соблюдение обязательных условий включает в себя процессы подтверждения того, что пользователь действительно сделал выбор по приобретению программного обеспечения, которое он должен оплатить, и безошибочного отказа признания любой попытки пользователя отказаться от осуществленных транзакций.
Системы инициализации выполняют эти этапы до генерирования события создания счета. Они должны поддерживать подтверждение покупки и соблюдение обязательных условий для того, чтобы обеспечивать полноту охвата финансовых и коммерческих транзакций. Все системы инициализации должны поддерживать защищенные коммуникации для денежных транзакций, передачи информации о покупке и информации о соблюдении обязательных условий.
Подтверждение совместимости
Подтверждение совместимостиПодтверждение совместимости - это процесс подтверждения совместимости приложения на основе контекстов устройства, пользователя и платформы J2ME. Все инициализирующие системы не должны позволять загрузку или даже обнаружение несовместимых приложений.
Диспетчер инициализации может проверять совместимость приложения с указанным устройством, сравнивая метаинформацию устройства и приложения. Все системы инициализации должны запрещать загрузку наборов приложений, которые несовместимы со средой указанного устройства.
В действительности проблема подтверждения совместимости является одной из причин, по которым атрибутам MicroEdition-Configuration и MicroEdition-Profile требуются атрибуты файла JAD. Система инициализации использует эту информацию при поиске совместимых приложений.
Поиск приложений
Поиск приложенийПоиск приложений - это процесс, с помощью которого пользователи обнаруживают интересующие их приложения. Это первый этап процесса инициализации, в который вовлечен конечный пользователь. Большинство систем инициализации обычно предоставляют пользователям какой-либо WML-интерфейс (или XHTML) для обнаружения приложений с помощью мобильных устройств. Мобильные устройства, которые предназначены для загрузки ОТА, имеют НТТР-микробраузер, который, наиболее вероятно, будет интегрирован до некоторой степени в программное ббеспечение пользовательского агента устройства.
Реальная работа пользователя с системами инициализации варьируется в зависимости от характеристик и поведения определенного продукта. Некоторые системы инициализации также предоставляют HTML-интерфейсы, которые ориентированы на персональные компьютеры. Пользователи могут выполнять этапы обнаружения и посредничества с помощью своих персональных компьютеров, а затем использовать свои мобильные устройства только для выполнения загрузки.
Как вы узнали в предыдущем разделе, одной из основных целей инициализации является содействие обнаружению приложений, которые совместимы со средой мобильного устройства. Диспетчер инициализации сравнивает атрибуты среды устройства с набором атрибутов приложения. Он может гарантировать, что несовместимые приложения никогда не загрузятся на устройство. При определении совместимости сред приложений и устройств среда мобильного устройства делится на следующие четыре контекста:
При анализе контекста устройства, однако, более важно должное техническое функционирование приложений. Системы инициализации требуют информации о контексте устройства, например, о том, как много у него памяти, каковы его рабочие ресурсы и так далее. Например, если в устройстве недостаточно общей памяти для хранения набора приложений, диспетчер инициализации должен запрещать его загрузку. Если недостаточно свободного места, AMS (JAM) уведомит пользователей, что они должны освободить место для приложения, удалив какое-либо другое содержимое.
Анализ контекста устройства связан с контекстом приложения. Приложения имеют определенные минимальные требования, соблюдение которых необходимо для их нормального выполнения. Например, приложению может потребоваться определенный размер и разрешение экрана или способность к воспроизведению цветов для эффективной работы. В связи с этим могут потребоваться определяемые производителем библиотеки, минимальные уровни рабочей памяти, определенный минимальный размер хранилища данных RMS и так далее.
Анализ среды J2ME, необходимой приложению, так же важен, как и анализ родной среды устройства. Приложения, возможно, потребуют, чтобы устройство поддерживало определенную версию MIDP или CLDC. Обычно это MIDP и CLDC, под которыми приложение было разработано. Какими бы ни были определенные характеристики клиентской среды, они переносятся на сервер инициализации, чтобы использоваться в качестве параметров поиска. Система инициализации сопоставляет эти характеристики с поисковыми категориями, которые она поддерживает. Системы инициализации варьируются в своей сложности и в своей возможности использовать определенные поисковые категории.
Результаты поиска представляют собой отфильтрованное подмножество приложений, зарегистрированных в системе инициализации. Они должны содержать только те приложения, которые совместимы с контекстом клиента. По самой меньшей мере системы инициализации будут ограничивать результаты поиска в соответствии с критериями пользователя, устройства и платформы J2ME. Некоторые системы инициализации могут также поддерживать более продвинутый поиск, такой, как возможность соотносить информацию о местоположении устройства с приложениями, которые подходят для этого региона, основываясь на выполняемых функциях, наличии локализации и так далее.
Информация о среде клиента обычно посылается как HTTP-заголовки HTTP-запроса из браузера устройства диспетчеру инициализации. Агент пользователя и браузер взаимодействуют для создания HTTP-запроса, который связывается с поисковой функцией инициализирующего сервера.
В менее автоматизированных системах инициализации пользователям может понадобиться ввести значения для поисковых категорий в браузере их устройства. Система инициализации может предоставлять HTTP-интерфейс, который позволяет пользователям указывать характеристики поиска.
В более автоматизированных системах система инициализации извлекает информацию о предпочтениях пользователя из сети транспортировщика. Агент пользователя может пересылать идентификационный номер мобильной станции (MSISDN или MSN) на сервер инициализации. Если сервер инициализации поддерживает интеграцию с внешними системами, такими, как серверы каталогов, он может получать пользовательскую информацию с сервера каталогов транспортировщика. Для того чтобы этот подход работал, транспортировщик должен предоставлять пользователям возможность вносить в пользовательские записи свои предпочтения при поиске для инициализации.
Чем выше уровень автоматизации, тем более эффективным становится процесс поиска, и тем менее вероятно, что он сделает ошибку. Это важно учитывать как пользователям, так и транспортировщикам. Просмотр вручную занимает много времени и часто приводит к появлению ошибок. Вероятно, можно с уверенностью сказать, что навигация вручную и серфинг по WML-страницам (или XHTML в развивающихся в настоящее время системах) требует длительных соединений. Это следует из больших затрат на время передачи в сетях с коммутацией каналов (сети 2G) и высоких издержек при передаче пакетов в сетях с коммутацией пакетов (сети 2.5G и 3G), которые нежелательны для пользователей. ^Это также занимает полосу пропускания, что нежелательно для транспортировщиков.
В процессе поиска система инициализации выводит результаты поиска в список для пользователя. Система может систематизировать результаты по группам, типам устройств, поддерживаемой платформе J2ME, типу лицензии программного обеспечения, цене покупки программного обеспечения или некоторым другим категориям. Некоторые системы могут поддерживать многоуровневую сортировку, так что пользователи могут проранжировать приложения и задать организацию результатов поиска в соответствии с несколькими критериями.
В общем, важно, чтобы разработчики приложений знали о свойствах, поддерживаемых системой инициализации. Знакомство с возможностями систем инициализации дает разработчикам возможность извлекать выгоду из свойств системы.
Поиск приложений является хорошим примером получаемых преимуществ. Например, если разработчик может предоставлять метаинформацию для поисковых категорий и типов поиска, поддерживаемых инициализирующим программным обеспечением, обнаружение подходящих приложений пользователями будет более успешным и точным. Количество заявок, полученных на приложение, может положительно повлиять на его коммерческий успех. Знакомство со свойствами и возможностями систем инициализации дает разработчикам возможность подготовить файлы дескриптора своих приложений и метаинформацию для более эффективного и плодотворного использования в системе инициализации.
Понятия
ПонятияПроизводители обычно устанавливают программное обеспечение, которое вы видите на мобильных устройствах, перед тем как устройства покидают завод. Но предварительная заводская установка всего этого программного обеспечения становится все менее и менее осуществимой для производителей. Устройства не имеют достаточно ресурсов памяти для хранения и увеличения количества разнообразных приложений, которые могут потребоваться пользователям. Более того, предварительно установленные приложения никогда не смогут удовлетворить требования всех пользователей. Поэтому пользователям необходимо иметь возможность устанавливать программное обеспечение на свои мобильные устройства, как они делают это на персональных компьютерах. Эта возможность позволяет пользователям изменять имеющийся у них набор приложений в любое время, удаляя некоторые приложения и устанавливая новые для того, чтобы обойти ограниченные возможности хранения устройства.
Для того чтобы инициализировать приложения на своих устройствах, пользователям нужна возможность обнаружения, выбора, покупки, загрузки и установки приложений с помощью своих мобильных устройств. Поскольку мобильные устройства не всегда имеют возможность соединения с какой-либо сетью или другим устройством посредством каких-либо способов, за исключением радиоинтерфейса, поддерживаемого беспроводной сетью, транспортировщики должны поддерживать установку приложений MIDP "по воздуху" (over-the-air (OTA)). Во время написания данной книги инициализация ОТА являлась краеугольным камнем инициализации приложений для мобильных устройств.
В настоящее время, однако, многие беспроводные устройства стали способны загружать приложения посредством некоторых других механизмов, отличных от ОТА, таких, как использование инфракрасных портов, модулей флэш-памяти и так далее. Тем не менее, возможно, что некоторые мобильные устройства по-прежнему будут иметь только беспроводную связь через радиоинтерфейс беспроводной сети в течение еще некоторого времени.
Беспроводные транспортировщики и другие поставщики услуг, поддерживающие инициализацию приложений для своих пользователей, предоставляют системы инициализации, с .которыми их пользователи могут взаимодействовать. Эти системы инициализации поддерживают инициализацию ОТА для М9бильных устройств, связанную с беспроводной сетью транспортировщика и шлюзом беспроводного Интернета (wireless Internet gateway (WIG)). На рисунке 10.1 показана одна из возможных схематичных логических диаграмм, которая отражает роль системы инициализации в сетевой инфраструктуре транспортировщика.
Инициализация ОТА требует установления "телефонного звонка", или соединения для передачи данных в сетях передачи данных, таких, как 2.5G или 3G - для соединения мобильного устройства, которое является клиентом, с инициализирующим сервером. Со стороны сервера диспетчер инициализации теоретически представляет основной компонент системы инициализации, который управляет различными стадиями процесса инициализации. Эти стадии включают динамическое обнаружение, представление описаний содержимого, поставку, фиксацию транзакции и создание счета.
На устройстве программное обеспечение агента пользователя взаимодействует с диспетчером инициализации. Программное обеспечение агента пользователя должно быть совместимо с механизмом взаимодействия, определяемым диспетчером инициализации. Возможны две схемы: обнаружение протокола Wireless Application Protocol (WAP) с HTTP-пересылкой и обнаружение WAP с сегментацией и повторным ассемблированием (SAR) WAP. Беспроводные транспортировщики на определенных рынках, особенно в Японии, создают инфраструктуры, которые поддерживают TCP/IP к телефонной трубке. Эти инфраструктуры способны поддерживать передачу HTML (XHTML) телефонной трубке посредством HTTP-транспортировки, перемещая WAP. Когда эта модель станет более распространенной в реализациях сетей 2.5G и 3G, интерфейсы диспетчера инициализации изменятся соответственно.
Регистрация приложений
Регистрация приложенийПеред тем как приложения получат возможность инициализации на устройствах, они должны быть зарегистрированы в системе инициализации. Зарегистрированное приложение - это то, что известно системе инициализации и может быть переслано на устройство.
Процесс регистрации приложения обычно инициирует человек или организация, которая разрабатывает приложение. Однако прежде, чем разработчик сможет зарегистрировать приложение, он или она должен обычно зарегистрироваться в качестве пользователя системы инициализации транспортировщика или члена программы разработчиков транспортировщика. Система инициализации может поддерживать регистрацию разработчиков через Web с использованием Web-интерфейса, основанного на HTML. После пользовательской регистрации разработчик может загружать приложения в систему.
Обычно системы инициализации поддерживают два основных механизма управления зарегистрированными приложениями. С помощью первого подхода разработчик загружает файлы JAR, JAD и манифеста приложения, как указано системой инициализации. Система инициализации физически содержит эти элементы в своем хранилище. С помощью второго подхода разработчик просто регистрирует URL и файл JAD (или метаданные, необходимые для создания файла JAD), которые показывают местонахождение, из которого диспетчер инициализации может извлекать приложение, необходимое во время инициализации. Разработчик или даже другой поставщик может фактически хранить файл JAR приложения.
Не все системы инициализации могут поддерживать как возможность внутреннего постоянного хранения файлов JAR, так и возможность ссылки на внешние файлы JAR. Как разработчик, вы должны предусмотреть, какие схемы приемлемы и какие, по вашему мнению, соответствуют сценариям использования ваших приложений. Возникают вопросы нетехнического плана - такие, как правовые вопросы обеспечения защиты от несанкционированного доступа к вашему приложению, которое может содержать ценную интеллектуальную собственность, или соглашения служебного уровня (service-level agreement (SLA)) с покупателем или транспортировщиком.
В обязанности разработчика входит поставка всей информации в форме, требуемой системой инициализации. Во время регистрации разработчик должен предоставить всю информацию, которая необходима во время процесса инициализации. По самой меньшей мере вам нужно предоставить JAD приложения и файлы манифеста, которые содержат информацию о платформе приложения, устройстве и требованиях ресурсов исполнения. Кроме того, система инициализации может поддерживать загрузку одного или нескольких собственных файлов с целью предоставления дополнительной информации о приложении. Например, вы имеете возможность предоставить файл XML, который описывает ваши предпочтения в лицензировании, оплате покупки, методах подтверждения покупки и так далее. Можно, конечно, определить атрибуты в файле JAD приложения, который описывает эти области. Это хороший пример, который демонстрирует, почему разработчики должны знать о возможностях системы инициализации или систем, которые они используют.
Система инициализации приложений
Процесс инициализацииНа практике процесс инициализации включает две основных фазы:
Согласование лицензии на программное обеспечение
Согласование лицензии на программное обеспечениеПроцесс согласования лицензии на программное обеспечение включает предоставление пользователю условий лицензирования и подтверждение пользователем его принятия условий. Этот этап осуществляется перед загрузкой приложения. Этот этап может быть выполнен на персональном компьютере вместо мобильного устройства.
Приложения, которые оговаривают требование покупки лицензии, должны предоставлять всю информацию, требуемую для процесса согласования лицензии. Эта информация должна быть доступна системе инициализации. Зависящая от приложения информация, такая, как тип лицензии, условия лицензирования и так далее, должна быть включена в файл JAD приложения.
Атрибуты MIDлета
Таблица 10.1. Атрибуты MID-лета, связанные с инициализацией приложения| Название атрибута MID-лета | Описание | Наличие |
| MI Diet- Dele te-Confirm | Определяет текстовое сообщение, которое должно быть представлено пользователю для подтверждения удаления набора MID-летов. Используется для уведомления пользователей во время работы AMS с приложением для того, чтобы освободить место для установки MID-лета | Необязателен |
| MIDlet-Description | Определяет текстовое описание набора MID-летов. Используется для представления описания пользователю во время обнаружения | Необязателен |
| MIDlet-Install-Notify | Определяет LJRL, на который пересылается отчет о состоянии установки MID-лета через HTTP-запрос POST | Необязателен |
| MIDlet-Jar-Size | Показывает размер (в байтах) файла JAR MID-лета. Используется AMS для определения того, содержит ли устройство достаточно общей памяти для установки набора MID-летов | Обязателен |
| MIDlet-Name | Определяет название набора MID-летов. Используется для предоставления названия набора MID-летов пользователям | Обязателен |
| MIDlet-Vendor | Определяет название поставщика набора MID-летов | Обязателен |
| MIDlet-Version | Используется для перемещения приложения | Обязателен |
Атрибут MIDlet-Install-Notify является необязательным атрибутом файлов JAD и manifest, который используется для инициализации. Его цель - дать программному обеспечению агента пользователя стандартный механизм передачи состояния установки в службу, предоставляющую набор MID-летов.
Значение атрибута MIDlet-Install-Notify должно описывать URL, на который агент пользователя посылает HTTP-запрос POST, содержащий информацию о состоянии установки. Посылать полный запрос POST в соответствии с рекомендациями приложения "Инициированная пользователем беспроводная инициализация" (Over the Air User Initiated Provisioning) спецификации MIDP входит в обязанности агента пользователя. То есть агенту пользователя необходимо получить некоторую информацию о приложении от AMS и включить ее - возможно, как параметры HTTP, - в запрос POST.
URL предоставляют некоторые программы систем инициализации, за исключением URL для определяемых приложением параметров, которые может предоставлять только AMS. Основная причина такой политики заключается в том, что программное обеспечение инициализации знает URL, который оно использует для сбора информации об установке, и оно может облегчить ношу разработчика, которому приходится разыскивать и предоставлять строку URL в каждом файле JAD. Разработчики должны знать о том, записывает ли система инициализации этот атрибут в файл JAD набора MID-летов. Если нет, разработчик должен включить этот атрибут в файл JAD.
Хорошей идеей является обеспечение того, что дескрипторы вашего приложения определяют значение атрибута MIDlet-Install-Notify, чтобы агент пользователя мог выдать состояние установки даже в случаях, когда набор MID-летов не был извлечен. Например, возможно, что URL, который определяет местоположение файла JAR и является значением атрибута MIDlet-Jar-URL, неправилен.
Удаление приложения
Удаление приложенияС точки зрения диспетчера инициализации, удаление приложения - это процесс получения уведомления о том, что приложение было удалено с устройства. AMS устройства заботится о самом удалении наборов приложений с устройства самостоятельно.
Разработчикам не нужно предусматривать уведомление сервера об удалении приложения. В этот процесс вовлечены только агент пользователя и сервер. Разработчики ' должны, однако, предусмотреть вероятность необходимого удаления приложения на клиенте при подготовке файла JAD приложения. Атрибут MIDlet-Delete-Conf irm является необязательным атрибутом файла JAD. Цель - предоставить AMS текстовое сообщение, предоставляемое пользователю для подтверждения удаления связанного набора MID-летов.
Диспетчеры инициализации, которые получают и сохраняют информацию об удалении приложений, могут предложить более гибкие сценарии инициализации. Например, пользователь.может захотеть удалить приложение с устройства, чтобы освободить память для другого приложения. Однако пользователь, возможно, захочет сохранить лицензию для первого приложения. Если диспетчер инициализации отслеживает эту информацию, он может проигнорировать этапы приобретения лицензии, оплаты и подтверждения в следующий раз, когда пользователь будет загружать первоначальное приложение.
Установка приложения и подтверждение установки
Установка приложения и подтверждение установкиУстановка приложения - это процесс установки программного обеспечения, которое уже находится на устройстве. После загрузки приложения браузер должен начать взаимодействие с AMS устройства, которая является компонентом, сохраняющим приложение на устройстве. AMS отвечает за установку программного обеспечения. Пользователь, однако, инициирует установку программного обеспечения посредством взаимодействия с AMS. AMS хранит приложения в определяемом устройством месте, но не в RMS MIDP, о которой вы узнали в главе 7.
Спецификация CLDC не требует того, чтобы AMS устройства хранила приложения MIDP, поскольку не все мобильные устройства поддерживают механизм постоянного хранения, такой, как файловая система. Альтернативным механизмом для AMS будет поддержка загрузки классификационных файлов с системы инициализации, необходимых для выполнения приложения. Виртуальная машина Java просто загружает классификационные файлы Java по мере их пересылки, выполняет приложение, а затем отбрасывает классификационные файлы, когда процесс установки завершен.
Приложения могут состоять из серверных компонентов и ресурсов, таких, как демоны сервера, базы данных и так далее. В таких случаях установка приложения должна включать установку этих серверных компонентов, а также компонентов клиента. Не все инициализирующие системы будут поддерживать эту возможность.
Информация дескриптора приложения должна включать информацию о том, как и когда должны быть установлены серверные компоненты. Например, дескриптор приложения должен показывать, должны ли серверные компоненты устанавливаться при первом использовании клиентского приложения или во время первой загрузки клиентских ресурсов. На сегодняшний день реальность заключается в том, что серверные компоненты должны быть установлены, сконфигурированы и протестированы до того, как клиенты начнут попытки получения доступа к ним.
Подтверждение установки включает информирование диспетчера инициализации об успешной установке. Уведомление об установке важно, поскольку пользователи обычно получают счета после того, как они установили приложение. Атрибут MIDlet-Install-Notify предоставляет способ для разработчиков приложений указывать URL, к которому должна быть послана HTTP-команда POST при успешной установке. Разработчики могут задавать значение этого атрибута. Иногда это значение будет задавать инициализирующее программное обеспечение, поскольку диспетчер инициализации лучше знает URL, который он установил для отслеживания установок.
Успешная установка подразумевает успешную загрузку. Когда установка завершена, пользователи могут выполнить приложение. Поэтому уместно потребовать оплату с пользователей за программное обеспечение при подтверждении установки. Конечно, некоторые приложения могут оплачиваться пользователями после первого использования, независимо от того, когда приложение было загружено. Здесь важно отметить, что устройство, а не приложение, чаще всего должно выполнять подобное подтверждение.
После уведомления система инициализации может генерировать событие создания счета. Обратите внимание, что подтверждение установки отличается от подтверждения покупки.
Загрузка приложения
Загрузка приложенияЗагрузка приложения - это процесс физической отправки приложения на мобильное устройство. Обычно этот процесс использует HTTP-механизм загрузки и браузер устройства для получения программного обеспечения.
Более совершенные системы будут поддерживать возможность контроля загрузки пользователем. Одним из примеров пользовательского контроля является возможность перезапуска прерванной загрузки пользователем. Прерывание загрузки может случиться, например, если вызов или данные соединения не прошли. Во время написания данной книги лучшим, на что мы могли надеяться, было то, что устройство выполняет частичные загрузки.
Некоторые системы поддерживают лишь возможность перезапустить загрузку с начала. Даже в этом случае пользователю не придется проходить вновь через весь цикл обнаружения, аутентификации и покупки. Система инициализации должна поддерживать достаточную информацию о состоянии операции, чтобы позволить пользователю перейти непосредственно к этапу загрузки.
Более совершенные системы дадут пользователю возможность перезапускать загрузку из точки, на которой произошло прерывание. Это свойство предпочтительно, поскольку оно сокращает продолжительность передачи и уменьшает использование полосы пропускания. Более совершенные системы также поддерживают как небезопасную (HTTP), так и безопасную (HTTPS/SSL) загрузку приложений.
Платформа программирования J2ME для портативных устройств
Apxитeктypa приложения
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 решения беспроводного Интернета
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-адрес. Тем не менее, безопасность, производительность и расширяемость останутся важными вопросами.
Беспроводные устройства получают
Среда беспроводного приложенияНа уровне приложений внешние интерфейсы, программные интерфейсы и транспортные механизмы представляют один из видов описания системы. В подобном ракурсе рассмотрения заинтересованы разработчики приложений, которые должны знать, как получать доступ и как взаимодействовать со службами программного обеспечения системы.
В других ракурсах описываются другие части системы. Например, системы имеют модели состояний, модели обработки транзакций и так далее, которые не могут быть в достаточной мере описаны с помощью диаграммы, представленной на рисунке 11.1. На некотором этапе процесса разработки приложения разработчикам необходимо знать эти характеристики систем, с которыми они взаимодействуют.
На рисунке 11.2 представлена схематичная логическая диаграмма, на которой показаны типичные компоненты системного уровня, находящиеся в системах беспроводного Интернета. На диаграмме представлены некоторые из наиболее часто используемых механизмов транспортировки, которые соединяют компоненты между собой. Цель данной диаграммы - дать разработчикам некоторый ракурс рассмотрения типов сред, которые поддерживают беспроводные приложения.
Среда, показанная на рисунке 11.2, является той, в которой беспроводные приложения устанавливаются и запускаются. Эти приложения включают не только приложения телефонов - такие, как приложения МID, - но также серверные компоненты, которые поддерживают службы, используемые приложениями на телефоне.
Серверные беспроводные приложения будут находиться внутри сети intranet транспортировщика. Они обычно состоят из нескольких систем аппаратных и программных компонентов, каждая из которых предоставляет собой одну или несколько служб приложений. Некоторые из этих служб приложений будут вести себя как стандартные основанные на Web приложения и предоставлять HTML-интерфейс, который требует браузера с клиентской стороны.
Платформа J2ME, и MIDP в частности, создают платформу, которая поддерживает так называемых интеллектуальных клиентов. Они могут быть приблизительно определены как приложения, которые выполняют значительно больший объем обработки на клиенте, чем Web-приложения. Огромное количество приложений MIDP будет приложениями клиент-сервер или распределенными приложениями, взаимодействующими с компонентами клиент-сервер. Эти приложения M1DP будут взаимодействовать с системами, которые находятся в сети intranet беспроводного транспортировщика.
Например, приложениям MIDP клиент-сервер или распределенным требуются возможности организации сети и коммуникаций для того, чтобы получать доступ к серверным компонентам в сети intranet транспортировщика. Хотя платформа MIDP скрывает подробности абстракций и реализации механизмов коммуникации, о которых вы узнали в главе 8, полезно иметь представление о том, как системы поддерживают различные службы.
Большая часть Интернета стандартизирована на HTTP как на основном транспортном механизме сеансового уровня. Протоколы уровня приложений туннелируются при помощи HTTP, что связано с вопросами безопасности.
Интерфейсы и транспортные механизмы
Беспроводные приложенияВ этом разделе кратко описываются типы приложений и служб приложений, которые обычно доступны на порталах беспроводного Интернета. Мы не будем обсуждать здесь разработку этих служб, потому что они могут включать комплексные архитектуры, которые требуют Web-серверов, серверов приложений и других компонентов системного уровня. Цель данного раздела - дать вступительное теоретическое описание среды беспроводного приложения и побудить разработчиков начать думать о том, как разрабатывать приложения для данной среды.
Обратите внимание, что на рисунке 11.2 не представлен вид, который структурирован настолько, чтобы показать системы внутри сети intranet транспортировщика, которая предоставляет индивидуальные программные службы. Причина этого кроется в том, что системы компонентов, которые беспроводные транспортировщики используют для предоставления обмена сообщениями, персонализации, служб личной информационной системы и так далее, часто являются комплексными собственными программными системами сторонних разработчиков. Интерфейсы и программные интерфейсы приложений для этих систем являются собственными, а описание коммерческих продуктов лежит за пределами темы данной главы или книги.
Обмен сообщениями
Обмен сообщениямиТеоретически существует, грубо говоря, три типа обмена сообщениями в беспроводных средах:
В беспроводных средах мгновенный обмен сообщениями обычно означает обмен SMS-сообщениями, поскольку системы каналов передачи SMS обычно используются для реализации службы IM. Адреса сообщений состоят из MSN. Каналы передачи SMS предоставляют мгновенный обмен сообщениями в пределах того, что сообщения посылаются "немедленно". Конечно, получается ли сообщение немедленно, зависит от нагрузки системы, управления потоком, ограничений полосы пропускания и так далее, что может привести к задержкам, которые будут выше, чем в технологиях мгновенной передачи сообщений проводных сетей.
Электронная почта (e-mail) - это передача сообщений произвольной длины с помощью модели с промежуточным хранением. Термин электронная почта подразумевает использование схемы адресации, сходной со схемой e-mail Интернета, пример которой показан ниже:
user@some-host.com
Конкретные возможности систем электронной почты в беспроводных сетях зависят от реализации. Например, не все реализации могут поддерживать доставку сообщений произвольной длины. Если электронная почта доставляет сообщения через стандартный канал передачи SMS, сообщения ограничиваются до 128 байтов. В Японии, например, два из основных беспроводных транспортировщиков поддерживают электронную почту на мобильных устройствах с длиной сообщений до нескольких килобайт. Эти системы используют собственную систему пересылки, полностью независимую от SMS.
Интегрированная система обработки сообщений - это понятие единого механизма, который интегрирует другие системы обработки сообщений и дает пользователям единую точку доступа ко всем службам обработки сообщений. Интегрированная система обработки сообщений может иметь пользовательский интерфейс как для Web-доступа, так и доступа с устройств с беспроводной связью. Коммерческие поставщики предлагают интегрированные системы обработки сообщений, которые обычно включают API для интерфейсов приложений. Например, приложение MIDP может взаимодействовать с системой через внешний API и представлять пользователю интегрированное отображение SMS и электронной почты. Этот сценарий работает только на телефонах, которые его поддерживают? Смысл в том, что интегрированные системы обработки сообщений извлекают подробную информацию о взаимодействии с отдельными системами обработки сообщений, такими, как серверы электронной почты или серверы SMS.
Отражает эту архитектуру между
Рисунок 11.2 отражает эту архитектуру между intranet, Интернет и пользовательскими объектами.Беспроводная сеть, однако, ставит некоторые уникальные проблемы. Беспроводные сети используют сложные наборы собственных протоколов, которые представляют собой решения практических проблем реализации организации межсетевого обмена между беспроводными интерфейсами. Эти собственные наборы развиваются параллельно совершенствованию беспроводных систем и их продвижению к поддержке TCP/IP на телефонных трубках в системах третьего поколения (3G). Тем не менее, уровни, лежащие под сетевым уровнем, все еще очень отличаются от уровней, расположенных в проводных сетевых комплексах.
Персонализация
ПерсонализацияПерсоначизация - это поддержка указания атрибутов, которые определяют контекст зарегистрированного системного пользователя. Пользовательский контекст включает указание предпочитаемых настроек для следующих категорий:
Приложения личной информационной системы
Приложения личной информационной системыПриложения личной информационной системы (Personal information management (PIM)) включают такие приложения, как календари с возможностью напоминания о событиях, адресную книгу, записную книжку и так далее. Все это - стандартные утилиты сайтов порталов Интернета. Проблема беспроводных сред заключается в поддержке : пользовательскогр интерфейса для этих приложений.
Мобильные устройства в настоящее время обладают не настолько большими ресурсами, чтобы поддерживать платформы, подобные тем, что созданы для настольных компьютеров, с мощными Web-браузерами. Кроме того, пропускная способность беспроводных сетей недостаточна для поддерживания огромного количества информации, создаваемой Web-интерфейсами, подобными тем, что используются в интернет-порталах.
Протокол доступа к сообщениям в Интернете (The Internet mail application protocol (IMAP)) и почтовый протокол (post office protocol (POP)) являются двумя наиболее распространенными протоколами, поддерживаемыми почтовыми серверами. Беспроводные сети будут либо поддерживать эти протоколы на телефонах, либо реализовывать собственные.
Службы календаря обычно определяют свои собственные API и интерфейсы, которые настроены как для Web-доступа, так и для доступа приложений. Некоторые системы определяют один интерфейс HTML-через-НТТР. Клиентскому приложению календаря, которое использует сервер, на котором находится серверный компонент календаря, пришлось бы создавать запросы HTTP в соответствии с API, определенным сервером. Календарное уведомление может использовать SMS для посылки уведомлений клиентам. Приложениям MIDP, например, может понадобиться наличие некоторого способа взаимодействия с родным программным обеспечением обработки сообщений на телефоне. Во время написания данной книги спецификация MIDP не связывала интерфейсы с программным обеспечением родной системы. Ожидается, что спецификация MIDP-NG (следующее поколение) будет связывать интерфейсы родного устройства с приложениями MIDP.
Происхождение терминология и понятия
Происхождение, терминология и понятияТермины беспроводный Web и беспроводный Интернет относятся к среде, в которой беспроводные радиоустройства могут получать доступ к World Wide Web и Интернету. Эти термины являются чем-то абстрактным по той причине, что они не несут информации об архитектуре или физической природе среды. Беспроводной Интернет, как и Интернет, является сетевым комплексом, объединением сетей. Однако, в отличие от Интернета, это объединение беспроводных и проводных сетей.
Беспроводные сети связываются с проводными сетями - и с Интернетом - посредством шлюза беспроводного Интернета (wireless Internet gateway (WIG)), шлюзом, состоящим из аппаратного и программного обеспечения, который соединяет беспроводную сеть транспортировщика с его собственной проводной сетью intranet. Шлюзы беспроводного доступа в Интернет обычно состоят из принадлежащего провайдерам программного и аппаратного обеспечения, которое позволяет взаимодействовать с мобильными центрами коммутации (mobile switching center (MSC)). Вместе все эти компоненты реализуют определенные типы систем беспроводных коммуникаций. Например, многие из производителей мобильных телефонов предлагают свои собственные WIG. Они работают только с определенными системами беспроводных коммуникаций и с определенными базовыми станциями и телефонами.
На рисунке 11.1 показана схематичная логическая диаграмма, которая представляет связи между компонентами беспроводной сети, шлюзами WIG и сетями intranet транспортировщика. WIG дает беспроводной сети - и беспроводным устройствам - доступ в Интернет посредством проводной сети intranet поставщика беспроводной связи. Intranet беспроводного транспортировщика соединяется с проводными сетями или сетевыми комплексами, которые дают ему доступ к Интернету.
Системные качества
Системные качестваОпределение того, отвечает ли прототип указанному набору требований, является центральным для любой архитектурной методологии или разработки. Поэтому указание полного набора требований является важной частью любого процесса разработки. В следующем списке содержится две категории требований:
Одним из краеугольных камней SunTone AM, который отличает ее от других методологий, является ее сконцентрированность на системных качествах. Важным критерием при оценке отличия хорошей архитектуры от сомнительной является определение того, насколько хорошо она поддерживает системные качества, определяемые требованиями. Конечно, чтобы создать всеобъемлющую архитектуру, разработчик должен взглянуть на систему со всех ракурсов.
SunTone AM определяет три измерения - ярус, уровень и системное качество - каждое из которых представляет собой уникальный взгляд на систему. Эти измерения поддерживают разбиение системы на ортогональные срезы, которые отражают соблюдение системой различных категорий требований.
В этой главе не описываются понятия ярусов и уровней, такое описание уведет слишком далеко в рассмотрение того, что такое архитектура и как ее осуществлять, и подходит больше для обучения тому, как разрабатывать многоярусные системы. Большая часть этой главы посвящена освещению архитектурных принципов для того, чтобы помочь перенести понятия на реальные системы и понять их характеристики.
Эта глава сконцентрирована на понятиях, связанных с системными качествами, поскольку это то, что наиболее часто упускается, и поскольку системные качества очень важны для достижения производительности, безопасности и массового распространения в средах беспроводного Интернета.
В контексте системной архитектуры системные качества включают следующие категории:
Центральным принципом SunTone AM является необходимость обращения к системным качествам с начального этапа проектирования архитектуры и разработки. Нереально ожидать, что вы будете способны изменить или перепроектировать ваши приложения в конце их цикла разработки для приспособления к системным качествам. Статистика индустрии поддерживает мысль о том, что большинство усилий, которые прилагаются для реализации соответствия системным качествам в самом конце процесса разработки, оказываются уже напрасными.
Качества пользовательского уровня. Качества пользовательского уровня включают практичность и доступность. Практичность - это измерение того, насколько интуитивно и просто использование приложения. Разработка пользовательского интерфейса должна быть приспособлена к пожеланиям пользователя. Разработка, поддерживающая пользовательский интерфейс, должна стоять на втором месте. Эта задача скорее всего возникнет в приложениях MIDP, поскольку пользовательский интерфейс MIDP ставит перед разработчиками задачу создания коммерчески рентабельных пользовательских интерфейсов. Разработчикам, возможно, придется пойти на компромисс в свойствах после экспериментирования с тем, насколько легко поддерживать интуитивный, практичный интерфейс.
Доступность - это измерение того, насколько доступно и легко для всех людей использовать приложение, включая тех, что имеют плохое зрение, и инвалидов. Среда MIDP не предназначена для работы с доступностью для инвалидов, как это делают среды AWT или Swing.
В контексте ограниченных возможностей ввода и отображения устройств MIDP доступность также подразумевает характеристики разработки приложений, которые обеспечивают интуитивные и простые пользовательские интерфейсы. По самой меньшей мере разработчик должен учитывать аспекты, которые могут сделать визуальное представление более читабельным, такие, как шрифты, размер шрифтов и так далее.
Качества уровня служб. Качества уровня служб включают производительность, надежность и доступность. Производительность - это измерение таких характеристик, как быстрота реагирования, время ожидания и пропускная способность. Для разработчиков приложений на MIDP производительность на клиентах является очень важным моментом. Но время ожидания и пропускная способность сетевых коммуникаций также являются важными задачами для распределенных приложений и приложений клиент-сервер. Например, на самом деле сложно написать активную игру для нескольких игроков на MIDP на сегодняшний день из-за времени ожидания сети.
Надежность - это измерение вероятности того, что система будет работать на должном уровне. Надежность приложений близко связана с надежностью компонентов платформы, на которых строится приложение. Например, надежность клиентского приложения MIDP частично зависит от надежности соединения с сервером.
Доступность - это измерение того, возможно ли получение доступа к службе (предоставляемой приложением). Доступность связана с надежностью. Различие между надежностью и доступностью заключается в том, что надежность относится к отдельным компонентам, в то время как доступность описывает степень, в которой доступна служба. Например, один из нескольких компонентов, который предоставляет резервирование, может сломаться, хотя служба может все равно остаться доступной.
Хотя доступность не является на самом деле проблемой при разработке отдельных приложений MIDP, она влияет на распределенные приложения MIDP, которые используют серверные компоненты. Вы не можете создать легкодоступное приложение MIDP, если оно использует сетевые службы, которые не являются легкодоступными. Это хороший пример, отражающий то, почему разработчик MIDP должен переносить архитектурный взгляд на все аспекты среды беспроводного Интернета, даже если он не разрабатывает и не создает сетевые службы, которые используются приложениями MIDP.
Качества стратегического уровня. Качества стратегического уровня включают расширяемость и гибкость. Расширяемость - это измерение степени, для которой приложение может приспосабливаться к увеличению одновременных пользователей во время поддержки одного уровня производительности. Расширяемость серверных компонентов влияет на клиентов MIDP. Разработчики приложений MIDP, которые запрашивают данные с серверных компонентов, должны рассматривать, какая модель доступа наилучшим образом смягчает негативное воздействие большого объема пользователей. Например, возможно, что клиент MIDP запросит больше данных на запрос и сделает меньше запросов. Снижение производительности может не быть очевидным при небольших объемах пользователей, но когда приложение устанавливается на больших беспроводных средах, снижение производительности может быть радикальным.
Гибкость - это измерение того, насколько легко приложение может приспособиться или объединиться с новыми или измененными службами. Например, разработчик нашего почтового клиента MIDP может захотеть предугадать необходимость соединения с обоими почтовыми серверами РОРЗ или ШАР. Это решение может подтвердить реали- } зацию образца разработки, который спрячет подробности механизма соединения от большинства приложений, делая легким добавление поддержки для новых почтовых протоколов программного уровня.
Другим примером является гибкость, с которой клиент может анализировать новые протоколы программного уровня или форматы данных; полученных из служб. Поставщики служб беспроводного Интернета могут периодически переконструировать свои службы. Гибкость вашего приложения MIDP может сохранить вам много времени и усилий, так что вы можете избежать переконструирования вашего приложения для приспособления к изменениям в сетевых службах и серверных компонентах. Взгляд на службу беспроводного Интернета с точки зрения архитектора позволит вам предвосхитить такого рода проблемы.
Качества системного уровня. Качества системного уровня включают безопасность, управляемость и восстанавливаемость. Безопасность - это измерение того, насколько хорошо приложение блокирует вторжения и предотвращает повреждения, наносимые несанкционированными пользователями.
Безопасность приложения также является важной задачей для всех приложений. Приложения MIDP могут быть защищены паролем, например. Безопасность уровня приложений также включает защиту от несанкционированного доступа к данным приложения. Например, приложениям парольной защиты на мобильных устройствах придется гарантировать, что пароли недоступны среднему пользователю или кому-либо, кто украл ваш телефон. AMS устройства может также поддерживать механизм защиты, который защищает все мобильное устройство целиком от несанкционированного использования приложений.
Приложения MIDP, однако, должны также рассматривать необходимость защиты в распределенной среде. Конечно, это включает взаимосвязь со службами безопасности. Сюда же относятся такие задачи, как определение того, какие сайты Интернета пользователи могут посещать или к каким устройствам интернет-пользователи могут получать доступ.
Понимание ограничений, связанных с безопасностью беспроводной среды, налагаемых транспортировщиком, может повлиять на выбор свойств вашего приложения MIDP. Более того, это может также повлиять на то, какую установку вашего приложения вы выбираете. Например, многие транспортировщики позволяют инициализацию приложений MIDP только с партнерских сайтов, чтобы избежать проблемы, связанной с тем, что пользователи загружают приложения из неофициальных источников, которые не несут ответственность за нанесение вреда устройствам пользователей или сети.
Управляемость - это измерение того, насколько легко управлять системой, контролировать ее и отслеживать операционные характеристики, которые могут указывать на проблемы в системе. Разработчик службы должен учитывать необходимость разработки системы с учетом поддержки управляемости. Разработчик приложения MIDP, однако, должен понимать и учитывать то, как приложение приспосабливается к модели управляемости службы. Например, каким образом почтовый клиент определяет временное ограничение в случае, если почтовый сервер не доступен и на одну сотую процента?
Восстанавливаемость - это измерение того, насколько легко восстановить систему. Это качество распространяется на все аспекты разработки системы или даже приложения MIDP. Вы должны учитывать не только восстанавливаемость самого приложения MIDP, но и влияние восстанавливаемости серверных компонентов на клиента MIDP.
Системные качества влияют на приложения MIDP различными способами. Во-первых, приложения MIDP - те, что находятся на мобильных устройствах, - должны быть рассмотрены с точки зрения того, насколько хорошо они работают с системными качествами.
Во-вторых, клиенты MIDP могут работать совместно с серверной службой, которая находится где-нибудь в беспроводном Интернете. Один и тот же разработчик может проектировать и клиентские, и серверные компоненты. Разработчики должны применять всеобъемлющие принципы построения архитектуры при разработке серверных компонентов. Среда платформы беспроводного Интернета является наиважнейшей средой для построения архитектуры из-за ее требований к массовой расширяемости, производительности, безопасности и так далее.
Наконец, клиенты MIDP должны знать системные качества любой службы, которую они используют. Даже если атрибуты этих служб лежат за границами контроля разработчика MIDP, важно понимать их ограничения и то, как они влияют на функциональные и системные качества приложения MID.P.
Службы местоопределения
Службы местоопределенияСлужбы местоопределения - это службы приложений, которые используют информацию о географическом местоположении клиента и выдают результаты, относящиеся к информации о данном местоположении. Службы местоопределения не являются исключительной собственностью мобильных устройств и приложений, хотя основные усилия индустрии вкладываются в совершенствование служб местоопределения для мобильных устройств. Службы местоопределения могут быть представлены в Интернете как для мобильных клиентов, так и для Web-клиентов.
Идея служб местоопределения заключается в обеспечении клиента информацией, которая относится к месту нахождения клиента. Например, службы приложений могут захотеть отображать объявления в областях, близких пользователю. Процесс включает определение правильного регионального контекста, обработку определяемой местонахождением информации и представление результатов пользователю.
Службы местоопределения могут ссылаться на статическую информацию локальных настроек или вычислять локальную информацию динамически. Например, профиль пользователя и информация о настройках могут состоять из пользовательских предпочтений для локализованного контекста. Эта информация может указывать службам местоопределения использовать предпочтительный для пользователя локальный контекст независимо от реального местоположения пользователя. Большинство мобильных приложений, однако, определяют местоположение мобильного устройства динамически.
В настоящее время существует несколько различных подходов, разработанных для предоставления информации о месте нахождения службам местоопределения:
Локальная информация, создаваемая сетевыми системами, менее точна, чем информация, создаваемая системами GPS. В основанных на сети системах беспроводная сеть сама определяет положение мобильного устройства. Мобильный центр коммутации (mobile switching center (MSC)) должен содержать программное обеспечение, которое может пересылать эту информацию службам приложения. Поскольку MSC обычно прозрачен для приложений, транспортировщик должен создавать объединение между MSC и службами приложения. То есть эти системы должны быть приспособлены друг к другу.
Системы полуавтоматической GPS включают неполные приемники GPS на мобильном устройстве, выделенные серверы полуавтоматической GPS в сети intranet транспортировщика и интеграцию с MSC. Как и в основанных на сети системах, транспортировщик должен предоставить эту инфраструктуру и определить механизм взаимодействия со службами приложения.
Виды приложений MIDP, которые разработчики MIDP могут создавать, зависят от типов и места нахождения доступных служб. Более того, разработчикам необходимо оценить альтернативы каждого из вышеупомянутых трех подходов к предоставлению определяемой местоположением информации. Каждый из них имеет свои сильные и слабые стороны, и каждый влияет на виды свойств, которые могут поддерживаться в реальности. Несмотря на различия между различными типами систем местоопределения, разработчикам приложений на MIDP придется использовать ту схему, которая поддерживается.
Структуры архитектуры
Структуры архитектурыСтруктура архитектуры - это базовая теоретическая структура, которая поддерживает определение архитектурной модели. Реальные архитектурные методологии описывают свои собственные структуры, поддерживающие определение элементов методологии, а именно, тех, что описывают архитектурные процессы, определение видов системы и артефактов, которые представляют собой конкретное определение разработки системы.
SunTone AM предлагает структуру, чьи основные характеристики включают:
Полное описание или объяснение даже одного из этих принципов лежит за пределами возможностей данной книги. Краткое описание, предоставляемое здесь, тем не менее, предназначено для ознакомления вас с понятиями, связанными с этими архитектурными принципами, и дает вам, разработчику приложений J2ME, понятие о том, как получить преимущества от искусства и науки построения архитектуры. Для более тесного знакомства с архитектурой и архитектурной методологией SunTone AM смотрите книгу "Dot-Corn & Beyond".
Первый элемент структуры SunTone AM, случай использования, - это описание системного требования. Случаи использования собирают и документируют системные требования в читабельной для человека форме. Невозможно преувеличить значение того, что разработка будет отвечать требованиям системы. Процесс сбора требований является деятельностью, дополняющей построение архитектуры. Существует несколько хороших книг, которые объясняют случаи использования в полном объеме, такие, как книга Элистера Кокбарна (Alistair Cockburn) "Writing Effective Use Cases", которая указана в разделе "Справочная литература" в конце данной книги.
Как правило, невозможно собрать все системные требования в достаточном объеме с первого раза. По этой причине SunTone AM подчеркивает важность выполнения итеративного сбора требований. Так как понятие системы развивается вместе с приобретаемым в процессе ее создания опытом разработчиков, маркетингового персонала и других работников, требования расширяются или становятся более четко очерченными и их описания могут быть заданы более точно.
Принцип итеративной разработки применяется на каждом этапе процесса разработки, а не только при сборе требований. Итеративная разработка связана с идеей выполнения нескольких повторений всего цикла разработки. Причина включения всех этапов в процесс итеративной разработки заключается просто в том, что сложно реализовать что-либо правильно в первый раз. Цикл разработки включает следующие этапы:
Второй этап начинается с повторной попытки сбора требований. Разработчик вновь исследует требования для того, чтобы лучше их понять или чтобы определить, нужны ли на самом деле определенные свойства или могут ли быть переопределены сценарии, определяющие модель использования определенных свойств. Дальше следует второй цикл создания архитектуры, разработки, создания прототипа и так далее через все этапы процесса.
Основная проблема процесса разработки заключается в том, чтобы определять в конце каждого цикла, отвечает ли система всем указанным требованиям в достаточной мере. Если нет, необходим еще один цикл. Сила итеративной разработки заключается в возможности для разработчиков создавать системы, которые отвечают всем требованиям эффективнейшим образом.
Ключевым моментом этой эффективности является понятие создания прототипов отдельных функциональных возможностей системы в каждом цикле. Эта философия отличается от традиционного "водопадного" подхода к разработке программного обеспечения. Например, при разработке нашего почтового клиента MIDP первый этап должен быть связан с созданием базовых свойств, присутствие которых обязательно для всех остальных свойств, таких, как вход пользователя в систему и выборка почтовых заголовков и сообщений. Если тестирование выявило, что эта базовая инфраструктура не работает, разработчики узнают об этом в самом начале разработки и смогут исправить это до того, как решатся углубиться дальше и создадут дополнительные свойства на треснувшем фундаменте. Более того, этот подход позволяет избежать комплексной интеграции массы свойств в конце одного цикла разработки, когда становится намного сложнее локализовать и исправить причину проблемы.
Бизнес: Предпринимательство - Малый бизнес - Управление
- Бизнес
- Разновидности бизнеса
- Планирование бизнеса
- Управление бизнесом
- Предпринимательство
- Русское предпринимательство
- Управление и предпринимательство
- Малый бизнес
- Виды малого бизнеса
- Русский малый бизнес
- Управление малым бизнесом
- Posix для малого бизнеса
- Телефония как малый бизнес
- Телефония на Java для малого бизнеса