McWalter.org :: Java splashscreen application


When a large java program starts the JVM often has to load and verify hundreds of classes, often taking a considerable period of time. To avoid users from mistaking this for a problem, many java applications display a simple graphic on the screen while the application is loading.

Doing this in java is a little tricky, as the code that displays the graphic must not contain a direct reference to the subsequent classes, as the splash screen code will not complete loading (and thus cannot run) until any classes it may be dependant upon are loaded.

This program functions as a simple wrapper application - it can be called with the name of another application as a parameter, and it displays a splash graphic before loading and launching said target application. No alteration (or recompilation) to the target application of the target application is needed.

Technical notes

This application is illustrative of two parts of the java api:

Demo archive

Here's a zip archive with the Splash.java source, the source of a tiny test application (which just prints its command line arguments), compiled .class files for both, a splash picture (not a very suitable one), and a batch file to run the splash demo. You'll probably need to change the path to the JDK to get this to work on your system. The zip file is here.

Splash.java sourcecode

/**
 *   Splash.java
 *
 *   A simple splashscreen application.
 *  
 *   When a large java program starts the JVM often has to load and verify
 *   hundreds of classes, often taking a considerable period of time.  To
 *   avoid users from mistaking this for a problem, many java applications
 *   display a simple graphic on the screen while the application is loading.
 *
 *   Doing this in java is a little tricky, as the code that displays the
 *   graphic must not contain a direct reference to the subsequent classes,
 *   as the splash screen code will not complete loading (and thus cannot
 *   run) until any classes it may be dependant upon are loaded.  
 *
 *   This program functions as a simple wrapper application - it can be
 *   called with the name of another application as a parameter, and it
 *   displays a splash graphic before loading and launching said target
 *   application.  No alteration (or recompilation) to the target application 
 *   of the target application is needed.
 *
 *   Calling:
 *      java Splash imgname.jpg targetClass arg0 arg1 arg2 ...
 *
 *                    |           |           |    |    |
 *                    |           |           subsequent arguments are
 *                    |           |           passed to the targe program
 *                    |           |
 *                    |           this is the name of the class (it must
 *                    |           be on the classpath) which the splash program
 *                    |           should load once it has displayed the
 *                    |           splash image.  This must contain the main
 *                    |           method of the target program.
 *                    |
 *                    this is the name of an imagefile which the splash
 *                    program should display on the screen while targetClass
 *                    is loaded.
 *
 *   Properties:
 *     The system property "Splash.delaytime" is used to specify a time, in
 *     seconds, for which the splash program waits between classloading the
 *     target and actually starting it.  This is mostly used for debug and demo
 *     purposes, as typically small java programs classload so quickly that
 *     they don't need a splashscreen.  By default (and in most practical
 *     use) this property is 0, meaning no extra delay is inserted.
 *
 *   Technical notes:
 *     This application is illustrative of two parts of the java api:
 *
 *     1. The use of a mediatracker to wait for an image to be loaded.
 *        In this case this is done asynchronously (we spawn a new thread
 *        in which the mediatracker waits) to allow classloading to
 *        operate concurrently with image loading.  For most simple
 *        applications a mediatracker proves to be greatly simpler
 *        than implementing an imageobserver to watch the image load.
 *
 *     2. Unlike many other OO frameworks, java chooses not to have
 *        applications be concrete subclasses of an abstract Application
 *        type.  It's hard to see why this was thought to be a good idea
 *        (perhaps some bizarre attempt at symmetry with C).  This proves
 *        to be a considerable complication when one program loads and
 *        runs another - it's necessary to do rather a lot of legwork
 *        using the java reflection API to be able to call the main()
 *        method of the target.  Had java applications been instances
 *        of a class or interface then one would need only to downcast
 *        the application class to that base class and one could call
 *        main without using reflection (this, for example, is how
 *        applets work).  The great bulk of Splash.main() is thus 
 *        consumed with doing the requisite reflection calls to
 *        call its counterpart - readers might find this an entertaining
 *        introduction to the reflection API.
 *
 *  Copyright (c) 2002 W.Finlay McWalter.  Licenced under version 2.0
 *  of the GNU General Public Licence.
 */

import java.awt.*;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;

public class Splash extends Frame {
    static Image   splashImage;
    static boolean imageLoaded = false;

