Технология J2ME
. Когда мобильные телефоны были просто средством коммуникации, то несовместимость не вызывала особых проблем. Но со временем они стали превращаться в сложные, функционально насыщенные устройства. Возникла потребность в разнообразном и качественном ПО. Хорошо сознавая, что сторонние разработчики не будут тратить время на написание программы для одной модели телефона, производители стали создавать платформы, позволяющие запускать одно и то же приложение на различных устройствах. В настоящее время в мире существует несколько таких решений, но в России действительно широко распространена только платформа Java 2 Micro Edition (J2ME).
Основная идея J2ME проста: разработчик компилирует программу в промежуточный код, выполняющийся специальным эмулятором — виртуальной Java-машиной (VJM), которая выступает посредником между программой и прошивкой телефона. Таким образом, основную часть работы по адаптации приложений к конкретной модели берет на себя производитель. Кроме того, VJM решает проблему безопасности, поскольку приложения не имеют прямого доступа к памяти и ресурсам телефона.
На практике VJM разных производителей не всегда выдают одинаковый результат. Из других недостатков J2ME следует отметить ее неспособность работать с вещественными числами (не актуально для CDLC 1.1), а также проблемы, возникающие с исходными текстами. Поскольку в результате компиляции получается промежуточный код, а не машинный, то он легко может быть переведен обратно в Java-исходник.

Рабочая среда
Чтобы начать разрабатывать мидлеты (так принято называть J2ME-приложения для мобильных телефонов), необходимы:

  • Java 2 Standard Edition (J2SE) SDK версии 1.4.2 или выше — компилятор и утилиты для создания Java-архивов;
  • J2ME Wireless Toolkit (WTK) 2.2 — набор утилит и эмуляторов для создания и отладки мидлетов;
  • текстовый редактор или IDE.

Все эти компоненты абсолютно бесплатны и доступны по адресам: http://java.sun.com/products, www.netbeans.org и http://www.mobilab.ru/soft.

Создание нового проекта
Запустите WTK KToolbar. Для создания нового проекта выберите пункт меню File • New Project. В появившемся окне заполните поля Project Name (название проекта) и MIDlet Class Name (имя основного класса создаваемого мидлета). Введите, например, Sample и SampleMIDlet. После заполнения этих полей будет предложено настроить параметры проекта. Ничего не изменяйте, просто нажмите кнопку OK. В папке [WTK]apps будет создан новый каталог SampleSuite. Каждый проект содержит стандартный набор каталогов:

  • bin — сюда попадают .jar- и .jad-файлы после упаковки проекта;
  • lib — здесь размещаются файлы подключаемых библиотек;
  • res — файлы ресурсов (например, .png, .txt);
  • src — .java-файлы с исходным текстом программы.

Название .java-файла должно соответствовать имени расположенного внутри него класса. Не забудьте, что язык Java чувствителен к регистру букв. Файл SampleMIDlet.java находится на «Мир ПК-диске».

Открытие и запуск примера
    После написания кода программы, как правило, наступает этап тестирования. Сначала приложение отлаживается на эмуляторе, а затем непосредственно на телефоне. Давайте откроем один из стандартных примеров, UIDemo. Чтобы запустить проект в окне эмулятора, его нужно откомпилировать (кнопка Build), а затем нажать кнопку Run на панели инструментов.
    Для переноса приложения на телефон его необходимо упаковать. Чтобы сделать это, нужно после компиляции выбрать пункт меню Project • Package. В папке bin проекта появятся файлы .jar и .jad. Файл .jar является Java-архивом, содержащим файлы проекта, а .jad — так называемым дескриптором приложения, который включает вспомогательную информацию о классах архива, используемую телефоном для эффективного управления ресурсами. Именно эти два файла нужно копировать в телефон.

