Pull to refresh

Screensaver на J2ME или Назад в прошлое. Часть первая

Reading time7 min
Views3.4K
Доброго времени суток!

Введение


На дворе третье января, а душа то и дело требует написать какую-нибудь программку. Недолго думая, я вспомнил, что когда-то предложил поправочку автору топика Splash Screen на J2ME, после чего сомнения по поводу того, что писать, окончательно развеялись. Сегодня я предлагаю вам окунуться в мир «обычных звонилок» и написать для них приложение — это будет обычный скринсейвер.


Итак, приступим


Для начала вам следует установить себе необходимое для разработки ПО. Думаю, не надо расписывать, что необходимо ставить себе на ПК для компиляции и запуска нашего приложения, т.к. в интернете такую инструкцию можно найти за пять секунд. Вот, например. Лично я компилирую все исходники приложений для J2ME на «рядовой звонилке». Да, да, есть умельцы на земле русской, которые портировали компилятор с преверификатором на платформу J2ME.
После того, как всё установлено, неплохо было бы вспомнить саму теорию, начиная устройством J2ME приложения и заканчивая директивами манифеста нашего приложения. Впрочем, если вы не знаете, что к чему, то не расстраивайтесь — немного теории по разработке приложений для J2ME будет разобрано в данном топике.

Анатомия J2ME приложения


Вообще, приложение в J2ME называется MIDlet-ом. Почему так, вы узнаете далее.

javax.microedition.midlet.MIDlet

Приложение для J2ME является ничем иным, как классом, наследованным от класса MIDlet из пакета javax.microedition.midlet. Данный класс является «сердцем» MIDlet'a. В нашем классе мы обязательно должны объявить три метода, необходимые для функционирования нашего приложения:

public void startApp() {
     // данный метод вызывается при запуске нашего приложения
}

public void pauseApp() {
     // данный метод вызывается, когда на наше устройство поступает звонок, SMS и т.д.
}

public void destroyApp(boolean unconditional) {
     // данный метод вызывается тогда, когда он вызывается.
     // сейчас объясню...
}

Дело в том, что данный метод указан, как необходимый к реализации в то время, как практической пользы он не несёт. Методы startApp() и pauseApp() используются самой AMS, а destroyApp() — нет. По сложившейся традиции, обычно блок данной функции выглядит следующим образом:

public void destroyApp(boolean unconditional) {
     notifyDestroyed(); // данный метод закрывает наше приложение
}

Как вы можете видеть, прекращением цикла жизни приложений руководит функция notifyDestroyed(), а не destroyApp(). Ну да ладно, предназначение последней так и останется тайной. Не будем отвлекаться, а перейдём дальше.

javax.microedition.lcdui.Display

Следующим шагом в реализации нашего приложения является получение ссылки на объект Display пакета javax.microedition.lcdui. Как вы могли догадаться, делается это для получения возможности работы с экраном, формами (javax.microedition.lcdui.Form) или же холстом (javax.microedition.lcdui.Canvas) и переключения между ними. Получить ссылку мы можем следующим образом:

Display dsp = Display.getDisplay(this); // вот так...

Дело сделано, переходим к следующему шагу.

javax.microedition.lcdui.game.GameCanvas

Сразу же хочется отметить, что с появлением класса GameCanvas — он начал вытеснять обычный Canvas из-за невозможности двойной буфферизации «из коробки», как таковой последнего. Поэтому, в качестве холста, я предлагаю использовать именно GameCanvas. Сказано — сделано.
GameCanvas и Canvas отличаются всего-лишь по двум признакам: первый уже был указан (это двойная буфферизация), а вторым является необходимость объявления метода paint в последнем. Для наглядности приведу следующий код реализации холста с помощью Canvas:

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics;

public class OurCanvas extends Canvas { // наследуемся от Canvas

     public void paint(Graphics g) {
	     
		 int w = getWidth(); // получаем высоту экрана
		 int h = getHeight(); // и про ширину не забываем
		 
		 g.setColor(0xffffff); // устанавливаем цвет кисти
		 g.fillRect(0,0,w,h); // заполняем выбранную область заданным цветом
	 }
}

В методе paint() мы выполняем всю отрисовку графических примитивов. Данный подход не особо эффективен по многим причинам, одной из которой является желание разделить логику.
GameCanvas позволяет вынести логику в другие методы, классы и т.д. Вот так:

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics;

public class OurCanvas extends GameCanvas implements Runnable { // наследуемся от GameCanvas и реализуем интерфейс Runnable, который используется классом Thread

