Сообщений 27    Оценка 290        Оценить  
Система Orphus

J2ME. С чего начать?

Автор: Данилов Кирилл aka Donz
Источник: RSDN Magazine #4-2007
Опубликовано: 15.03.2008
Исправлено: 10.12.2016
Версия текста: 1.0.2
Введение
Платформа, ее конфигурации и профили
Средства разработки и эмуляторы
Манифест и дескриптор приложения
«Hello, World!» и самые основы
Использование графики и анимации
Особенности разработки на J2ME
Выбор тестовых устройств и список портирования
Настройка IDE и сборка проекта
Полезные ссылки
Книги
Заключение

Введение

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

Статья подразумевает знание языка Java и основных пакетов J2SE (java.lang.*, java.util.*, java.io.*), и касается в основном CLDC и MIDP.

Если вы не знаете языка или только-только начали знакомство с Java, то эта статья пока не для вас, и единственно правильный совет – прежде чем переходить на J2ME, вы должны уверенно себя чувствовать в J2SE.

Платформа, ее конфигурации и профили

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

Устройства, на которых сможет работать J2ME-приложение, определяются поддерживаемой конфигурацией (конфигурация определяет самые базовые классы, такие как класс System, Runtime, Thread и т.д., то есть является фундаментом платформы) и профилем платформы (который определяет более специфичные для устройства свойства).

Существующие конфигурации:

Конфигурация CLDC (Connected Limited Device Configuration) предназначена для устройств с ограниченным объемом памяти и вычислительной мощностью. Содержит только базовые пакеты java.lang.*, java.io.*, java.util.*, javax.microedition.io.* и добавленный в версии 1.1 пакет java.lang.ref.*. Пакеты, совпадающие с J2SE, содержат минимальный набор классов, необходимых для создания приложений.

По реализации пересекающихся с J2SE классов, версии байт-кода CLDC 1.0 соответствует JDK 1.1, CLDC 1.1 –- JDK 1.3. Иногда к названию конфигурации добавляют HI, что означает HotSpot Implementation (виртуальная машина с улучшенными алгоритмами оптимизации выполняемого кода в целом и часто выполняемых кусков кода в частности, для J2SE она стала виртуальной машиной по умолчанию с версии 1.3).

CDC (Connected Device Configuration) предназначена для устройств с достаточным объемом памяти и производительностью. В дополнение к пакетам CLDC содержит классы для работы с архивами zip и jar, более развита рефлексия, работа с сетью, коллекциями, текстом, и определен класс BigInteger для операций с большими числами.

Профили:

MIDP (Mobile Information Device Profile) – профиль для конфигурации CLDC. Он содержит пакеты для работы с графикой, звуком, взаимодействия с консолью (клавиатура и экран), базовый набор классов для отображения стандартных экранов и control-ов. Существуют две основные версии API – 1.0 и 2.0 (самих версий MIDP несколько больше, но в них изменения касаются безопасности и не затрагивают само API).

IMP (Information Module Profile) – подкласс профиля MIDP для встраиваемых устройств, где не предполагается взаимодействие с пользователем. В этом профиле отсутствуют возможности работы с экраном.

Foundation Profile – профиль для конфигурации CDC, не имеющий функциональности для работы с GUI. Предназначен для встраиваемых устройств.

Personal Basis Profile – профиль для конфигурации CDC, содержащий основные элементы GUI. Является надстройкой над Foundation Profile.

Personal Profile – профиль для конфигурации CDC, содержащий графический интерфейс пользователя, основанный на AWT. Является надстройкой над Personal Basis Profile.

Дальнейшие возможности устройств с точки зрения разработчика обуславливаются поддержкой дополнительных пакетов, например, работа с мультимедиа – MMAPI (JSR-135).

ПРИМЕЧАНИЕ

JSR (Java Specification Request) – официальная спецификация какой-либо Java технологии или библиотеки, созданное сообществом JCP (Java Community Process).