Простейшее приложение
Особенность программной модели мидлета — возможность вмешательства операционной системы в ход выполнения программы. Работа приложения может быть в любой момент прервана каким-нибудь внешним событием, например входящим телефонным звонком. Поэтому требуется, чтобы система постоянно контролировала ход выполнения программы, что реализуется с помощью специального управляющего модуля, называемого AMS (Application-Management Software).
Каждый Java-разработчик знает, что работа программы начинается с запуска метода main(). В J2ME этот метод недоступен, вместо него предлагается использовать startApp().
Запущенное приложение может находиться в трех возможных состояниях: paused — мидлет запущен, но не активен; active — мидлет активен и destroyed — мидлет остановлен и готов к завершению.
Рис. 1. Состояния мидлетаРассмотрим жизненный цикл мидлета (рис. 1). Сразу после запуска он находится в состоянии paused. Прежде чем перейти в активный режим, он должен выполнить инициализацию. Мидлет не имеет специального инициализационного метода, и все действия выполняются внутри startApp(). Через некоторое время после запуска программы AMS вызывает метод startApp(), и в результате приложение переходит в состояние active. Если при запуске возникли какие-либо ошибки, управление передается методу javax.microedition.midlet.MIDletStateChangeException, который переводит мидлет в состояние destroyed.
В любое время AMS может прервать выполнение мидлета и перевести его в состояние paused. При этом AMS вызывает метод pauseApp(). Чтобы перевести приложение в состояние destroyed и закрыть его, AMS вызывает метод destroyApp().
Мидлет не способен перевести сам себя из одного состояния в другое. Он может только послать соответствующий запрос AMS, воспользовавшись одним из методов:

  • notifyDestroyed() — запрос на завершение работы;
  • notifyPaused() — запрос на дезактивацию и переход в состояние паузы;
  • resumeRequest() — запрос на реактивацию и переход в активное состояние.

AMS решает, нужно ли удовлетворить запрос, и если нужно, то устанавливает, когда это следует сделать. После запроса resumeRequest() AMS вызывает метод startApp(). В ответ на notifyPaused() и notifyDestroyed() AMS напрямую переводит мидлет в требуемое состояние.
Ниже приведен текст простого мидлета-каркаса. Рекомендую сохранить его где-нибудь на диске и использовать в качестве шаблона для новых приложений.

Листинг 1.  Код мидлета-каркаса
//файл SampleMIDlet.java
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;

public class SampleMIDlet extends MIDlet {
  private Display display;
  public SampleMIDlet(){}

  protected void destroyApp( boolean unconditional ) throws MIDletStateChangeException
   {
    exitApp(); // вызывает уборщик мусора
   }

  protected void pauseApp()
   {
     // Сюда следует добавить код, который выполняется непосредственно
     // перед переводом приложения в режим паузы.
   }

  protected void startApp() throws MIDletStateChangeException
   {
     if( display == null )
       {
         // Этот код выполняется при запуске мидлета
         initApp();
       }
      // Сюда следует добавить код, который выполняется
      // непосредственно перед переводом приложения в
      // активный режим.
   }
  private void initApp()
   {
     display = Display.getDisplay( this );
     // Сюда добавляется код инициализации приложения.
   }
  public void exitApp()
   {
     // Сюда следует добавить код, который будет выполняться
  // при закрытии приложения.
     notifyDestroyed(); // Уничтожение мидлета
   }
}

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

Создание окон
Высокоуровневый интерфейс определяет четыре типа окон: Alert, TextBox, List и Form (рис. 2).
Рис. 2. Классы пакета javax.microedition.lcdui

Alert — окно служебных сообщений. При создании требует четыре параметра: заголовок окна, текстовое сообщение, его рисунок и тип (ALARM, CONFIRMATION, ERROR, INFO, WARNING). С помощью метода setTimeout можно установить время (в миллисекундах), через которое окно будет закрыто.

Alert myAlert=new Alert(“Заголовок сообщения“,“сообщение“,image,AlerType.ALARM);
myAlert.setTimeout(1000);

TextBox — окно-редактор текста. При создании требует, чтобы ему передали заголовок, начальный текст, максимальный размер текста и ограничения. В качестве ограничений можно использовать константы:
ANY — ограничений на текст нет;
EMAILADDR — ввод e-mail-адресов;
NUMERIC — ввод чисел;
PASSWORD — ввод пароля;
PHONENUMBER — ввод телефонных номеров;
URL — ввод URL-адресов.
Для получения введенного текста можно воспользоваться либо методом getChars(char[] chAr), записывающим текст в переданный массив символов chAr, либо getString(), возвращающим строку. С помощью метода size() можно узнать размер введенного текста.

TextBox myTb=new TextBox(“Введите текст“, ““, 255,TextBox.ANY);
...
String strRes=myTb.getString();

