Splash screen

Splash screens are simple graphics which load quickly upon startup, and assure the user that the application is loading promptly. The SplashScreen class was added to JSE 6, and should be used if available.

If JSE 6 is not available, then the following technique is provided as an alternative.

Various policies for controlling the appearance and disappearance of the splash screen are possible. Here, the splash screen disappears when a main window has finished loading.

This example is also of interest since it demonstrates clearly that the launch thread is unusual - it's distinct from the event dispatch thread, where most of the work of a Swing application takes place:


package hirondelle.stocks.main;

import java.awt.*;
import java.net.URL;
import java.awt.image.ImageObserver;

/**
* Present a simple graphic to the user upon launch of the application, to 
* provide a faster initial response than is possible with the main window.
* 
* <P>Adapted from an 
* <a href=http://developer.java.sun.com/developer/qow/archive/24/index.html>item</a> 
* on Sun's Java Developer Connection.
*
* <P>This splash screen appears within about 2.5 seconds on a development 
* machine. The main screen takes about 6.0 seconds to load, so use of a splash 
* screen cuts down the initial display delay by about 55 percent.
* 
* <P>When JDK 6+ is available, its java.awt.SplashScreen class should be used instead 
* of this class.
*/
final class SplashScreen extends Frame {

  /**
  * Construct using an image for the splash screen.
  *  
  * @param aImageId must have content, and is used by  
  * {@link Class#getResource(java.lang.String)} to retrieve the splash screen image.
  */
  SplashScreen(String aImageId) {
    /* 
    * Implementation Note
    * Args.checkForContent is not called here, in an attempt to minimize 
    * class loading.
    */
    if (aImageId == null || aImageId.trim().length() == 0){
      throw new IllegalArgumentException("Image Id does not have content.");
    }
    fImageId = aImageId;
  }
   
  /**
  * Show the splash screen to the end user.
  *
  * <P>Once this method returns, the splash screen is realized, which means 
  * that almost all work on the splash screen should proceed through the event 
  * dispatch thread. In particular, any call to <tt>dispose</tt> for the 
  * splash screen must be performed in the event dispatch thread.
  */
  void splash(){
    initImageAndTracker();
    setSize(fImage.getWidth(NO_OBSERVER), fImage.getHeight(NO_OBSERVER));
    center();
    fMediaTracker.addImage(fImage, IMAGE_ID);
    try {
      fMediaTracker.waitForID(IMAGE_ID);
    }
    catch(InterruptedException ex){
      System.out.println("Cannot track image load.");
    }
    SplashWindow splashWindow = new SplashWindow(this,fImage);
  }
  
  // PRIVATE
  private final String fImageId;
  private MediaTracker fMediaTracker;
  private Image fImage;
  private static final ImageObserver NO_OBSERVER = null; 
  private static final int IMAGE_ID = 0;

  private void initImageAndTracker(){
    fMediaTracker = new MediaTracker(this);
    URL imageURL = SplashScreen.class.getResource(fImageId);
    fImage = Toolkit.getDefaultToolkit().getImage(imageURL);
  }

  /**
  * Centers the frame on the screen.
  *
  *<P>This centering service is more or less in {@link hirondelle.stocks.util.ui.UiUtil}; 
  * this duplication is justified only because the use of  
  * {@link hirondelle.stocks.util.ui.UiUtil} would entail more class loading, which is 
  * not desirable for a splash screen.
  */
  private void center(){
    Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
    Rectangle frame = getBounds();
    setLocation((screen.width - frame.width)/2, (screen.height - frame.height)/2);
  }
 
  private final class SplashWindow extends Window {
    SplashWindow(Frame aParent, Image aImage) {
       super(aParent);
       fImage = aImage;
       setSize(fImage.getWidth(NO_OBSERVER), fImage.getHeight(NO_OBSERVER));
       Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
       Rectangle window = getBounds();
       setLocation((screen.width - window.width) / 2,(screen.height - window.height)/2);
       setVisible(true);
    }
    @Override public void paint(Graphics graphics) {
      if (fImage != null) {
        graphics.drawImage(fImage,0,0,this);
      }
    }
    private Image fImage;
  }
  