Место платформы J2ME и иерархия конфигураций и платформ хорошо представлена на следующей схеме (источник http://java.sun.com):


Рис. 1 Иерархия платформ Java.

На рисунке упоминается KVM вместо JVM для конфигурации CLDC. Это связано с тем, что KVM (Kilobyte Virtual Machine) является намного более упрощенной версией виртуальной машины, чем ее привыкли представлять разработчики настольных приложений, и разница в названии как раз акцентирует внимание на значительных отличиях, часть которых будет разобрана ниже.

Далее рассматривается только связка CLDC 1.0 (версия конфигурации на этапе знакомства с платформой не имеет значения) и MIDP 2.0 (хотя большая часть материала в равной мере относится и к MIDP 1.0), как наиболее распространенная. Наверное, я не сильно ошибусь, если оценю долю MIDP-приложений (они также называются мидлетами) среди всех J2ME-программ в 95-99%. Такая популярность обусловлена распространенностью устройств с поддержкой этого профиля (почти все не самые бюджетные мобильные телефоны, смартфоны, КПК) и обширной аудиторией потенциальных пользователей (у кого в наше время нет мобильника?).

Средства разработки и эмуляторы

Для удобства советую прописать в системную переменную PATH пути до каталогов bin JDK и SE SDK. При установке в каталоги по умолчанию это будут:

C:\Program Files\Java\jdk1.6.0_01\bin\

и

C:\SonyEricsson\JavaME_SDK_CLDC\PC_Emulation\WTK2\bin

Манифест и дескриптор приложения

Для установки приложения необходимы два файла: jad и jar. Точнее, эти файлы необходимы для установки OTA (Over The Air), то есть через браузер телефона. Устройствам, которые позволяют устанавливать мидлеты через Bluetooth, инфракрасный порт или другие средства связи, jad обычно не требуется.

jad – это дескриптор приложения (Application Descriptor) (MIME-тип text/vnd.sun.j2me.app-descriptor, его необходимо прописать в настройках Web-сервера, чтобы он понимал, что делать при запросе данного типа файлов).

jar – это сама программа, точнее, это пакет мидлетов (Midlet Suite) (MIME-тип application/java-archive).

Для корректной обработки запросов файлов этих типов Web-сервером их необходимо прописать в его конфигурации. Например, для Apache добавьте в файл .htaccess следующие строки:

AddType text/vnd.sun.j2me.app-descriptor .jad
AddType application/java-archive .jar

В абсолютном большинстве случаев в одном jar-файле находится один мидлет, но их может быть и больше. Формат мобильного jar’а не отличается от формата настольного – он так же создается утилитой jar, и так же содержит манифест.

Возникает логичный вопрос: «Для чего нужен дескриптор, если есть манифест?». Дело в том, что мидлеты в основном предназначены для установки OTA, а значит, придется тратить трафик и время на закачку приложения. Дескриптор же обычно занимает не больше трехсот байт, и из него можно сразу узнать название приложения, производителя, версию, размер приложения и самое главное – есть возможность отсеять неподходящий для данного устройства мидлет до начала его установки.

Спецификация дескриптора приложения приведена в спецификации MIDP любой версии. Более удобный вариант можно посмотреть на сайте Sun – Описание атрибутов дескриптора. Главные моменты перечислены ниже:

Название атрибута Описание
MIDlet-Name пользовательское название пакета мидлетов
MIDlet-Version версия мидлета
MIDlet-Vendor разработчик или издатель мидлета
MIDlet-Jar-URL абсолютный или относительный (от местоположения самого дескриптора) URL до jar-файла
MIDlet-Jar-Size размер jar-файла в байтах
Название атрибута Описание
MIDlet-<n> Описание каждого мидлета в пакете мидлетов (формат будет разобран ниже)
MicroEdition-Profile Профиль содержащихся в пакете мидлетов
MicroEdition-Configuration Конфигурация мидлетов

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

Название атрибута Описание
MIDlet-Description Описание мидлета
MIDlet-Icon Пиктограмма пакета мидлета, в общем случае показывается именно она. Если пакет мидлетов содержит только один мидлет, то в этом атрибуте следует указать то же, что и в MIDlet-1.
MIDlet-Info-URL URL подробного описания пакета мидлетов.
MIDlet-Data-Size В спецификации указано, что это минимальный размер хранилища (RecordStore). На практике же часто случается так, что этот параметр задает фактический размер хранилища, который нельзя ни уменьшить, ни увеличить.
MIDlet-Permissions Права, необходимые приложению для функционирования. Если указать этот параметр, то запрошенные права будут показаны пользователю при установке, и он заранее сможет отказаться от мидлета, если решит, что не хочет предоставлять эти права. Для неподписанных мидлетов указывать этот атрибута не обязательно, так как на действия, которые должны контролироваться пользователем, служба управления приложением (Application Management Service, AMS) все равно выдаст запрос на подтверждение.
MIDlet-Permissions-Opt Права, которые могут понадобиться приложению для функционирования. То же самое, что и предыдущий атрибут с той разницей, что права, запрошенные в этом параметре, необязательны для нормального функционирования приложения, а могут понадобиться для какой-либо вспомогательной функции.
MIDlet-Push-<n> Регистрирует мидлет <n> для его вызова при указанном входящем соединении.
MIDlet-Install-Notify URL, по которому AMS сделает запрос при установке мидлета или каких-либо проблемах при установке.
MIDlet-Delete-Notify URL, по которому AMS сделает запрос при удалении мидлета.
MIDlet-Delete-Confirm Текст, запрос с которым будет выведен пользователю при удалении мидлета.
MIDlet-Certificate-<n>-<m> Указывается для подписанного мидлета и содержит сертификат в кодировке base64.
ПРИМЕЧАНИЕ

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

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

Пример дескриптора:

MIDlet-Jar-URL: test.jar
MIDlet-Jar-Size: 84835
MIDlet-Name: Test midlet
MIDlet-Vendor: Test Inc.
MIDlet-Version: 1.2.1
MIDlet-1: Test midlet, /icon.png, test.Test
MicroEdition-Configuration: CLDC-1.0
MicroEdition-Profile: MIDP-2.0
MIDlet-Icon: /icon.png
MIDlet-Delete-Confirm: Are you sure to delete this midlet?
MIDlet-Install-Notify: http://testinc.com/installNotify.php?midlet=Test
Published: 31.10.2007

«Hello, World!» и самые основы

Итак, можно приступить к написанию первого мидлета. Точкой входа в мидлет является класс, унаследованный от javax.microedition.midlet.MIDlet, как уже говорилось выше. Переопределять конструктор по умолчанию необязательно, но если требуется при старте мидлета однократно выполнить какие-либо действия, не связанные с загрузкой ресурсов, созданием потоков и использованием соединений, то их можно выполнить именно в конструкторе. После инициализации нового объекта AMS вызывает метод startApp(), сигнализируя о переходе мидлета в активное состояние, именно отсюда правильно начинать пользовательский жизненный цикл приложения (системным циклом жизни управляет AMS). В классе MIDlet также есть еще два абстрактных метода, pauseApp и destroyApp. AMS вызывает метод pauseApp(), например, при сворачивании приложения (на самом деле вызывается только некоторыми устройствами), указывая мидлету освободить все возможные ресурсы, закрыть потоки и соединения. destroyApp(boolean unconditional) сигнализирует о необходимости закрытия мидлета и также указывает освободить все ресурсы и закрыть потоки и соединения. Если в качестве параметра было передано false, и приложение не готово сейчас окончить работу, можно сгенерировать исключение MIDletStateChangeException и продолжить работу до следующего вызова этого метода.

Для работы с экраном необходимо с помощью вызова Display.getDisplay(MIDlet midlet) получить текущий объект класса Display, который создается и инициализируется системой, и задать через его метод setCurrent объект Displayable, который должен отобразиться на экране.

Создайте каталог для тестового мидлета (далее при указании относительного пути подразумевается, что этот каталог является текущим), а в нем подкаталоги src, classes и preverified. Пакет приложения будет называться test, а сам класс – Hello:

Hello.java

Файл Hello.java находится в каталоге src/test.

Компилируем код:

javac.exe -target 1.1 -source 1.3 -bootclasspath C:\SonyEricsson\JavaME_SDK_CLDC\PC_Emulation\WTK2\lib\cldcapi10.jar 
-cp C:\SonyEricsson\JavaME_SDK_CLDC\PC_Emulation\WTK2\lib\midpapi20.jar -d classes src/test/*.java

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

Следующим шагом является процесс предварительной проверки. Виртуальная машина J2SE проводит верификацию кода на лету. Этот процесс проверяет правильность формата class-файла, правильность объявлений классов, их членов и локальных переменных, корректность вызовов и т.д. Для увеличения производительности KVM этот процесс в значительной степени переносится в стадию сборки. Также при предварительной верификации в байт-код добавляются аннотации, позволяющие KVM провести более быстрый анализ кода, и они тоже позволяют повысить производительность виртуальной машины. Дополнительно проводится анализ кода на отсутствие блоков finalize, прямых вызовов методов ОС (native methods) и, для CLDC 1.0, на отсутствие данных и операций с плавающей точкой.

preverify.exe –cldc -classpath 
C:\SonyEricsson\JavaME_SDK_CLDC\PC_Emulation\WTK2\lib\midpapi20.jar;
C:\SonyEricsson\JavaME_SDK_CLDC\PC_Emulation\WTK2\lib\cldcapi10.jar 
-d preverified classes

Теперь можно собрать jar-файл, но перед этим необходимо написать manifest.mf:

MIDlet-Vendor: Test Inc.
MIDlet-Version: 1.0.0
MicroEdition-Configuration: CLDC-1.0
MIDlet-1: Hello, , test.Hello
MIDlet-Name: Hello
MicroEdition-Profile: MIDP-2.0

Итак, сборка:

jar.exe cvfm hello.jar manifest.mf -C preverified .

Последний шаг: написание дескриптора hello.jad:

MIDlet-Jar-URL: hello.jar
MIDlet-Jar-Size: 1382
MIDlet-Name: Hello
MIDlet-Vendor: Test Inc.
MIDlet-Version: 1.0.0
MIDlet-1: Hello, , test.Hello
MicroEdition-Configuration: CLDC-1.0
MicroEdition-Profile: MIDP-2.0
MIDlet-Delete-Confirm: Are you sure to delete this midlet?

Атрибут MIDlet-Jar-Size, возможно, придется подкорректировать.

Теперь можно запускать:

C:\SonyEricsson\JavaME_SDK_CLDC\PC_Emulation\WTK2\bin\emulator.exe -Xdevice:SonyEricsson_K750_Emu -Xdescriptor:hello.jad

Все современные эмуляторы поддерживают UEI (Unified Emulator Interface), который, в частности, регламентирует параметры командной строки для запуска. Но некоторые SDK расширяют набор опций, их можно посмотреть в документации, поставляемой с этим SDK.

Если все сделано правильно, то должен появиться этот экран:


Рисунок 2. Экран “Hello, world!”

Использование графики и анимации

В предыдущем разделе для вывода строки использовался стандартный экран, но любой программист, которому не безразличны внешний вид и удобство его программы, вряд ли будет использовать стандартные экраны и компоненты даже в бизнес-приложениях, не говоря уж об играх, которые занимают почти весь рынок. Так или иначе, в более-менее сложном мидлете придется использовать графику, и маловероятно, что ее дизайн совпадет со стандартным Look and Feel. Кроме того, при использовании стандартных экранов будут большие сложности с переносимостью, так как расположение компонентов, их поведение и даже наличие определенных команд придется адаптировать к гораздо большему количеству устройств. Использование встроенного пользовательского интерфейса может оправдать только наличие функциональности, которую в силу ограниченности платформы невозможно и или очень трудно реализовать самому, например, набор текста с T9 или специфичная для некоторых устройств функция выбора номера телефона из записной книжки, когда при этом не поддерживается библиотека для работы с контактами. При разработке игр вам придется использовать не только полностью свой GUI, но и свои стилизованные шрифты. Можете представить меню Quake с Arial TTF? Я тоже нет.

Для вывода графики используется класс Canvas из пакета javax.microedition.lcdui. (рекомендую внимательно прочитать всю документацию по нему, это избавит начинающих от стандартных ошибок), а точнее, метод paint(Graphics g). Он вызывается системой в следующих случаях:

Вывод графики

Нарисуем картинку, чтобы заодно рассмотреть загрузку ресурсов:


Картинка в проекте располагается в каталоге «res».

Hello.java
HelloCanvas.java

В данном примере создается два потока, и, хотя к ним даны комментарии, стоит остановиться на этом подробнее. Методы startApp, pauseApp, keyPressed и т.д. вызываются AMS, то есть в системной нити, которая обрабатывает очередь событий и вызывает методы согласно их месту в этой очереди. Если мы загрузим какой-либо callback-метод длительными операциями или работой с внешними ресурсами (загрузка файла, сетевое взаимодействие), то очередь не будет обрабатываться, пока мы не отпустим системную нить. Если же произойдет исключение, то мидлет с очень большой вероятностью аварийно завершит свою работу. Поэтому все методы, вызываемые AMS, должны отрабатывать мгновенно – оптимально просто взводить флажок, что был сделан такой-то вызов, а сами действия производить уже в своей нити (в данном случае операция создания и старта нити приемлема по времени исполнения). Единственным исключением является метод paint. Хотя спецификация виртуальной машины не оговаривает, должна ли для его выполнения создаваться отдельная нить, чтобы не мешать обработке очереди событий, обычно разработчики устройств отдельную нить для него выделяют. Но все равно не стоит загружать paint объемными вычислениями, оставьте ему только рисование примитивов.

Не забудьте модифицировать строку упаковки jar’а, чтобы добавить в архив картинку:

jar.exe cvfm hello.jar manifest.mf -C preverified . -C res .

Анимация и обработка клавиатуры

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

Hello.java
HelloCanvas.java

Как видно из листинга, для синхронизации логики с отрисовкой экрана используется стандартный механизм Java – synchronized, wait и notifyAll, причем для усыпления нити используется цикл while с переменной. Этому есть две причины:

  1. На одном объекте может спать много нитей, а метод notify будит только одну по своему усмотрению, то есть мы не можем гарантировать, что разбудят именно нашу.
  2. В одной из версий VM была допущена ошибка, из-за которой нить могла проснуться сама по себе. До некоторого времени эта причина оставалась теоретической, но, к сожалению, пару месяцев назад я с ней столкнулся на Motorola L6.

В MIDP 2.0 присутствуют свои методы синхронизации paint:

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

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

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

ПРИМЕЧАНИЕ

Двойная буферизация необходима для устранения мелькания экрана. Дело в том, что в объекте Graphics, полученном методом paint, рисуются примитивы, при этом программист не может знать, когда устройство решит вывести свой буфер на экран. Получается, что при отдельно взятой перерисовке на экране может отобразиться только часть примитивов, так как на момент принятия решения об обновлении физического экрана в буфер были занесены только они. Проще говоря, обновление физического экрана может произойти в середине метода paint. Данная ситуация решается путем введения еще одного буфера, в этом случае сначала все рисуется именно в него, а после полной отрисовки картинка буфера скидывается в системный буфер. Проверить, поддерживает ли устройство двойную буферизацию, можно через вызов Canvas.isDoubleBuffered(). Если не поддерживает, то эту схему надо реализовать самому через введение дополнительного Image, который и будет служить буфером.

В примечании упоминается объект Image, в который можно рисовать. Этот объект отличается от объекта Image, который использовался в примерах работы с графикой. В MIDP картинки бывают двух типов: изменяемые (mutable) и неизменяемые (immutable). В первые, соответственно, можно рисовать, но они не поддерживают прозрачность, вторые же не могут быть изменены, но могут содержать прозрачные и полупрозрачные (зависит от устройства) пиксели, и отрисовка их проходит быстрее.

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

И последнее в этом разделе – от чего должна зависеть скорость анимации. Те, кто помнит старые ДОСовские игры, однозначно скажут – не от производительности компьютера. За основу должно браться время. Скорость изменения картинки, движение объекта – все должно быть завязано на секундах, а не частоте конкретного процессора. И надо учитывать, что в реальной жизни мгновенного набора скорости не существует, поэтому движение, реализованное с ускорением (школьная формула x = x0 + V0*t + a*t2/2), будет смотреться приятнее, хотя наличие ускорения и не всегда можно определить на глаз.

Хранилище (RecordStore)

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

В стандартном MIDP есть только один механизм постоянного хранения данных – RecordStore (javax.microedition.rms). Пакет позволяет создавать отдельные хранилища с записями, данные которых не удаляются при закрытии мидлета. Основной проблемой является ограниченность их размера. Для настроек звука, номера пройденного уровня и таблицы рекордов, конечно, хватит, но хранение большого объема данных возможно не на всех устройствах. К счастью, таковых становится все меньше.

Ограничения:

Особенности разработки на J2ME

Многообразие виртуальных машин

Основная беда платформы J2ME – фрагментированность, то есть существование очень многих версий виртуальных машин. Практически каждый производитель успел придумать свою реализацию KVM, либо вообще использует AOT (Ahead Of Time) компиляцию (при установке мидлета его байт-код транслируется в родной для устройства бинарный код, который в итоге и будет исполняться). Кроме того, существуют отдельные фирмы и энтузиасты, которые тоже не прочь «оптимизировать» существующие проекты. При этом, к сожалению, Sun практически забросила сертификацию мобильных VM (другого объяснения примитивным багам я не вижу), решив отделаться reference design’ом. В настоящий момент исходный код их оригинальный виртуальной машины доступен как Open Source – phoneme.

Пару лет назад к различиям в реализации добавлялись и собственные пакеты от производителей, которые были призваны помочь разработчикам создавать более продвинутые мидлеты, но, к сожалению, они поддерживались только самими производителями устройств. Исключение составляло Nokia UI API, которое сертифицировал Sony Ericsson. На данный момент нужда в дополнительных нестандартизованных пакетах практически отпала, и они, если и поддерживаются, то в основном для совместимости.

ООП или не ООП?

Наверное, первое, что бросается в глаза в коде, – это использование всего двух классов, хотя картинка и методы обсчета ее координат вполне могут составить отдельную сущность. Причина проста: использование классов, полиморфизма, интерфейсов и прочих сущностей, облегчающих жизнь программисту, выливается в увеличение размера кода, используемой памяти и снижает производительность программы, что очень критично для мобильных устройств. Всего год с небольшим назад мне пришлось делать версию игры для Nokia Series 40 Developer Platform 1.0 (ограничение на размер jar’а – 655xx байт). При этом использовалось всего три класса, размер основного составлял около 12000 строк. Кажется немыслимым?Но добавление хотя бы еще одной сущности означало ухудшение качества графики, которая и так уже была близка к минимуму. Естественно, что в данном случае должен мучиться программист, а не пользователь, выложивший за игру кровные деньги.

В интернете не единожды возникали споры, в которых ортодоксы Java пытались доказать, что такой подход неправилен, раз в названии есть Java, то должно быть полное OOP и OOD, и предлагали различные выходы, начиная с отказа поддержки устройств, которые не потянули бы «правильно» спроектированное приложение, до бойкота вообще всей платформы J2ME. Мое же мнение таково: определитесь, что и для кого вы пишете. Если для самоудовлетворения или для знакомых, либо если заранее известно, что конечными устройствами будут топовые смартфоны и коммуникаторы, то не мучайте себя, в этих случаях использование объектного подхода полностью оправдано. Если же ваш продукт должен быть массовым, чтобы пользователи как можно большего количества устройств смогли им воспользоваться, не пожалев денег и трафика, то в первую очередь необходимо позаботиться о комфортной работе в приложении, и только потом о собственном удобстве при разработке. Абсолютное большинство успешных проектов подтверждает именно этот подход.

Помните, что любая парадигма или технология – всего лишь средство для достижения цели, но не самоцель.

Универсальный мидлет

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

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

Основные причины для портирования:

  1. Активно используется графика.
  2. Возможность работы (то есть, приложением можно будет пользоваться и без этого) с пока еще не очень распространенными библиотеками: JSR-075 (FileConnection и PIM API), JSR-082 (Bluetooth API), JSR-211(CHAPI) и т.д. К тому же производители и даже команды, работающие над разными моделями, могут по-своему понимать спецификации, или не понимать их вообще.
  3. Конечные устройства могут быть очень ограничены в ресурсах, что потребует изменения алгоритма или сокращения возможностей.
  4. Поддержка специфичных устройств (некоторые Sharp, Panasonic и т.д.). Некоторые, например, не смогут понять стандартный дескриптор, так как опять же разработчики не удосужились вникнуть в документацию.

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

Обфускация

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

Интерфейс пользователя и отличия от «настольного» приложения

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

Главные особенности:

Выбор тестовых устройств и список портирования

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

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

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

В любом случае устройства придется группировать. Для Sony Ericsson и Nokia делать это, можно сказать, одно удовольствие. Эти производители не стесняются делиться с разработчиками спецификациями телефонов и даже сами составляют группы совместимости. Так что в первую очередь уделяйте внимание именно этим производителям. Motorola тоже предоставляет информацию об устройствах, но с группами совместимости не все так гладко, как и у остальных производителей, вам придется самим их составлять на основе тем в форумах и открытых проектов. А вот у Samsung (в России, да и в мире он находится в тройке лидеров) почти каждая модель уникальна в самом худшем значении этого слова. У них тоже есть портал для разработчиков, где имеются спецификации для части устройств, и даже когда-то были документы по переносу приложений и спискам совместимости. Плохо одно – часто информация не соответствует действительности, или какая-нибудь модель имеет такой баг, что приходится делать отдельную версию, хотя вроде бы по остальным параметрам все сходится.

Другие производители мобильных устройств о J2ME-разработчиках не беспокоятся, насколько необходимы версии приложений под них, решайте сами (хотя за вас могут решить контент-провайдеры). При разработке игр я не обошел бы вниманием коммуникаторы с Windows Mobile, как они довольно распространены и обычно имеют предустановленную VM TAO Intent JMM, что означает некую совместимость и приличную возможность «отделаться» одной версией для одинаковых экранов.

Настройка IDE и сборка проекта

Уже довольно давно появились плагины для современных IDE, облегчающие жизнь разработчика J2ME. Но даже самые продвинутые из них не обеспечивают той гибкости, которую можно получить при помощи сборщика проектов. Я использую Ant и Antenna (набор задач для Ant’а и препроцессор) версии 0.9.13, и в этом разделе будет рассмотрена настройка двух наиболее популярных IDE при работе с Ant’ом. Для других сред разработки действия будут аналогичными.

Ant и Antenna

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

Простейший скрипт сборки может выглядеть так:

build.xml

Перед запуском замените метод startApp на следующий для демонстрации препроцессора:

          public
          void startApp()
  {
    //Вызываем инициализацию класса HelloCanvas, так как любому вызову //startApp предшествует либо первый запуск мидлета, либо вызов //pauseApp, в котором мы должны осводить все ресурсы и остановить//созданные нити
    canvas.init();
    //Установка текущего экрана (объекта Displayable), который должен //показываться
    display.setCurrent(canvas);
    //Для теста препроцессора//#if TEST//# System.out.println("Preprocess is working!");//#endif
  }

При запуске эмулятора вы увидите в консоли «Preprocess is working!»

IntelliJ IDEA

Версия 7.0.2

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

Выберите New Projeсt…, далее «Create project from scratch», на следующем экране укажите корневой каталог проекта. На последнем экране мастера проверьте, что напротив каталога src стоит галка, и нажмите «Finish».

Проект создан, теперь его надо настроить: File -> Settings. На закладке General укажите «No JDK». Так как библиотеки у вас свои (CLDC и MIDP), а собираться проект будет через Ant, то JDK действительно не нужен, и даже наоборот, он будет захламлять IDEA, которая будет пытаться сканировать его библиотеки на предмет нужных классов и JavaDoc.

Перейдите на закладку «Libraries», нажмите на плюсик и задайте имя библиотеки, например, «CLDC&MIDP», укажите созданный ранее модуль.

ПРИМЕЧАНИЕ

Модуль в IDEA – минимальная функциональная единица, которую можно компилировать, отлаживать, запускать и т.д. Проект может состоять из нескольких модулей.

Далее нажмите «Attach Classes…» и выберите нужные библиотеки из SE SDK: cldcapi10.jar и midpapi20.jar из каталога C:\SonyEricsson\JavaME_SDK_CLDC\PC_Emulation\WTK2\lib\. В некоторых SDK все пакеты находятся в одном архиве с названием midp.jar или аналогичным. Нажмите «Attach JavaDoc..» и добавьте нужную документацию – каталог C:\SonyEricsson\JavaME_SDK_CLDC\docs\j2me\midp_2_0 (документация по обеим библиотека объединена в одну, так удобнее пользоваться ей в браузере, не переключаясь между двумя закладками).

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

Следующий шаг – настройка отладчика. Run -> Edit Configurations, жмете на плюсик и выбираете конфигурацию «Remote». Укажите название и уберите все галки. В принципе, можно указать запуску эмулятора в Ant’е опцию spawn=”true” (выполнение задачи в отдельном потоке) и выбрать исполнение этой задачи перед стартом отладчика, но тогда потеряется консоль, а вывод информации через System.out бывает очень полезен, так что я этой опцией не пользуюсь.

Подключите build.xml, нажав на плюсик на панели Ant Build, расположенной справа. В любом месте панели щелкните правой кнопкой и перейдите в свойства скрипта. На закладке «Execution» необходимо указать JDK, так как по умолчанию он соответствует JDK проекта, который был специально отключен ранее. Также удобно будет проводить сборку в фоновом режиме, поэтому стоит выбрать «Make build in background».

Теперь запустите «runSE_debug», после появления окна эмулятора выберите Run->Debug. Для начала отладки запустите приложение в эмуляторе.

Eclipse

Версия 3.3.1.1

Создайте новый проект: File->New->Java Project. Введите имя проекта и укажите каталог src. В области Contents на следующем экране мастера в качестве папки с исходным кодом оставьте только src. На закладке Libraries удалите все библиотеки и добавьте cldcapi10.jar и midpapi20.jar из каталога C:\SonyEricsson\JavaME_SDK_CLDC\PC_Emulation\WTK2\lib\ (кнопка «Add External JARs…»)

Проект создан. В окне Window->Preferences->Java->Installed JREs убедитесь, что текущая JRE указывает на нужный JDK (именно JDK, а не JRE, иначе Ant не сможет собрать проект). В меню Project снимите галку «Build Automatically», так как компиляция на лету в данном случае бесполезна. В свойствах проекта (Project->Properties) выберите JavaDoc Location и укажите путь C:\SonyEricsson\JavaME_SDK_CLDC\docs\j2me\midp_2_0. Далее выберите Run->Open Debug Dialog. В появившемся окне создайте конфигурацию Remote Java Application, введите название и измените порт по умолчанию на 5005, указанный в build.xml.

Теперь нужно настроить выполнение скрипта Ant. Выберите Window->Show View->Ant и добавьте файл build.xml. Запустите «runSE_debug» и выберите Run->Open Debug Dialog… В этом окне нажмите кнопку «Debug». После первого запуска отладки можно будет просто выбирать пункт Run->Debug.

Полезные ссылки

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

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

Спецификации

Официальные порталы для разработчиков

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

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

Некоторые производители, например, LG, никогда не имели программ для разработчиков. Некоторые успели обанкротиться (Siemens). Но кое-что по их устройствам удалось найти:

Базы данных по устройствам

ПРИМЕЧАНИЕ

UAP (User Agent Profile) – профиль телефона с информацией, предназначенной в основном для разработчиков wap-сайтов. Некоторая информация полезна и для J2ME-разработчиков.

Форумы (кроме форумов на порталах производителей)

Книги

К сожалению, книги по платформе J2ME, вышедшие на русском языке, не заслужили признания разработчиков, так что данный раздел будет ограничен перечислением существующей литературы. При желании мнение программистов можно найти на форуме http://forum.juga.ru

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

Заключение

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


Эта статья опубликована в журнале RSDN Magazine #4-2007. Информацию о журнале можно найти здесь
    Сообщений 27    Оценка 290        Оценить