Факультет «Информатика и системы управления»
Методические указания к лабораторной работе
по курсу«Распределенные системы обработки информации»
Разработка приложений для мобильного устройства.
Москва, 2009
ОглавлениеЦель работыЗадания ксамостоятельной работеЗадания клабораторной работеСоздание отчетаКонтрольныевопросы1. Структура MIDPприложения2. Основысоздания мидлетовПриложение1.Примеры создания MIDP приложенийЛитература
Цель работы
Получить знания обосновах создания MIDP приложенийдля мобильных устройств на языке Java.Познакомиться с библиотеками javax.microedition. Применить полученные знаниядля создания MIDP приложений./>/>/>/> Задания ксамостоятельной работе
Ознакомиться стеоретическим материалом, представленным в приложениях к данным методическимуказаниям и примерами программ. Ознакомиться с текстом задания к лабораторнойработе. /> Задания клабораторной работе
Задание 1. Создать игру, реализованную как MIDP приложение.
1. Создать менюигры, содержащее пункты:
1.1. запуск игры
1.2. уровень сложностии другие настройки.
2. В игре должныподсчитываться набранные очки и лучший результат сохраняется в течение игры.
Игра выбирается самимстудентом.
Задание 2.
Создать сервер обменатекстовыми сообщениями.
1. Создать сервер,которые будет получать данные от клиента в виде строк и выводить на экранмидлета.
2. Сервер долженбыть многопоточным, т.е. обслуживать одновременно с несколько клиентов.
3. Для завершенияработы клиент должен послать строку «exit».
Создать клиента длясервера обмена сообщениями.
1. Клиентскоеприложение должно иметь поле для ввода данных для отправки.
По выбору студентаприложение может выполнять функции и сервера, и клиента одновременно, либомогут быть реализованы, как два отдельных приложения. />Создание отчета
Отчет должен содержать:
1. Постановкузадачи, решаемой отлаженной программой.
2. Руководствопользователя отлаженной программы, содержащее описание интерфейсов всехфункций программы.
3. Листинг программыс необходимыми комментариями./>/>/>/>/>/>/>/> Контрольныевопросы.
1. Что такое MIDP?
2. Структура MIDP?
3. Для чего нуженкласс MIDlet и его методы?
4. Каким образомосуществляется взаимодействие MIDPприложения с пользователем?
5. Для чего нуженкласс Canvas и его методы?
/>1. Структура MIDP приложения
MIDP приложение имеетстрогую структуру, которая описана ниже. Приложение, которое может бытьзапущено на телефоне, называется мидлетом (midlet). Мидлеты обязательнозапаковываются в JAR архив, причем в одном JAR – архиве могут находится сразунесколько мидлетов. Архив с мидлетам(и) называется MidletSuite (набор мидлетов)
Набор мидлетов состоит изJAR – архива, содержащего мидлет(ы), вспомогательные классы и ресурсы (напримерфайлы с картинками и т.п.); JAR – манифеста (JAR Manifest), которыйпредставляет собой файл, находящийся в JAR – архиве (файл manifest.mf в папкеMETA-INF в корне архива); дескриптора приложения (Application Descriptor) – этофайл с тем же именем, что и JAR – архив и расширением JAD.
Манифест и дескрипторсодержат атрибуты приложения в формате имя_атрибута: значение_атрибута.
Некоторые из атрибутовдолжны присутствовать обязательно и при этом совпадать в дескрипторе и манифесте.Если это условие не будет выполнено, то приложение не запустится и даже неустановится на приборе, для которого оно предназначено.
Заполнение обязательныхатрибутов берет на себя программное средство, предназначенное для разработкиMIDP приложений (KToolbar из J2MEWTK и Forte for Java CE). Кроме того, этосредство предоставляет возможность добавления и редактирования атрибутов.
Мидлет может получитьзначение любого атрибута с помощью метода мидлета:
getAppProperty(Stringkey)
Ниже приведены некоторыеатрибуты, которые могут быть полезны разработчику.
· MIDlet-:,,-описание n-ого мидлета в наборе. Здесь name – имя мидлета, icon – «иконка»(файл в формате PNG), class – файл класса, расширяющего (extends) класс MIDlet(фактически тот класс, который будет «исполняться»). При открытии на приборенабора мидлетов, на экране высвечивается список мидлетов в нем, в которомпредставлены имена мидлетов с соответствующими иконками.
Пример: MIDlet-1:worm,/liqWorm/worm.png,liqWorm.worm
· MIDlet-Version:-версия наборамидлетов в формате xx.yy.zz.
Пример: MIDlet-Version: 0.1.0
· MIDlet-Info-URL:-URL, по которому можно найти информацию о наборе мидлетов.
Пример: MIDlet-Info-URL:xdimas@yahoo.com
· MIDlet-Description:-описание наборамидлетов.
Пример: MIDlet-Description: Myfirst MIDlet!
· MIDlet-Vendor:-информация о разработчике мидлета.
Пример: MIDlet-Vendor: xDimas
Необходимо отметить, чтоспособы установки набора мидлетов на прибор, для которого тот предназначен, неоговаривается в рамках стандарта J2ME.
Для создания итестирования мидлетов необходимо сказать последнюю версию J2ME_wireless_toolkitс сайта разработчика: http://java.sun.com/j2me/index.jsp/>2. Основы создания мидлетов
Необходимо отметить, чтоMIDP является не просто урезанным вариантом J2SE (Java2 Standard Edition).Здесь появляются свои особенности, продиктованные особенностями устройств, длякоторых мидлеты предназначены.
Класс, который будетявляться мидлетом, должен расширять (extends) класс MIDlet (аналогично классуApplet при разработке аплетов). Этот класс должен иметь конструктор безпараметров. Класс MIDlet имеет методы, предназначенные для управления жизненнымциклом мидлета. Так для того, чтобы сообщить виртуальной машине (ВМ) о том, чтомидлет завершается (фактически завершает выполнение мидлета) используетсяметод: notifyDestroyed(), а чтобы сообщить мидлету о том, что он будетзавершен, ВМ вызывает метод: destroyApp(bolean uconditional).
Мидлет, в отличие отаплета, может находится в состоянии паузы (paused — например, когда дисплейзанят каким-нибудь сообщением и т.п.). Чтобы сообщить мидлету о том, что онпереходит в состояние паузы, ВМ вызывает метод мидлета: pauseApp(), а чтобывойти в состояние паузы, мидлет использует метод: notifyPaused().
Когда мидлет входит вактивное состояние (выход из паузы и начало работы мидлета), вызывается егометод: startApp().
Важно помнить, что этотметод может вызываться несколько раз за время выполнения мидлета.
Класс, расширяющий MIDletможет объявлять (implements) различные интерфейсы, например интерфейс Runnable.
Для взаимодействия спользователем в MIDP присутствуют классы Display и Displayable (точнее егонаследники).
Объект класса Displayсоздается ВМ и за все время работы мидлета для него присутствует только одинобъект этого класса. Получить его можно при помощи статического метода:
static DisplayDispaly.getDisplay(MIDlet m)
Объект класса Displayоперирует с объектами класса Displayable. Объекты класса Displayableпредназначены непосредственно для взаимодействия с пользователем (т.е. длявывода на экран, обработки нажатий клавиш и т.п.). Для работы с этими объектамив классе Displayесть два метода:
voidsetCurrent(Displayable d)
DisplayablegetCurrent()
Сам класс Displayableобъявлен как абстрактный, так что работать можно только с его потомками. Их два– это классы Canvas и Screen.
Потомки класса Screenопределяют набор визуальных компонент для высокоуровнего взаимодействия спользователем (формы, поля ввода, списки и т.п.). Надо отметить, что этот наборвесьма примитивен и предоставляет минимум (однако достаточный) возможностей длявзаимодействия с пользователем. Использовать этот набор в приложениях,требующих интерактивности (например в играх), не представляется возможным, нонекоторые компоненты все же удобно использовать во вспомогательных целях(например поле ввода для ввода имени игрока в таблицу рекордов, меню и т.п.).Подробно рассматривать этот набор здесь не будем.
Класс Canvas предназначендля низкоуровнего взаимодействия с пользователем. В нем определен набор методовдля обеспечения перерисовки содержимого экрана, получения информации о нажатиикнопок и т.п. Остановимся подробнее на некоторых особенностях использованиякласса Canvas.
Необходимо помнить о том,что на разных приборах, на которых может быть запущен мидлет, могут бытьразличные размеры дисплея. Для их получения используются методы:
intgetHeight()
int getWidth()
Перерисовка содержимогоэкрана осуществляется ВМ самостоятельно и когда она будет выполнена, точносказать нельзя. Можно лишь сообщить ВМ о том, что необходимо обновитьсодержимое экрана вызовом метода:
void repaint()
или
voidrepaint(int x, int y, int width, int height)
Можно принудить ВМ кнемедленному выполнению перерисовки вызовом метода: void serviceRepaints()
Когда ВМ осуществляетперерисовку содержимого экрана, вызывается метод: void paint(Graphics g)
Объект g связан сизображением, которое будет выведено на экран. Использование объектов классаGraphics происходит так же, как и при работе с аплетами.
Необходимо заметить, чтометод paint объявлен как абстрактный и поэтому для работы с объектом классаCanvas разработчик должен создать класс, расширяющий Canvas и определяющийметод paint.
Получить объект классаGraphics, который отвечает за перерисовку экрана, как это можно сделать дляаплета методом: Graphics Applet.getGraphics(), для потомков класса MIDletпрямым способом невозможно. Можно попытаться сохранить объект, поступающий вкачестве параметра в метод paint, но делать это не рекомендуется.
MIDP предоставляетэлегантный метод для синхронизации перерисовки экрана с ходом выполненияосновной программы. В классе Display присутствует метод:
callSerially(Runnable r)
Вызов этого методазаставляет ВМ вызывать метод run() объекта r сразу после окончания перерисовкиэкрана. Вызов осуществляется только один раз.
Объекты класса Canvasмогут реагировать на нажатие кнопок при помощи методов:
keyPressed(intkey)
keyReleased(intkey)
keyRepeated(int key)
Использовать коды кнопокнапрямую не рекомендуется. Для получения действия, связанного с тем или инымкодом кнопки, используется метод:
int getGameAction(intkey)
Он возвращает коддействия (коды определены как константы в классе Canvas на пример FIRE). Есть иобратный ему метод:
intgetKeyCode(int gameAction)
Для каждого объектакласса Displayable может быть задан набор команд, определенных пользователем.Каждая команда является объектом класса Command и создается при помощиконструктора:
Command(Stringcommand, int type, int priority)
Для добавления и удалениякоманд в классе Displayable предусмотрены методы:
voidaddCommand(Command c)
voidremoveCommand(Command c)
Команды, в зависимости отих типа, могут закрепляться за кнопками под экраном телефона или заноситься вэкранное меню (это делается автоматически). При этом над соответствующейкнопкой отображается имя команды.
Для того, чтобы мидлетмог обрабатывать команды, он должен объявлять (implements) интерфейсCommandListener. У этого интерфейса есть единственный метод: voidcommandAction(Command c, Displayable d), который вызывается после того, какпользователь выберет команду c.
Для того, чтобы объявитьв объекте класса Displayable обработчик команд listener, используется методэтого класса:
voidaddListener(CommandListener listener)
/>Приложение 1.Примеры создания MIDP приложений
Давайте создадим простейшее MIDP приложение-заготовку для нашей игры,на основе игры «червяк»./>/>/>
package example.wormgame;
import java.lang.Thread;
// подключаем требуемые намкомпоненты
importjavax.microedition.midlet.MIDlet;
importjavax.microedition.midlet.MIDletStateChangeException;
importjavax.microedition.lcdui.Form;
importjavax.microedition.lcdui.Item;
importjavax.microedition.lcdui.Gauge;
importjavax.microedition.lcdui.Display;
importjavax.microedition.lcdui.Displayable;
importjavax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
/**
* Основной класс нашегомидлета
*/
public class WormMainextends MIDlet implements CommandListener {
/** Класс описывающий«червяка» */
private WormPittheGame;
/** Кнопка выхода изигры. */
private CommandexitCmd = new Command(«Exit», Command.EXIT, 3);
/** Элемент меню, поменять уровеньсложности. */
private CommandlevelCmd = new Command(«Change Level», Command.SCREEN, 2);
/** Элемент меню, начать новую игру. */
private CommandstartCmd = new Command(«Start», Command.SCREEN, 1);
/** Элемент меню,перезапустить игру. */
private CommandrestartCmd = new Command(«Restart», Command.SCREEN, 1);
/** Элемент меню, вернутся в игру безизвенений. */
private CommandcancelCmd = new Command(«Cancel», Command.ITEM, 1);
/** Элемент меню, для подтвержждениявыбранных установок. */
private Command OKCmd= new Command(«OK», Command.OK, 1);
/**
* Конструктор по умолчанию, вкотором создаются графические вомпоненты и
* устанавливается command listener.
*/
public WormMain() {
theGame = newWormPit();
theGame.addCommand(exitCmd);
theGame.addCommand(levelCmd);
theGame.addCommand(startCmd);
theGame.addCommand(audioOnCmd);
theGame.setCommandListener(this);
}
/**
* Деструктор для очистки памятизанятой приложением.
*/
protected voiddestroyApp(boolean unconditional) {
theGame.destroyGame();
Display.getDisplay(this).setCurrent((Displayable)null);
}
/**
* Приостановка работы приложения
*/
protected void pauseApp() {
}
/**
* Запуск приложения
*/
protected voidstartApp() {
Display.getDisplay(this).setCurrent(theGame);
try {
// Запуск игры в отдельномпотоке
Thread myThread = newThread(theGame); // создаём новый поток
myThread.start();// запуск потока
} catch (Error e) {
destroyApp(false);
notifyDestroyed();
}
}
/**
* Выполнения функций приложенияв ответ на действия пользователя.
*/
public voidcommandAction(Command c, Displayable d) {
if (c ==restartCmd) {
theGame.restart();
} else if (c ==levelCmd) {
Item[] levelItem ={
newGauge(«Level», true, 9, theGame.getLevel())};
Form f = newForm(«Change Level», levelItem);
f.addCommand(OKCmd);
f.addCommand(cancelCmd);
f.setCommandListener(this);
Display.getDisplay(this).setCurrent(f);
} else if (c ==exitCmd) {
destroyApp(false);
notifyDestroyed();
} else if (c ==startCmd) {
theGame.removeCommand(startCmd);
theGame.addCommand(restartCmd);
theGame.restart();
} else if (c ==OKCmd) {
Form f = (Form)d;
Gauge g =(Gauge)f.get(0);
theGame.setLevel(g.getValue());
Display.getDisplay(this).setCurrent(theGame);
} else if (c ==cancelCmd) {
Display.getDisplay(this).setCurrent(theGame);
}
}
Теперь необходимо создать меню ипрочие графические компоненты на экране мобильного устройства.
public class WormPitextends Canvas implements Runnable {
/** Очки в игре. */
private int score = 0;
/** Уровень сложности. */
private int level = 5;
/** Ширина экрана в пикселях. */
static int CellWidth;
/** Длина экрана в пикселях. */
static int CellHeight;
/** Высота шрифта для вывода наэкран счёта. */
private static finalint SCORE_CHAR_HEIGHT;
/** Ширина шрифта для вывода на экрансчёта. */
private static finalint SCORE_CHAR_WIDTH;
/** Время по умолчанию междуперерисовкой червя (400 milliseconds) */
private static final int DEFAULT_WAIT= 400;
/** Цвет шрифта.(0xff0000) */
static final intTEXT_COLOUR = 0x00ff0000;
/** Размер клеткичервя. */
public static finalint CELL_SIZE = 5;
// Установка размерашрифта
static {
Font defaultFont =Font.getDefaultFont(); // взять шрифт по умолчанию
SCORE_CHAR_WIDTH =defaultFont.charWidth('S');
SCORE_CHAR_HEIGHT =defaultFont.getHeight();
SCORE_HEIGHT =SCORE_CHAR_HEIGHT * 2;
}
/**
* Конструктор. Задания ширины ивысоты червя.
*/
public WormPit() {
width =round(getWidth());
height =round(getHeight()-SCORE_HEIGHT);
WormPit.CellWidth =(width-(START_POS*2)) / WormPit.CELL_SIZE;
WormPit.CellHeight =(height-(START_POS*2)) / WormPit.CELL_SIZE;
myWorm = new Worm(this);
/**
* Обработчик событий от нажатияклавишь на мобильном устройстве.
* Стрелки(джойстик) на мобильномустройстве (UP, DOWN, LEFT, RIGHT)
*/
public voidkeyPressed(int keyCode) {
switch(getGameAction(keyCode)) {
case Canvas.UP:
myWorm.setDirection(Worm.UP);
break;
case Canvas.DOWN:
myWorm.setDirection(Worm.DOWN);
break;
case Canvas.LEFT:
myWorm.setDirection(Worm.LEFT);
break;
case Canvas.RIGHT:
myWorm.setDirection(Worm.RIGHT);
break;
case 0:
// можно использовать клавишис номерами 2,4,6,8
switch (keyCode) {
caseCanvas.KEY_NUM2:
myWorm.setDirection(Worm.UP);
break;
case Canvas.KEY_NUM8:
myWorm.setDirection(Worm.DOWN);
break;
caseCanvas.KEY_NUM4:
myWorm.setDirection(Worm.LEFT);
break;
caseCanvas.KEY_NUM6:
myWorm.setDirection(Worm.RIGHT);
break;
}
break;
}
}
/**
* Перерисовка экрана и всехобъектов.
*/
private voidpaintPitContents(Graphics g) {
try {
myWorm.update(g); // update worm position
/* логика проверки съел ли червьобъект или нет и подсчсёт очков
для вывода на экран */
g.setColor(WormPit.ERASE_COLOUR);
g.fillRect((width- (SCORE_CHAR_WIDTH * 3))-START_POS,
height-START_POS,
(SCORE_CHAR_WIDTH * 3),
SCORE_CHAR_HEIGHT);
g.setColor(WormPit.DRAW_COLOUR);
// Отобразить новый счёт
g.drawString(""+ score,
width — (SCORE_CHAR_WIDTH* 3) — START_POS,
height — START_POS, g.TOP|g.LEFT);
} catch (WormExceptionse) {
gameOver = true;
}
}
/**
* Вывод на экран всехкомпонентов
*/
public voidpaint(Graphics g) {
if (forceRedraw) {
// Перерисоватьвесь экран
forceRedraw =false;
// Очистить заднийплан
g.setColor(WormPit.ERASE_COLOUR);
g.fillRect(0, 0,getWidth(),
getHeight());
// Нарисоватьграницы поля
g.setColor(WormPit.DRAW_COLOUR);
g.drawRect(1, 1,(width — START_POS), (height — START_POS));
// Отобразитьтекущий счёт
g.drawString(«L: » + level, START_POS, height, g.TOP|g.LEFT);
g.drawString("" + score,
(width — (SCORE_CHAR_WIDTH * 3)),
height,g.TOP|g.LEFT);
// Отобразить наивысший счёт на этомуровне
g.drawString(«H: »,
(width — (SCORE_CHAR_WIDTH * 4)),
(height +SCORE_CHAR_HEIGHT),
g.TOP|g.RIGHT);
g.drawString("" + WormScore.getHighScore(level),
(width — (SCORE_CHAR_WIDTH * 3)),
(height +SCORE_CHAR_HEIGHT),
g.TOP|g.LEFT);
// Нарисовать червя и еду
g.translate(START_POS, START_POS);
g.setClip(0, 0,CellWidth*CELL_SIZE, CellHeight*CELL_SIZE);
myWorm.paint(g);
myFood.paint(g);
} else {
// Нарисоватьчервя и еду
g.translate(START_POS, START_POS);
}
/**
* Вызывает перерисовку экрана икомпонентов при съёме паузы
*/
protected voidhideNotify() {
super.hideNotify();
forceRedraw = true;
if (!gameOver) {
gamePaused = true;
}
}
/**
* Основной цикл выполнения MIDP приложения
*/
public void run() {
while (!gameDestroyed){
try {
synchronized (myWorm) {
/* логика вычислений очковдвидения червя и действий пользователя*/
repaint();
}
}
} catch(java.lang.InterruptedException ie) {
}
}
}
/**
* Вызывает событиеуничтожения приложения
*/
public voiddestroyGame() {
synchronized (myWorm){
gameDestroyed =true;
//myWorm.notifyAll();
myWorm.notifyAll();
}
}
}
Приложение 2. Примеры создания MIDP приложений
Для выполнения задания номер 2потребуется создать TCP соединение спомощью сокетов и форму вводу передаваемых значений.
Для начала создадим MIDP приложение.
/**
* Основной класс MIDPприложения
*/
public class SocketMIDletextends MIDlet implements CommandListener {
private final staticString SERVER = «Server»;
private final staticString CLIENT = «Client»;
private staticString[] names = {SERVER, CLIENT};
private static Displaydisplay; // дисплей
private Form f; //форма
private ChoiceGroupcg;
private booleanisPaused;
private Server server;
private Client client;
// левая функциональная кнопка намобильном устройстве
private Command exitCommand = newCommand(«Exit», Command.EXIT, 1);
// правая функциональная кнопка намобильном устройстве
private Command startCommand = newCommand(«Start», Command.ITEM, 1);
/**
* Конструктор. создаётграфические компоненты на экране.
* И устанавливает обработчикисобытий.
*/
public SocketMIDlet() {
display = Display.getDisplay(this);
f = newForm(«Socket Demo»);
cg = newChoiceGroup(«Please select peer»,
Choice.EXCLUSIVE, names, null);
f.append(cg);
f.addCommand(exitCommand);
f.addCommand(startCommand);
f.setCommandListener(this);
display.setCurrent(f);
}
public booleanisPaused() {
return isPaused;
}
/**
* Запуск приложения
*/
public void startApp(){
isPaused = false;
}
/**
* Приостановкаприложения
*/
public void pauseApp(){
isPaused = true;
}
/**
* Остановкаприложения
*/
public voiddestroyApp(boolean unconditional) {
if (server !=null) {
server.stop();
}
if (client !=null) {
client.stop();
}
}
/**
* Обработчик событий.
*/
public voidcommandAction(Command c, Displayable s) {
if (c ==exitCommand) {
destroyApp(true);
notifyDestroyed();
} else if (c ==startCommand) {
String name =cg.getString(cg.getSelectedIndex());
if(name.equals(SERVER)) {
server =new Server(this);
server.start();
} else {
client =new Client(this);
client.start();
}
}
}
}
Для создания соединения с сервером спомощью сокетов потребуется следующая конструкция:
// Установить сокет соединение на5000 порту с localhost
SocketConnection sc =(SocketConnection) Connector.open(«socket://localhost:5000»);
// Входной поток для записионформации в сокет
InputStream is = sc.openInputStream();
// Выходной поток для чтенияинформации из сокета
OutputStream os = sc.openOutputStream();
Для открытия соединения для ожидания соединенияпотребуется следующая конструкция:
// Установить сокет на5000 порту
ServerSocketConnection scn= (ServerSocketConnection) Connector.open(«socket://:5000»);
// Ожидать соединения отдругих машин
SocketConnection sc =(SocketConnection) scn.acceptAndOpen();
Литература.
1. Кен Арнольд,Джеймс Гослинг, Дэвид Холмс. Язык программирования Java™.
2. Официальный сайтJava — java.sun.com/ (есть раздел на русском языке с учебником).
3. Java™ 2SDK, Micro Edition Documentation – java.sun.com/products/midp/index.jsp