List — окно-список элементов. При создании список требует четыре параметра: заголовок списка, тип списка, массив строк и массив картинок для элементов списка. Два последних параметра можно опустить, но тогда для формирования списка нужно использовать метод append(String str, Image img).
В J2ME реализованы три типа списков: тип EXCLUSIVE предназначен для пометки одного элемента из списка (соответствует TRadioButton в Delphi), MULTIPLE предназначен для пометки нескольких элементов (соответствует TCheckBox), IMPLICIT позволяет выбрать один элемент из списка (cоответствует TListBox).
Чтобы узнать, какие элементы выбрал или пометил пользователь, можно воспользоваться одним из следующих методов: getSelectedIndex() — возвращает номер выбранного элемента; getSelectedFlags(boolean[] selArr) — записывает в переданный массив selArr состояние всех элементов списка.

String st={“Элемент 1“, “Элемент 2“, “Элемент 3“};
List myList=new List(“Элементы“,Choice.IMPLICIT,st,null);
...
int index=myList.getSelectedIndex();

Form — форма, или контейнер, куда можно помещать различные визуальные элементы управления. Если элементы формы не умещаются на экране телефона, создается вертикальная полоса прокрутки. Для добавления элемента на форму используется метод append(). В качестве единственного параметра ему нужно передать либо строку, либо картинку, либо один из визуальных компонентов класса Item.
ChoiceGroup — группа элементов для выбора. Этот элемент аналогичен List. Как и List, он позволяет создавать EXCLUSIVE- и MULTIPLE-списки. Вместо типа IMPLICIT нужно использовать POPUP, который создает список в виде выпадающего меню.
DateField — элемент для установки даты и времени. При его создании требуется передать три параметра: заголовок, режим даты (DATE, TIME или DATE_TIME) и часовой пояс, впрочем, последний можно опустить.
TextField — поле для ввода текста; очень похоже на TextBox, но в отличие от последнего не занимает весь экран.
StringItem — элемент для добавления на форму статических строк текста, кнопок и гиперссылок. При его создании нужно передать три параметра: строку-метку, строку текста и форматирование текста. В качестве последнего используются константы: BUTTON — для создания кнопки, HYPERLINK — для гиперссылки, LAYOUT_BOTTOM, LAYOUT_CENTER, LAYOUT_TOP, LAYOUT_LEFT, LAYOUT_RIGHT — для определения положения текста на экране.
Spacer — элемент для создания свободного пространства указанного размера. Применяется для позиционирования других элементов на форме. При его создании задаются ширина и высота области.
ImageItem — элемент для размещения изображения на форме. При создании требуется передать пять параметров: текст над изображением, объект класса Image с загруженным рисунком, расположение рисунка (LAYOUT_LEFT, LAYOUT_RIGHT, LAYOUT_CENTER), текст под рисунком и форматирование. Последний из них аналогичен рассмотренному в StringItem и также может быть опущен.
J2ME гарантированно умеет работать с рисунками в формате PNG. Все рисунки проекта должны быть расположены в папке res. Код загрузки и использования рисунка нужно обязательно помещать в скобки try{...}catch{...}, в противном случае при сборке проекта возникнут ошибки. Для загрузки изображения из файла используется метод Image.createImage(“/файл.png“).
Gauge — элемент для отображения полос состояния. При его создании требуется передать три параметра: метку — флаг типа boolean, указывающий на то, должна ли полоса быть интерактивной; значение, соответствующее заполненной полосе, и начальное значение. Для установки текущего положения применяется метод setValue(int value).
Помимо указанных элементов под заголовком окон TextBox, List и Form можно поместить бегущую строку с текстом. Для этого нужно создать объект класса Tricker и вызвать метод setTricker для окна, указав в качестве параметра созданный объект.

Листинг 2. Пример создания элементов окна приложения
Form myForm=new Form(“Пример формы“);
ChoiceGroup myCG=new ChoiceGroup(«Элементы»,Choice.POPUP);
DateField myDF=new DateField(“Дата и время“,DateField.DATE_TIME);
TextField myTF=new TextField(“Введите текст“, ““, 20,TextField.ANY);
StringItem mySI1=new StringItem(“Ф.И.О.“, “Иванов Иван Иванович“);
StringItem mySI2=new StringItem(“URL“,
“www.MobiLab.ru“,Item.HYPERLINK);
StringItem mySI3=new StringItem(“OK“, ““, Item.BUTTON);
Spacer mySp=new Spacer(50,0);
Gauge myG=new Gauge(“Идет процесс“,true,100,45);
try{
Image img=Image.createImage(“/mobilab.png“);
ImageItem myII=new
ImageItem(“Логотип“,img,ImageItem.LAYOUT_CENTER,“www.mobilab.ru“);
myForm.append(myII);
}catch(java.io.IOException ex){}
myForm.append(myCG);
myForm.append(myDF);
myForm.append(mySp);
myForm.append(myTF);
myForm.append(mySp);
myForm.append(mySI1);
myForm.append(mySI2);
myForm.append(mySI3);
myForm.append(myG);
Tricker myTR=new Tricker(“Это бегущая строка...“);
myForm.setTricker(myTR);