  /**
  * Developer test harness shows the splash screen for a fixed length of 
  * time, without launching the full application.
  */
  private static void main(String... aArgs){
    SplashScreen splashScreen = new SplashScreen("StocksMonitor.gif");
    splashScreen.splash();
    try {
      Thread.sleep(2000);
    }
    catch(InterruptedException ex) {
      System.out.println(ex);
    }
    System.exit(0);
  }
}
 

Here's an example of a class which launches an application using the above SplashScreen:
package hirondelle.stocks.main;

import java.util.logging.*;
import java.awt.EventQueue;
import hirondelle.stocks.util.Util;
import hirondelle.stocks.util.Consts;

/**
* Launch the application using an older version of a splash screen.
*
*<P>Perform tasks in this order :
*<ul>
* <li>log basic system information 
* <li>promptly show a splash screen upon startup
* <li>show the main screen
* <li>remove the splash screen once the main screen is shown
*</ul>
*
* These tasks are performed in a thread-safe manner.
*/
public final class Launcher { 

  /**
  * Launch the application and display the main window.
  *
  * @param aArgs are ignored by this application, and may take any value.
  */
  public static void main (String... aArgs) {
    
    /*
    * Implementation Note:
    *
    * Note that the launch thread of any GUI application is in effect an initial 
    * worker thread - it is not the event dispatch thread, where the bulk of processing
    * takes place. Thus, once the launch thread realizes a window, then the launch 
    * thread should almost always manipulate such a window through 
    * EventQueue.invokeLater. (This is done for closing the splash 
    * screen, for example.)
    */
    
    //verifies that assertions are on:
    //  assert(false) : "Test";
    
    logBasicSystemInfo();
    showSplashScreen();
    showMainWindow();
    EventQueue.invokeLater(new SplashScreenCloser());
    fLogger.info("Launch thread now exiting...");
  }

  // PRIVATE 
  
  private static SplashScreen fSplashScreen;
  private static final Logger fLogger = Util.getLogger(Launcher.class);
  private static final String SPLASH_IMAGE = "StocksMonitor.gif";

  /**
  * Show a simple graphical splash screen, as a quick preliminary to the main screen.
  */
  private static void showSplashScreen(){
    fLogger.info("Showing the splash screen.");
    fSplashScreen = new SplashScreen(SPLASH_IMAGE);
    fSplashScreen.splash();
  }
  
  /**
  * Display the main window of the application to the user.
  */
  private static void showMainWindow(){
    fLogger.info("Showing the main window.");
    StocksMonitorMainWindow mainWindow = new StocksMonitorMainWindow();
  }

  /** 
  * Removes the splash screen. 
  *
  * Invoke this <tt>Runnable</tt> using 
  * <tt>EventQueue.invokeLater</tt>, in order to remove the splash screen
  * in a thread-safe manner.
  */
  private static final class SplashScreenCloser implements Runnable {
    @Override public void run(){
      fLogger.fine("Closing the splash screen.'");
      fSplashScreen.dispose();
    }
  }
  
  private static void logBasicSystemInfo() {
    fLogger.info("Launching the application...");
    fLogger.config(
      "Operating System: " + System.getProperty("os.name") + " " + 
      System.getProperty("os.version")
    );
    fLogger.config("JRE: " + System.getProperty("java.version"));
    fLogger.info("Java Launched From: " + System.getProperty("java.home"));
    fLogger.config("Class Path: " + System.getProperty("java.class.path"));
    fLogger.config("Library Path: " + System.getProperty("java.library.path"));
    fLogger.config("Application Name: " + Consts.APP_NAME + "/" + Consts.APP_VERSION);
    fLogger.config("User Home Directory: " + System.getProperty("user.home"));
    fLogger.config("User Working Directory: " + System.getProperty("user.dir"));
    fLogger.info("Test INFO logging.");
    fLogger.fine("Test FINE logging.");
    fLogger.finest("Test FINEST logging.");
  }
} 

See Also :
Swing threads