    static class AsyncImageLoader implements Runnable {
        String  imageFileName;
        Thread  loaderThread;
        Splash  parentFrame;

        public AsyncImageLoader(Splash parent, String fileName){
            parentFrame   = parent;
            imageFileName = fileName;
            loaderThread  = new Thread(this);
            loaderThread.start();
        }
        
        public void run() {
            // start loading the image
            splashImage = Toolkit.getDefaultToolkit().createImage(imageFileName);
            
            // wait for image to be loaded
            MediaTracker tracker = new MediaTracker(parentFrame);
            tracker.addImage(splashImage,0);
            try {
                tracker.waitForID(0);
            }
            catch(InterruptedException e){
                e.printStackTrace();
                System.exit(1);
            }
                    
            // check to ensure the image loaded okay. It would be nice to give
            // a more specific error message here, but the Image load/observe
            // API doesn't give us further details.
            if(tracker.isErrorID(0)){
                System.err.println("splashloader: error loading image \"" +
                                   imageFileName +
                                   "\"");

                // this isn't a fatal error - the target class should be able
                // to load.
                return;
            }

            // resize frame to match size of image, and keep frame at centre of screen
            parentFrame.positionAtCenter(splashImage.getWidth(null), 
                                         splashImage.getHeight(null));

            // signal a redraw, so the image can be displayed
            imageLoaded = true;
            parentFrame.repaint();
        }
    } /* end of static inner class AsyncImageLoader */
    
    /**
     *  Positions the window at the centre of the screen, taking into account
     *  the specified width and height
     */
    private void positionAtCenter(int width, int height){
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        setBounds((screenSize.width-width)/2,
                  (screenSize.height-height)/2,
                  width,
                  height);
    }

    public void paint(Graphics g){
        if(imageLoaded){
            g.drawImage(splashImage,0,0,null);
        }
    }

    public void update(Graphics g){
        paint(g);
    }

    public static final void main(String [] args){
        // create a splash window, in which we'll display an image
        Splash f = new Splash();
        f.setUndecorated(true);
        f.positionAtCenter(1,1);
        f.show();
        
        // start loading the image, asynchronously      
        AsyncImageLoader loader = new AsyncImageLoader(f, args[0]);
    
        try {
            // load the target class, and get a reference to its main method
            Class [] mainArgs = new Class[1];
            mainArgs[0] = Class.forName("[Ljava.lang.String;");
            Method mainMethod = Class.forName(args[1]).getMethod("main", mainArgs);

            // verify that the main method is static
            if((mainMethod.getModifiers() & Modifier.STATIC)==0){
                System.err.println("splashloader: main method in target class is not static");
                System.exit(-1);
            }
            
            // verify that the main method returns a void
            if(mainMethod.getReturnType() != void.class){
                System.err.println("splashloader: target class main must return void");
                System.exit(-1);
            }

            // wait for a time (specified in seconds by property "Splash.delaytime") and
            // then remove the splashscreen window.  If the property isn't specified,
            // then there is no delay.  This delay is mostly used for debugging.
            Thread.sleep(1000 * (Integer.getInteger("Splash.delaytime",0)).intValue()); 
            f.hide();
            
            // we need to make a copy of our args, with the first two (which were
            // intended for the splashloader) stripped off
            String [] processedArgs = new String[args.length-2];
            System.arraycopy(args,2,processedArgs,0,args.length-2);

            // call the target class main method
            Object[] actualArgs = new Object[1];
            actualArgs[0] = (Object)processedArgs;
            mainMethod.invoke(null,actualArgs);
        }
        catch(ClassNotFoundException e){
            System.err.println("splashloader: can't find class \"" + 
                               e.getMessage() +
                               "\" when loading target class \"" + 
                               args[1]
                               +"\"");
            System.exit(-1);
        }
        catch(NoSuchMethodException e){
            System.err.println("splashloader: no main(String[]) method found in class " + args[1]);
            System.exit(-1);
        }
        catch(IllegalAccessException e){
            System.err.println("splashloader: error calling main method in class " + args[1]);
            System.exit(-1);
        }
        catch(InvocationTargetException e){
            System.err.println("splashloader: error calling main method in class " + args[1]);
            System.exit(-1);
        }
        catch(InterruptedException e){}
    }
}