Создание и настройка команд
Для перехода между окнами используется метод Display.setCurrent(Displayable d). Обычно переход выполняется после какого-то действия пользователя. Для настройки реакции на такое действие необходимо:
1) создать команду;
2) связать ее с экраном или элементом управления;
3) связать с экраном или элементом класс, реализующий обработчик событий;
4) внутри метода commandAction запрограммировать реакцию на данную команду.
Для реализации команды необходимо организовать объект класса Command:

private Command MyCommand=new Command(“Моя команда“,Command.SCREEN,2);

Первый его параметр определяет строковое представление команды, второй — ее тип, третий — приоритет. Для типа выбирается одна из следующих констант: BACK, CANCEL, EXIT, HELP, ITEM, OK, SCREEN, STOP. Тип команды лишь указывает на ее предполагаемое назначение, но не влияет на функциональность.
Связь команды с экраном обеспечивается методом Displayable.addCommand(Command cmd), а ее связь с элементом управления — Item.setDefaultCommand (Command cmd).
Если с экраном связаны две команды, то для них создаются соответствующие подэкранные кнопки. Если команд больше двух, то для одной из них создается кнопка, а остальные помещаются в меню, вызываемое нажатием второй кнопки. Система решает, какую из команд поместить на экран, ориентируясь на их тип. Например, если среди них есть команда типа EXIT, то именно с ней и будет связана подэкранная кнопка.
Класс способен обрабатывать команды только при условии, что он реализует интерфейсы CommandListener и ItemCommandListener:

public class Navigator extends MIDlet implements CommandListener, ItemCommandListener{...}

Для связи обработчика с экраном используется метод Displayable.setCommandListener(CommandListener cl); для связи с элементом формы — Item.setItemCommandListener(ItemCommandListener cl). В качестве параметра нужно указать объект, реализующий обработчик событий. Если обработка команд выполняется внутри того класса, откуда вызывается метод, то в качестве параметра можно использовать слово this.
При выполнении команды система вызывает метод commandAction(Command c, Displayable d) для окна и commandAction(Command c, Item i) для элементов формы. Вызвавшая метод команда передается в параметр c. Внутри этого метода необходимо идентифицировать команду и написать код ее обработки.

Листинг 3. Обработка команд от окна
public void commandAction(Command c, Displayable d){
 if (c==command1) {
 //Сюда помещаем код, выполняющийся при
 //возникновении команды command1
 }
 if (c==command2) {... }
...}

//Обработка команд от формы
public void commandAction(Command c, Item i){
 if (c==comForm1) {
 //Сюда помещаем код, выполняющийся при
 //возникновении команды comForm1
 }
...}

Теперь есть вся информация, необходимая для написания полноценного мобильного приложения. Для примера разработаем небольшую программу-тестирование. Она будет иметь окна четырех типов: заглавное, с логотипом программы; для ввода информации о пользователе; с вопросами; с результатами тестирования. Исходный текст с комментариями и откомпилированное приложение приведены на «Мир ПК-диске».

Рекомендуемая литература и ресурсы

  1. Картузов А.В., Николенко Д.В. Программируем на языке Java: краткий курс. СПб.: Наука и техника, 2001. 192 с.
  2. Горнаков С.Г. Программирование мобильных телефонов на Java 2 Micro Edition. М.: ДМК-Пресс, 2004. 336 с.
  3. Моррисон М. Создание игр для мобильных телефонов: Пер. с англ. М.: ДМК-пресс, 2006. 496 с.
  4. http://www.mobilab.ru — сайт о мобильном программировании + тематический форум.
  5. http://www.juga.ru — портал Java-разработчиков.

Об авторе
Александр Сергеевич Ледков — аспирант Самарского государственного аэрокосмического университета. Сайт: http://www.mobilab.ru/, e-mail: rusice@yandex.ru.