     public void run() {
	     
		 Graphics g = getGraphics(); // получаем ссылку на объект Graphics
		                           // с помощью которого мы и отрисовываем эти самые графические примитивы
								   
		 int w = getWidth(); // получаем высоту экрана
		 int h = getHeight(); // и про ширину не забываем
		 
		 g.setColor(0xffffff); // устанавливаем цвет кисти
		 g.fillRect(0,0,w,h); // заполняем выбранную область заданным цветом
	 }
}

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

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics;

public class OurCanvas extends GameCanvas implements Runnable {
	 Graphics g = getGraphics();				   
	 int w = getWidth();
	 int h = getHeight();
		 
     public void run() {
		 g.setColor(0xffffff);
		 g.fillRect(0,0,w,h);
		 isSomethingDone();
	 }
	 
	 public void isSomethingDone() {
	     g.setColor(0xababab);
		 g.drawLine(25,25,125,125);
	 }
}

Как-то так. Вот и всё — необходимая база знаний для реализации задуманного у нас есть. Поехали!

Переходим к разработке


Наше приложение будет состоять из нескольких классов:
Встречайте! midlet.java:

import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.*;
import java.util.Timer;

public class midlet extends MIDlet {
     Timer timer;
     midletTimerTask task;
     midletCanvas canvas;

     public midlet () {
         canvas  = new midletCanvas(this);    // создаем канву
         timer = new Timer ();
         task = new midletTimerTask (canvas); // и задачу, которая будет выполняться
         timer.scheduleAtFixedRate(task,10,10); // каждые 10 миллисекунд
 // 10 мс - очень мало. Поэтому вызываемый метод repaint будет проверять, 
 // закончена ли перерисовка предыдущего экрана, с помощью флага in
    }
 
     protected void startApp() { // сразу передаем управление канве
         Display.getDisplay(this).setCurrent (canvas);
     }
 
     protected void pauseApp() {}

     protected void destroyApp(boolean unconditional) {}
 
     public void exitMIDlet()  {
         notifyDestroyed();
     }
}

Далее сам холст — midletCanvas.java:

import javax.microedition.lcdui.*;

class midletCanvas extends Canvas  {
 midlet midlet;
 random Random;
 static int [] PlasmaTab = { //таблица цветов плазмы - 256 элементов
  32,32,33,34,35,35,36,37,38,39,39,40,41,42,42,43,
  44,45,45,46,47,47,48,49,49,50,51,51,52,52,53,54,
  54,55,55,56,56,57,57,58,58,59,59,59,60,60,60,61,
  61,61,62,62,62,62,63,63,63,63,63,63,63,63,63,63,
  63,63,63,63,63,63,63,63,63,63,62,62,62,62,62,61,
  61,61,60,60,60,59,59,58,58,58,57,57,56,56,55,54,
  54,53,53,52,52,51,50,50,49,48,48,47,46,46,45,44,
  43,43,42,41,40,40,39,38,37,37,36,35,34,33,33,32,
  31,30,30,29,28,27,26,26,25,24,23,23,22,21,20,20,
  19,18,17,17,16,15,15,14,13,13,12,11,11,10,10, 9,
   9, 8, 7, 7, 6, 6, 5, 5, 5, 4, 4, 3, 3, 3, 2, 2,
   2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2,
   2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9,
   9,10,11,11,12,12,13,14,14,15,16,16,17,18,18,19,
  20,21,21,22,23,24,24,25,26,27,28,28,29,30,31,31
 }; 
 //Естественно, я не набивал вручную эту таблицу, а получил 
 //по закону 32* (1+ sin (x*(2*pi/255)), приведя все к целым числам

 static int Delta = 6; //размер ячейки плазмы в пикселах

 static int Yold_pos=0, Yold_asp=0, Xold_pos=0, Xold_asp=0;
 int Ynew_pos, Ynew_asp, Xnew_pos, Xnew_asp, x, y, Color;
 static int Width, Height; //Ширина и высота экрана
 static boolean in = false; //состояние перерисовки
 Graphics gbuffer; //а это для
 Image screen;     //двойной буферизации
  
 public midletCanvas (midlet midlet)  {
  this.midlet = midlet;
  Random = new random ();
  setFullScreenMode (true); //Единственный оператор профиля MIDP 2.0
  Width = getWidth ();
  Height = getHeight ();
  screen=Image.createImage(Width,Height);
  gbuffer=screen.getGraphics(); //рисовать будем на gbuffer,
  draw (gbuffer); //а потом одномоментно выводить его на канву!
 }

