Programming Java 2 Micro Edition for Symbian OS 2004 (779882), страница 65
Текст из файла (страница 65)
In this case weneed to maintain a stack containing the branches: we push nodes ontothe stack as we go down the tree, then pop them off the stack as we goback up. Details of how to do this can be found in Section 2.3.1 of TheArt of Computer Programming, Volume 1 by Knuth.These approaches to searching and enumerating are used in theordered binary tree container discussed in Section 7.14.7.12.5 PollingAvoid using loops that poll. The following is a snippet of code that pollsthe keepRunning flag. (printText(String) just displays Stringin a UI component.)boolean keepRunning = false;...public void run(){printText("Started");while(keepRunning){printText("In loop");}printText("Stopped");}Running the loop increased battery consumption from 66 mA to anunacceptable 163mA on my Psion Series 5MX (unfortunately there isno easy way of monitoring battery consumption on more recent mobilephones).
The battery consumption returned to 66 mA when keepRunning was set to false. Further, loops like this will hog the CPU anddeprive other applications of CPU cycles.In order to read from an InputStream we can sit in a loop pollingfor the number of available bytes using InputSteam.available().When data becomes available, we read it using InputStream.read().However, it is far better to create a separate reader thread that callsInputStream.read() directly. Because this method blocks, the threadwill wait until data is present and not consume any unnecessary CPUbandwidth.
Although both an event model and a wait–notify modelrequire an extra Thread, this is generally worthwhile.3587.13WRITING OPTIMIZED CODEGraphicsGraphics coders face two problems: speed and flicker. There are threemain causes of flicker:• before drawing a new frame we need to clear the old frame bypainting in the background; we see flicker because the backgroundshows through between frames• there is insufficient time to paint frames between paint requests, sothat we start drawing the new frame before the old frame has beencompletely drawn• the underlying OS buffers paint requests, so not every frame is drawn.There are two main approaches we can take to reducing flicker.
Wecan use a clipping region, so that we only repaint the area that needs tobe updated, or we can create the new image in an off-screen buffer andcopy this into our graphics context as required.Using a clipping region is generally faster and will reduce, but noteliminate flicker. An off-screen buffer should eliminate flicker and improveperformance, but requires extra memory for the buffer.However, the graphics on Symbian’s Java implementation are alreadydouble-buffered so that flickering should not occur, and you shouldnot use double-buffering in your application. In general, you can useCanvas.isDoubleBuffered() to check if the implementation isdouble-buffered and code appropriately. Interestingly, the AMark 1.3benchmark does not check but does the double-buffering itself.
Thisunfairly penalizes platforms like Symbian OS which provide doublebuffering.7.13.1 The Popcorn Drawing DemonstratorPopcorn is a test program used to investigate the different approachesto painting. It moves a puck back and forth across the screen without dropping frames (Figure 7.2). The code can be downloaded fromwww.symbian.com/books.The MIDlet is implemented with three strategies for painting (the usercan choose which one to use):• on each repaint request, paint the whole background, then the puck• on each repaint request, just paint the background that has changed,then paint in the puck• create separate images for the background and the puck; on each paintrequest, paint in only the bits that have changed in the backgroundimage and then the puck image.GRAPHICS359Figure 7.2 Popcorn test program.The squared background in the playing field gives paint() somethingto do, as well as adding a bit of interest.It is instructive to run Popcorn in the Wireless Toolkit emulator withdouble-buffering enabled and then disabled.
In the latter case the basicplaying field flickers so badly as to be unusable. The playing field witha clipping region just flickers in the clipped region, which is interestingbecause it makes the clipping region visible. None of the implementationsflicker when run on Symbian OS.Popcorn is written for MIDP 1.0. The GameCanvas class of MIDP 2.0can be used to simplify the program logic, and we shall see later how wecan modify the MIDlet to use this class.7.13.1.1 Painting the Whole BackgroundThe PlayingField class displays the puck (a filled circle) in a rectangular playing field. It is responsible for painting in the playing fieldand the puck. Every time PlayingField.move() is called, the puck isredrawn xDelta pixels to the left or to the right.
The horizontal size ofthe playing field is xMax pixels plus the size of the puck (PUCK_SIZE).360WRITING OPTIMIZED CODEPlayingField is a Canvas. It implements Runnable so that wecan have a separate thread for the animation, and CommandListenerso that we can add Go and Back Command objects (menu entries)to the display. We also define a few constants, including some basiccolors. xDelta is the amount we move the puck on each frame (+1or −1 pixel).
xMax is the playing width, the distance the puck moves.paintCount is the number of times paint() is called in order toredisplay the puck in a new position (ignoring paint() requests whenpart of the screen has been obscured). paintCount should be the sameas the number of moves we request; if not, we know we have droppeda frame.class PlayingField extends Canvas implements Runnable, CommandListener{private Command goCommand = new Command("Go", Command.SCREEN, 1);private Command backCommand = new Command("Back", Command.BACK, 1);protected static final int PUCK_SIZE = 30;protected static final int Y_POS = 40;protected static final int LOOP_COUNT = 500;protected static final int BLACK = 0x00000000;protected static final int RED = 0x00C12100;protected static final int BLUE = 0x000000FF;protected static final int GREEN = 0x00008800;protected static final int WHITE = 0x00FFFFFF;protected static final int GREY = 0x00808080;int colourBackground = BLUE;protected int xPos = 0;protected int xPosOld = 0;protected int xDelta = 1;protected int xMax;int paintCount = 0;The constructor adds the commands and calculates xMax, the distancethe puck moves:PlayingField(){addCommand(goCommand);addCommand(backCommand);setCommandListener(this);xMax = getWidth() - PUCK_SIZE;}commandAction() either takes us back to the start screen (which isa simple menu for choosing a repaint method), or starts the animation:public void commandAction(Command c, Displayable s) {long timeElapsed = 0;if (c == backCommand) {Popcorn.popcorn.startApp();}GRAPHICS361else if (c == goCommand){new Thread(this).start();}}run() sits in a loop that repeats 500 times (the number of animations)and records how long it takes.
move() is called on each pass to do mostof the work. At the end, run()uses a class called OutputTextBox todisplay the results:public void run(){paintCount = 0;long timeStart = System.currentTimeMillis();for(int i = LOOP_COUNT; --i >=0; ){move();}long timeElapsed = System.currentTimeMillis() - timeStart;OutputTextBox.backScreen = this;OutputTextBox.println("Time: " + timeElapsed);OutputTextBox.println("Count: " + paintCount);}move() calculates the new position, modifies the direction if necessaryand then calls repaint() to request that the screen is repainted withthe puck in the new position:public void move(){xPosOld = xPos;xPos += xDelta;if((xPos <= 0) || (xPos > xMax)) xDelta = -xDelta;repaint();serviceRepaints();}paint() draws in first the background and then the puck. If the puckhas moved, paintCount is incremented:public void paint(Graphics g){drawBackground(g);g.setColor(RED);g.fillRoundRect(xPos, Y_POS, PUCK_SIZE, PUCK_SIZE, PUCK_SIZE,PUCK_SIZE);if(xPosOld != xPos) paintCount++;}drawBackground() is called by paint().
It fills in a backgroundcolor and then paints a pattern of filled and unfilled squares. Interestingly, you get a marginal (1–2 %) increase in performance on Symbian’s362WRITING OPTIMIZED CODEimplementation if you do line and filled drawing separately, rather thaninterlaced in the same loop as we have done:void drawBackground(Graphics g){int width = this.getWidth();int height = this.getHeight();int pitch = 12;int size = 10;g.setColor(WHITE);g.fillRect(0, 0, width, height);g.setColor(colourBackground);for (int xPos = 0; xPos < width; xPos += pitch){for (int yPos = 0; yPos < height; yPos += pitch){g.fillRect(xPos, yPos, size-1, size-1);g.drawRect(xPos, yPos, size, size);}}}As a general rule it is better to paint a few large areas rather than lotsof small areas, even if it means you have to paint in a significantly biggertotal area.7.13.1.2 Using a Clipping RegionWe use a clipping region so that we paint only what is needed.
To definea clipping region we simply need to modify move() so that we callrepaint() with a clipping rectangle (in fact, if you look at the Popcornsource you will see a new class, PlayingFieldWithClipping, thatsubclasses PlayingField):public void move(){int absXDelta = Math.abs(xDelta);xPosOld = xPos;xPos += xDelta;if((xPos <= 0) || (xPos > xMax)) xDelta = -xDelta;repaint(xPos - absXDelta, Y_POS, PUCK_SIZE + 2*absXDelta,PUCK_SIZE);serviceRepaints();}We need to ensure the clipping region encompasses the old positionand the new position of the puck irrespective of the puck’s direction,which means we are repainting a bit more than we strictly need to.Note that paint requests may be buffered so that the actual clippingrectangle will be the union of the clipping rectangles requested by eachrepaint().GRAPHICS3637.13.1.3 Using an Image BufferThere are various ways in which we could use an image buffer.
In thiscase we have created an image for the background and an image for thepuck. On each animation we draw in the background image, then thepuck image. To improve performance further, we again call repaint()with a clipping region.It is worth going through the whole PlayingFieldBufferedclass – there is not much to it, as it only has to add a bit to PlayingField.puckImage and backgroundImage hold the two images:class PlayingFieldBuffered extends PlayingField{Image puckImage;Image backgroundImage;The constructor calls the PlayingField constructor so that thecommands are added to the canvas, sets a background color for later useand calls makeImages():PlayingFieldBuffered(){super();colourBackground = RED;makeImages();}makeImages() makes the background and puck images. The firstthree lines create our background image, making use of the superclassdrawBackground() method.
For the puck it was easier to create aPNG image and load it from the JAR file. This is because we needed asolid circular disc, with the rest of the image transparent. Constructingimages with transparency or alpha blending is difficult with MIDP 2.0;the closest method is probably Image.createRGBImage().private void makeImages(){backgroundImage = Image.createImage(this.getWidth(),this.getHeight());Graphics bG = backgroundImage.getGraphics();drawBackground(bG);try{puckImage = Image.createImage("/res/puckSm.png");}catch (java.io.IOException ioe){OutputTextBox.println("Couldn't find puck image");}}364WRITING OPTIMIZED CODEpaint() simply has to paint in the background image, then the puckimage in the correct position:public void paint(Graphics g) {g.drawImage(backgroundImage, 0, 0, Graphics.TOP|Graphics.LEFT);g.drawImage(puckImage, xPos, Y_POS, Graphics.TOP|Graphics.LEFT);if(xPosOld != xPos) paintCount++;}move() is identical to the move() method in PlayingFieldWithClipping.