Fejlett Webes Technológiák II. Bilicki Vilmos 2002.10.28
A mai előadás tartalma: Hatékony MIDP programozás Mintaalkalmazás: Akvárium (véletlen számok, szálak, grafika)
Hatékony MIDP programozás Végrehajtás sebessége JAR fájl mérete Erőforrás használat Tapasztalt sebesség (felhasználó akció kezelés)
Végrehajtási sebesség Program végrehajtási sebesség Hálózati sebesség
Program végrehajtási sebesség A program idejének 90%-át kódjának 10%-ában tölti. Célszerű az egész kód optimalizálása helyett a gyenge pontokat felfedni Gondos tervezés és algoritmus választás nagyobb előnnyel jár mint a soronkénti optimalizálás Java programok több időt töltenek a könyvtári kódok végrehajtásával mint saját kódunk végrehajtásával (Telefon típusonként, verziónként különböző lehet a végrehajtási idő)
Program végrehajtási sebesség Mérése: Az emulátor-ban futó program máshogy viselkedhet mint a valós környezetben futó. Általános módszer: long startTime = System.currentTimeMillis(); doSomething(); long timeTaken = System.currentTimeMillis() - startTime; A zavaró hatások elkerülése érdekében érdemes lefuttatni a mérések előtt a szemétgyűjtőt: System.gc(); A telefon által szolgáltatott idő nem feltétlenül milliszekundum !
Program végrehajtási sebesség Grafikai műveletek: A magas szintű grafikai elemekkel nem kell foglalkoznunk. Foglalkoznunk kell viszont a Canvas osztállyal. (telefon, verzió függő) Általában több száz sort vagy téglalapot írhatunk a képernyőre másodpercenként. Célszerű csak azt a képernyő részt újrarajzolni amelyet frissíteni kell: Canvas.repaint(int x, int y, int width, int height); Általánosan használt eljárás ha a képernyő frissítés lassú: a memóriába rajzolunk és csak a végén tesszük ki a képernyőre (a szükséges területet)
Program végrehajtási sebesség Szemét gyűjtés: Ne hozzunk létre feleslegesen sok objektumot, próbáljuk a már meglévőket újrahasznosítani. Megváltoztathatatlan (immutable) objektumok problémája: Fontosak a kód megbízhatósága szempontjából Gyorsan feleslegessé válhatnak (nem adhatunk nekik új értéket) Hatékonyabb megváltoztatható objektumokat használni
Példa static String reverse(String s) { String t = ””; for(int a=s.length()-1;a>=0;a--) t += s.charAt(a); } return t;
Program végrehajtási sebesség Mivel a String objektum immutable minden egyes betű hozzáadás új objektumot hoz létre ! Érdemes a StingBuffer objektumot használni !
Példa static String reverse(String s) { StringBuffer t = new StringBuffer(s.length()); for(int a=s.length()-1;a>=0;a--) t.append(charAt(a)); } return t.toString();
Program végrehajtási sebesség Többszálúság: Segítségével meggyorsíthatjuk programunk futási sebességét (amíg az egy szál várakozik valamire a másik dolgozhat) Nincs garancia a preemtív multitaszking-ra ! Nekünk kell olyan programot írnunk amely nem önző (yield(), wait())
Példa try { while(!stopped) try{ doSomething(); synchronized(this) wait(500); } catch(InterruptedException e)
Program végrehajtási sebesség Hálózati sebesség Átviteli sebessége (nagy mennyiségű adatnál érdekes) Késleltetés (kis mennyiségű adatnál érdekes)
Program végrehajtási sebesség Nagy késleltetés (akár százszorosa a vezetékes hálózaténak (x ms)) Nehéz a valós idejű játékokat írni (vagy lehetetlen) Mindenképpen külön szálnak kell kezelnie a hálózatot A HTTP kérések számát minimalizálni kell (a weblapon lévő összes elemet egyszerre kell kérnie)
Program végrehajtási sebesség Ne használjunk komplex redundáns protokollokat (SOAP) Esetenként célszerű saját egyszerű protokollt használni (verzió!!)
JAR fájl mérete Miért kell csökkenteni a méretét? Gyorsabban letölthető (kb 1 másodperc kbyte-onként) Felső méret korlát 30 kbyte Az általánosan használt telefonok korlátos tárolási kapacitással rendelkeznek (180 kbyte) A felhasználók szeretnek egyszerre több programot is tárolni, használni
JAR fájl mérete Hogyan csökkentsük? Minden osztály saját fejléccel rendelkezik Minél kevesebb osztályt használni Egy osztály dolgonként (nem kell a MVC architektúrát megvalósítatnunk) Minél kevesebb interfészt használjunk (osztályként tárolódik) Névtelen csomagok használata – minden osztályunkat egy azonos nevű csomagba helyezve csak növeli a JAR fájl méretét Korlátozzuk a static elemeket – tárolása több helyet foglal mint a normál elemeké
JAR fájl mérete Használjunk Obfuscator programokat Módosítják a lefordított Java programot, hogy kiszűrjék a szükségtelen információkat (hosszú metódus, változó nevek …) Érthetetlenné teszik lefordított kódunkat Kb. 10% méretcsökkenés érhető el!
JAR fájl mérete Könyvtárak (libraries) Normál szoftver fejlesztésnél a gyakran használt dolgokat célszerű könyvtárakként megvalósítani. Itt nem célszerű követnünk ezt a módszert (a könyvtárak tárolása miatt). Célszerűbb a cut&paste módszer használata (JAR fájl tömörítve)
JAR fájl mérete Erőforrások: PNG képek Mérete jelentősen eltérhet a különböző eszközöknél ! Képek kombinálása egy fájlba (hatékonyabb tömörítés) g.setClip(x, y, FRAME_WIDTH, FRAME_HEIGHT); g.drawImage(fiveMenImage, x - FRAME_WIDTH * frameNumber, y, Graphics.TOP|Graphics.LEFT); frameNumber {0,1,2,3,4,3,2,1}
Erőforrások kezelése Heap memória A nem használt objektumokat szabadítsuk fel Csak akkor hozzuk létre a ritkán használt objektumokat amikor használjuk (Help screen) Figyeljünk oda a memória szivárgásra (nem használjuk az objektumot, de még van rá mutató referencia) NULL !
Erőforrások kezelése Hálózat kezelés: Csak akkor használjuk ha mindenképpen szükséges (ne küldjünk akkor is csomagokat ha nem változott semmi) Próbáljuk az események menetét megjósolni és csak akkor küldjünk csomagot ha a változás nem számításaink szerint következik be.
Érzékelt sebesség Mindég jelezzük a felhasználónak azt, hogy a MIDlet működik (hosszú számítás, hálózati kapcsolat, …) Reakcióidő, amíg a felhasználónak várakoznia kell a reakcióra. Nem szabad egy másodpercnél hosszabbnak lennie. megszakítható szálak használata (Cancel gomb) jelzés a felhasználónak arról, hogy valami történik
Érzékelt sebesség A késleltetések elrejtése splash screen alkalmazása (pl forgó fogaskerekek, …)
FishTank Nokia UI API használata UI transzparens képek képek manipulálása UI egy képernyő
Felépítése
Működése FishTankCanvas mint UI Elkülönített animáló szál (tick()) A mélység 5 szintre van osztva 0 a legközelebbi, 4 a legtávolabbi Három halfajta mely a Fish objektum létrehozásánál véletlenszerűen dől el. A halak véletlenszerűen mozognak Ha elérik az akvárium szélét akkor megfordulnak (Nokia UI API) A vízinövények az akvárium alján vannak, három különböző minta lehetséges. Minden bokor önállóan animált.
package example.fishtank; import javax.microedition.midlet.*; import javax.microedition.lcdui.*; public class FishTankMIDlet extends MIDlet { private final FishTankCanvas canvas; public FishTankMIDlet() { canvas = new FishTankCanvas(this); } public void startApp() Display.getDisplay(this).setCurrent(canvas); canvas.start(); } public void pauseApp() { canvas.stop(); } public void destroyApp(boolean unconditional) void exitRequested() destroyApp(false); notifyDestroyed();
import javax. microedition. lcdui. ;import java. util import javax.microedition.lcdui.*;import java.util.Vector;import com.nokia.mid.ui.*; public class FishTankCanvas extends FullCanvas implements Runnable { static final int NUM_PLANES = 5; private static final int WEEDS_PLANE = 2; private static final int MILLIS_PER_TICK = 100; static int waterWidth, waterHeight; private final FishTankMIDlet parent; private final Vector fishes = new Vector(); private final Weeds weeds; private volatile Thread animationThread = null; public FishTankCanvas(FishTankMIDlet parent) this.parent = parent; waterWidth = getWidth(); waterHeight = (getHeight() * 15) / 16; weeds = new Weeds(); int numFishes = (waterWidth * waterHeight) / (35 * 35); if (numFishes > 20) { numFishes = 20; } for (int i = 0; i < numFishes; ++i) { fishes.addElement(new Fish()); } }
synchronized void start() { animationThread = new Thread(this); animationThread.start(); } synchronized void stop() { animationThread = null; } public void run() { Thread currentThread = Thread.currentThread(); try { while (currentThread == animationThread) { long startTime = System.currentTimeMillis(); tick(); repaint(0, 0, waterWidth, waterHeight); serviceRepaints(); long timeTaken = System.currentTimeMillis() - startTime; if (timeTaken < MILLIS_PER_TICK) synchronized (this) { wait(MILLIS_PER_TICK - timeTaken); } } catch (InterruptedException e)
private synchronized void tick() { for (int i = 0; i < fishes.size(); ++i) { Fish fish = (Fish)(fishes.elementAt(i)); fish.tick(); } } public void paint(Graphics g) { drawFishTank(g); } private synchronized void drawFishTank(Graphics g) { g.setColor(0, 255, 255); g.fillRect(0, 0, waterWidth, waterHeight); g.setColor(255, 128, 64); g.fillRect(0, waterHeight, waterWidth, getHeight()); for (int plane = 0; plane < NUM_PLANES; plane++) if (plane == WEEDS_PLANE) { weeds.draw(g); } for (int i = 0; i < fishes.size(); i++) Fish fish = (Fish)(fishes.elementAt(i)); if (fish.getZ() == plane) { fish.draw(g); }
public class Fish { private static final int TYPE_NUM = 3; private static final int FRAME_NUM = 2; private static final Image[][] fishImage; private static final int fishWidth, fishHeight; private static final Random random = new Random(); private final int type; private int frame; private int x, y, z; private int vx, vy; private int ticksSinceLastChangeX = 0; private int ticksSinceLastChangeY = 0; private int ticksSinceLastChangeD = 0; static { fishImage = new Image[TYPE_NUM][FRAME_NUM]; int i = 0; int k = 0; try for (i = 0; i < TYPE_NUM; i ++) for (k = 0; k < FRAME_NUM; k ++) { fishImage[i][k] = Image.createImage("/fish" + i + k + ".png"); } } catch (java.io.IOException e) { fishImage[i][k] = null; } fishWidth = fishImage[0][0].getWidth(); fishHeight = fishImage[0][0].getHeight();
public Fish() { type = rand(TYPE_NUM); frame = rand(FRAME_NUM); x = rand(FishTankCanvas.waterWidth - fishWidth); y = rand(FishTankCanvas.waterHeight - fishHeight); vx = rand(2) * 2 - 1; // -1 or 1 vy = rand(3) - 1; // -1, 0 or 1 z = rand(FishTankCanvas.NUM_PLANES); }
public void tick() { ticksSinceLastChangeX++; ticksSinceLastChangeY++; ticksSinceLastChangeD++; if ((ticksSinceLastChangeX > 20) && (rand(20) == 0)) vx = rand(2) * 2 - 1; // -1 or 1 ticksSinceLastChangeX = 0; } if (((vx < 0) && (x + vx < 0)) || ((vx > 0) && (x + fishWidth + vx > FishTankCanvas.waterWidth))) vx = -vx; ticksSinceLastChangeX = 0; if ((ticksSinceLastChangeY > 20) && (rand(20) == 0)) vy = rand(3) - 1; // -1, 0 or 1 ticksSinceLastChangeY = 0; if (((vy < 0) && (y + vy < 0)) || ((vy > 0) && (y + fishHeight + vy > FishTankCanvas.waterHeight))) if (rand(2) == 0) { vy = -vy; } else { vy = 0; }
if ((ticksSinceLastChangeD > 10) && (rand(10) == 0)) { z += rand(3) - 1; // -1, 0 or 1 if ((z < 0) || (z == FishTankCanvas.NUM_PLANES)) if (rand(2) == 0) z = (z < 0) ? 0 : z - 1; } else z = (z < 0) ? 1 : z - 2; ticksSinceLastChangeD = 0; x += vx; y += vy;
public void draw(Graphics g) { DirectGraphics dg = DirectUtils.getDirectGraphics(g); Image img = fishImage[type][frame]; if (img != null) if (vx < 0) dg.drawImage(img, x, y, (Graphics.LEFT | Graphics.TOP), DirectGraphics.FLIP_HORIZONTAL); } else dg.drawImage(img, x, y, (Graphics.LEFT | Graphics.TOP), 0); frame = (frame + rand(2)) % FRAME_NUM; int getZ() { return z; } static int rand(int scale) { return (random.nextInt() << 1 >>> 1) % scale; }
public class Weeds { private static final int PATTERN_NUM = 3; private static final int FRAME_NUM = 4; private static final Image[][] weedImage; private static final Random random = new Random(); private static final int weedHeight, weedWidth; private final int weedBunches; private final int frame[]; private final int pattern[]; private final int y; static { weedImage = new Image[PATTERN_NUM][FRAME_NUM]; int i = 0; int k = 0; try for (i = 0; i < PATTERN_NUM; i ++) for (k = 0; k < FRAME_NUM; k ++) { weedImage[i][k] = Image.createImage("/weed" + i + k + ".png"); } } catch (java.io.IOException e) { weedImage[i][k] = null; } weedWidth = weedImage[0][0].getWidth(); weedHeight = weedImage[0][0].getHeight();
public Weeds() { weedBunches = (FishTankCanvas.waterWidth - 1) / weedWidth + 1; pattern = new int[weedBunches]; frame = new int[weedBunches]; for (int i = 0; i < weedBunches; i ++) { int patternNum = Fish.rand(PATTERN_NUM); while ((i > 0) && patternNum == pattern[i-1]) { patternNum = Fish.rand(PATTERN_NUM); } pattern[i] = patternNum; frame[i] = Fish.rand(FRAME_NUM); } y = FishTankCanvas.waterHeight - weedHeight + 2; public void draw(Graphics g) Image img = null; for (int i = 0; i < weedBunches; i++) img = weedImage[pattern[i]][frame[i]]; if (img != null) g.drawImage(img, i * weedWidth, y, Graphics.LEFT | Graphics.TOP); frame[i] = (frame[i] + 1) % FRAME_NUM; } }}
Forrás: http://www.forum.nokia.com/main/1,35452,1_0,00.html