 void draw (Graphics g) { //рисуем в буфере очередное состояние плазмы
  in = true;
  Ynew_pos = Yold_pos;         
  Ynew_asp = Yold_asp;
  for (y=0; y<Height; y+=Delta) {
   Xnew_pos = Xold_pos;       
   Xnew_asp = Xold_asp;
   for (x=0; x<Width; x+=Delta) {
    Color = PlasmaTab[Ynew_pos]+ PlasmaTab[Ynew_asp]+ 
            PlasmaTab[Xnew_pos]+ PlasmaTab[Xnew_asp];
    g.setColor(((255-Color)<<16 | Color<<8 | 128+(Color>>1)));
    g.fillRect (x,y,Delta,Delta);
    Xnew_pos += 1;
    if (Xnew_pos > 255) Xnew_pos=0;
    Xnew_asp += 7;
    if (Xnew_asp > 255) Xnew_asp=0;
   }
   Ynew_pos += 2;
   if (Ynew_pos > 255) Ynew_pos=0;
   Ynew_asp += 1;
   if (Ynew_asp > 255) Ynew_asp=0;
  }
  Xold_pos -= 2;
  if (Xold_pos<0) Xold_pos=255;
  Xold_asp += Random.get(8);
  if (Xold_asp > 255) Xold_asp=0;
  Yold_pos += 4;
  if (Yold_pos > 255) Yold_pos=0;
  Yold_asp -= Random.get(6);
  if (Yold_asp<0) Yold_asp=255;
  in = false;
 }

 protected void paint (Graphics g) {
  if (in == true) return; //Если мы еще в состоянии перерисовки
  g.drawImage(screen,0,0,0);
  draw (gbuffer);
 }
    
 public void keyPressed(int keyCode) {
  switch (keyCode) { //Выход по клавише #
   case Canvas.KEY_POUND:    
    midlet.exitMIDlet();
   break;
  }
 }
}

Следующим по счёту идёт таймер — midletTimerTask.java:

import java.util.TimerTask;
import javax.microedition.midlet.MIDlet;

class midletTimerTask extends TimerTask {
 midletCanvas canvas;  
    
 public midletTimerTask (midletCanvas canvas) {
  this.canvas = canvas;
 }

 public final void run() {  
  canvas.repaint();
 }
}

И, наконец, реализация выбора случайных чисел — random.java

import java.util.Date;
import java.util.Random;

class random { //Класс-оболочка генератора случайных чисел
 private Random r;
 private Date d;

 public random () {
  d = new Date ();
  r = new Random (d.getTime());
 }

 public int get (int max) { //случайное число из диапазона [0,max-1]
  int n=r.nextInt()%max; 
  return (n<0 ? -n : n);
 }
}


Скандалы, интриги, расследования


Мои исходники не хотят компилироваться. Вернее, они компилируются, но выполняется не то, что хочется мне. Поздняя ночь, поэтому вашему покорному слуге пришлось взять не свои исходники. Извиняюсь за форматирование кода — завтра поправлю.

Дальше дело техники


Компиляция и преверификация. Этот шаг я оставляю на вас.

MANIFEST.MF


Я предлагаю вам повозиться с этим файлом, а находится он в каталоге META-INF. Ведь, если мы делаем скринсейвер, то и вести он должен себя соответствующе. К сожалению, такое поведение доступно только телефонам от Sony Ericsson. Давайте добавим в него следующую строку:
SEMC-StandbyApplication: Y


Что мы сделали


Мы сделали возможной загрузку нашего скринсейвера, как фоновый рисунок. Для этого вам необходимо кинуть MIDlet в телефон. Далее заходим в «Приложения», находим наше. Следующим действием будет нажатие правой софт-клавиши и выбор пункта «Как фон. рисунок». Выбираем — кушать подано. Вы увидите следующее

Вот, что получилось






All you need


Исходники вы можете взять на Pastebin. вот они:
midlet.java
midletCanvas.java
midletTimerTask.java
random.java

Заключение


Вот и всё. Всего-лишь за несколько шагов мы написали MIDlet. В наши дни J2ME постепенно уходит в никуда и, возможно, у вас уже не осталось подобной трубки, но пока Oracle занимается её поддержкой — кто знает, то ли ещё будет. Надеюсь вам было интересно.

До скорых встреч!
Tags:
Hubs:
+13
Comments36

Articles