swingworker--Simple Background Tasks

Simple Background Tasks

The TumbleItem applet loads a set of graphic files used in an animation. If the graphic files are loaded from an initial thread, there may be a delay before the GUI appears. If the graphic files are loaded from the event dispatch thread, the GUI may be temporarily unresponsive.

To avoid these problems, TumbleItem creates and executes an instance of SwingWorker from its initial threads. The object's doInBackground method, executing in a worker thread, loads the images into an ImageIcon array, and returns a reference to it. Then the done method, executing in the event dispatch thread, invokes get to retrieve this reference, which it assigns to an applet class field named imgs. This allows TumbleItem to construct the GUI immediately, without waiting for the images to finish loading.

Here is the code that defines and executes the SwingWorker object.

SwingWorker worker = new SwingWorker<ImageIcon[], Void>() {

    @Override

    public ImageIcon[] doInBackground() {

        final ImageIcon[] innerImgs = new ImageIcon[nimgs];

        for (int i = 0; i < nimgs; i++) {

            innerImgs[i] = loadImage(i+1);

        }

        return innerImgs;

    }

 

    @Override

    public void done() {

        //Remove the "Loading images" label.

        animator.removeAll();

        loopslot = -1;

        try {

            imgs = get();

        } catch (InterruptedException ignore) {}

        catch (java.util.concurrent.ExecutionException e) {

            String why = null;

            Throwable cause = e.getCause();

            if (cause != null) {

                why = cause.getMessage();

            } else {

                why = e.getMessage();

            }

            System.err.println("Error retrieving file: " + why);

        }

    }

};

 

All concrete subclasses of SwingWorker implement doInBackground; implementation of done is optional.

Notice that SwingWorker is a generic class, with two type parameters. The first type parameter specifies a return type for doInBackground, and also for the get method, which is invoked by other threads to retrieve the object returned by doInBackground. SwingWorker's second type parameter specifies a type for interim results returned while the background task is still active. Since this example doesn't return interim results, Void is used as a placeholder.

You may wonder if the code that sets imgs is unnecessarily complicated. Why make doInBackground return an object and use done to retrieve it? Why not just have doInBackground set imgs directly? The problem is that the object imgs refers to is created in the worker thread and used in the event dispatch thread. When objects are shared between threads in this way, you must make sure that changes made in one thread are visible to the other. Using get guarantees this, because using get creates a happens before relationship between the code that creates imgs and the code that uses it. For more on the happens before relationship, refer to Memory Consistency Errors in the Concurrency lesson.

There are actually two ways to retrieve the object returned by doInBackground.

l  Invoke SwingWorker.get with no arguments. If the background task is not finished, get blocks until it is.

l  Invoke SwingWorker.get with arguments indicating a timeout. If the background task is not finished, get blocks until it is — unless the timeout expires first, in which case get throws java.util.concurrent.TimeoutException.

Be careful when invoking either overload of get from the event dispatch thread; until get returns, no GUI events are being processed, and the GUI is "frozen". Don't invoke get without arguments unless you are confident that the background task is complete or close to completion.

public class TumbleItem extends JApplet implements ActionListener {

    int loopslot = -1; // the current frame number

 

    String dir; // the directory relative to the codebase

    // from which the images are loaded

 

    Timer timer;

    // the timer animating the images

 

    int pause; // the length of the pause between revs

 

    int offset; // how much to offset between loops

    int off; // the current offset

    int speed; // animation speed

    int nimgs; // number of images to animate

    int width; // width of the applet's content pane

    Animator animator; // the applet's content pane

 

    ImageIcon imgs[]; // the images

    int maxWidth; // width of widest image

    JLabel statusLabel;

 

    // Called by init.

    protected void loadAppletParameters() {

       // Get the applet parameters.

       String at = getParameter("img");

       dir = (at != null) ? at : "images/tumble";

       at = getParameter("pause");

       pause = (at != null) ? Integer.valueOf(at).intValue() : 1900;

       at = getParameter("offset");

       offset = (at != null) ? Integer.valueOf(at).intValue() : 0;

       at = getParameter("speed");

       speed = (at != null) ? (1000 / Integer.valueOf(at).intValue()) : 100;

       at = getParameter("nimgs");

       nimgs = (at != null) ? Integer.valueOf(at).intValue() : 16;

       at = getParameter("maxwidth");

       maxWidth = (at != null) ? Integer.valueOf(at).intValue() : 0;

    }

 

    /**

     * Create the GUI. For thread safety, this method should be invoked from the

     * event-dispatching thread.

     */

    private void createGUI() {

       // Animate from right to left if offset is negative.

       width = getSize().width;

       if (offset < 0) {

           off = width - maxWidth;

       }

 

       // Custom component to draw the current image

       // at a particular offset.

       animator = new Animator();

       animator.setOpaque(true);

       animator.setBackground(Color.white);

       setContentPane(animator);

 

       // Put a "Loading Images..." label in the middle of

       // the content pane. To center the label's text in

       // the applet, put it in the center part of a

       // BorderLayout-controlled container, and center-align

       // the label's text.

       statusLabel = new JLabel("Loading Images...", JLabel.CENTER);

       animator.add(statusLabel, BorderLayout.CENTER);

    }

 

    // Background task for loading images.

    SwingWorker worker = new SwingWorker<ImageIcon[], Void>() {

       @Override

       public ImageIcon[] doInBackground() {

           final ImageIcon[] innerImgs = new ImageIcon[nimgs];

           for (int i = 0; i < nimgs; i++) {

              innerImgs[i] = loadImage(i + 1);

           }

           return innerImgs;

       }

 

       @Override

       public void done() {

           // Remove the "Loading images" label.

           animator.removeAll();

           loopslot = -1;

           try {

              imgs = get();

           } catch (InterruptedException ignore) {

           } catch (java.util.concurrent.ExecutionException e) {

              String why = null;

              Throwable cause = e.getCause();

              if (cause != null) {

                  why = cause.getMessage();

              } else {

                  why = e.getMessage();

              }

              System.err.println("Error retrieving file: " + why);

           }

       }

    };

 

    // Called when this applet is loaded into the browser.

    public void init() {

       loadAppletParameters();

 

       // Execute a job on the event-dispatching thread:

       // creating this applet's GUI.

       try {

           SwingUtilities.invokeAndWait(new Runnable() {

              public void run() {

                  createGUI();

              }

           });

       } catch (Exception e) {

           System.err.println("createGUI didn't successfully complete");

       }

 

       // Set up timer to drive animation events.

       timer = new Timer(speed, this);

       timer.setInitialDelay(pause);

       timer.start();

 

       // Start loading the images in the background.

       worker.execute();

    }

 

    // The component that actually presents the GUI.

    public class Animator extends JPanel {

       public Animator() {

           super(new BorderLayout());

       }

 

       protected void paintComponent(Graphics g) {

           super.paintComponent(g);

 

           if (worker.isDone() && (loopslot > -1) && (loopslot < nimgs)) {

              if (imgs != null && imgs[loopslot] != null) {

                  imgs[loopslot].paintIcon(this, g, off, 0);

              }

           }

       }

    }

 

    // Handle timer event. Update the loopslot (frame number) and the

    // offset. If it's the last frame, restart the timer to get a long

    // pause between loops.

    public void actionPerformed(ActionEvent e) {

       // If still loading, can't animate.

       if (!worker.isDone()) {

           return;

       }

 

       loopslot++;

 

       if (loopslot >= nimgs) {

           loopslot = 0;

           off += offset;

 

           if (off < 0) {

              off = width - maxWidth;

           } else if (off + maxWidth > width) {

              off = 0;

           }

       }

 

       animator.repaint();

 

       if (loopslot == nimgs - 1) {

           timer.restart();

       }

    }

 

    public void start() {

       if (worker.isDone() && (nimgs > 1)) {

           timer.restart();

       }

    }

 

    public void stop() {

       timer.stop();

    }

 

    /**

     * Load the image for the specified frame of animation. Since this runs as

     * an applet, we use getResourceAsStream for efficiency and so it'll work in

     * older versions of Java Plug-in.

     */

    protected ImageIcon loadImage(int imageNum) {

       String path = dir + "/T" + imageNum + ".gif";

       int MAX_IMAGE_SIZE = 2400; // Change this to the size of

       // your biggest image, in bytes.

       int count = 0;

       BufferedInputStream imgStream = new BufferedInputStream(this.getClass()

              .getResourceAsStream(path));

       if (imgStream != null) {

           byte buf[] = new byte[MAX_IMAGE_SIZE];

           try {

              count = imgStream.read(buf);

              imgStream.close();

           } catch (java.io.IOException ioe) {

              System.err.println("Couldn't read stream from file: " + path);

              return null;

           }

           if (count <= 0) {

              System.err.println("Empty file: " + path);

              return null;

           }

           return new ImageIcon(Toolkit.getDefaultToolkit().createImage(buf));

       } else {

           System.err.println("Couldn't find file: " + path);

           return null;

       }

    }

 

    public String getAppletInfo() {

       return "Title: TumbleItem v1.2, 23 Jul 1997/n"

              + "Author: James Gosling/n"

              + "A simple Item class to play an image loop.";

    }

 

    public String[][] getParameterInfo() {

       String[][] info = {

              { "img", "string",

                     "the directory containing the images to loop" },

              { "pause", "int",

                     "pause between complete loops; default is 3900" },

              {

                     "offset",

                     "int",

                     "offset of each image to simulate left (-) or "

                            + "right (+) motion; default is 0 (no motion)" },

              {

                     "speed",

                     "int",

                     "the speed at which the frames are looped; "

                            + "default is 100" },

              { "nimgs", "int",

                     "the number of images to be looped; default is 16" },

              {

                     "maxwidth",

                     "int",

                     "the maximum width of any image in the loop; "

                            + "default is 0" } };

       return info;

    }

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章