user3787028 user3787028 - 29 days ago 13
Java Question

Java splash screen not working on Mac OSX but working on a PC

the code below is the implementation of a splash screen for a small Java application I have developed with Eclipse. The splash screen works perfectly well on a PC but not on a MAC. On MAC OSX, the frame first appears has a gray area during 2 seconds and then the image appears for the remaining of the 4 seconds. The image should normally appears right away and for a duration of 4 seconds. Do you have any idea why there is a delay before the image appears on a MAC while everything works well on a PC? PS: I have deployed the application as an executable Jar and I'm using Java 8 on all computers. Thank you.

public static void main(String[] args)
{
SplashScreen splSplashScreen = new SplashScreen();
//Main window
FenetrePrincipale fenetrePrincipale = new FenetrePrincipale();
}
public class SplashScreen extends JWindow
{
/**
* Numéro de série
*/
private static final long serialVersionUID = 1592663893301307318L;

private final static long TEMP_AFFICHAGE = 4000;

/**
* Constructeur par initialisation
* @param p_Frame Frame
* @param p_TempsAffichage Temps d'affichage en millisecondes
*/
public SplashScreen()
{
super(new Frame());
JLabel lblImage = new JLabel(new ImageIcon(this.getClass().getResource("/res/ui/splashScreen.jpg")));

Container container = this.getContentPane();
container.add(lblImage, BorderLayout.CENTER);
pack();

Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Dimension labelSize = lblImage.getPreferredSize();
this.setLocation(screenSize.width/2 - (labelSize.width/2), screenSize.height/2 - (labelSize.height/2));

this.setVisible(true);

try
{
Thread.sleep(TEMP_AFFICHAGE);
}
catch (InterruptedException ex)
{
ApplicationLogger.getInstance().severe(ex.getLocalizedMessage());
}
finally
{
this.setVisible(false);
}
}
}

Answer

Edit This is completely different from the original answer; I'm leaving the original answer below.

It looks like the initial JLabel render is dog slow on OSX - removing all the sleeps still leaves me with a ~2 second pause while java renders the label. So we change the rules.

First we create a JPanel class that takes a buffered image:

class ImgPanel extends JPanel {
    private BufferedImage img;

    public ImgPanel(BufferedImage img) {
        this.img = img;
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(img.getWidth(), img.getHeight());
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(img, 0, 0, null);
    }
}

We then change the splash screen constructor like so:

public SplashScreen()
{
    BufferedImage img = null;
    try {
        img = ImageIO.read(this.getClass().getResource("/res/ui/splashScreen.jpg"));
    } catch (Exception ex) {
    }

    ImgPanel panel = new ImgPanel(img);
    Container container = this.getContentPane();
    container.add(panel);
    pack();

    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    this.setLocation(screenSize.width/2 - (img.getWidth()/2), screenSize.height/2 - (img.getHeight()/2));

    this.setVisible(true);
 }

This renders the image as soon as the panel appears without a pause.

Note - I've removed the entire sleep code to reduce confusion - the logic would be better captured in a separate SwingWorker to perform the hide of the splash screen and the display of the main screen:

class worker extends SwingWorker<String, Object> {
    private final static long TEMP_AFFICHAGE = 4000;
    private SplashScreen splash;
    private FenetrePrincipale principale;

    public worker(SplashScreen splash, FenetrePrincipale principale) {
        this.splash = splash;
        this.principale = principale;
        this.splash.setVisible(true);
    }

    @Override
    public String doInBackground() {
        try
        {
            Thread.sleep(TEMP_AFFICHAGE);
        }
        catch (InterruptedException ex)
        {
            ApplicationLogger.getInstance().severe(ex.getLocalizedMessage());
        }
        return "";
    }

    @Override
    protected void done() {
        splash.setVisible(false);
        principale.setVisible(true);
    }
};

Then the main code looks like:

public static void main(String[] args)
{
    SplashScreen splSplashScreen = new SplashScreen();
    //Main window
    FenetrePrincipale fenetrePrincipale = new FenetrePrincipale();
    worker w = new worker(splSplashScreen, fenetrePrincipale);
    w.execute();
}

Original answer - putting the thread for sleeping into a SwingWorker is still a good idea, as it would allow you to perform actual work before initialization.

Ok, a simple example of putting the sleep off the gui thread, using your splash code - this code comes after the this.setVisible(true), and replaces the try {} catch {} finally {} clause:

    this.setVisible(true);

    worker do_work = new worker(this);
    do_work.execute();
}

class worker extends SwingWorker<String, Object> {
    private SplashScreen parent;

    public worker(SplashScreen parent) {
        this.parent = parent;
    }

    @Override
    public String doInBackground() {
        try
        {
            Thread.sleep(TEMP_AFFICHAGE);
        }
        catch (InterruptedException ex)
        {
            ApplicationLogger.getInstance().severe(ex.getLocalizedMessage());
        }
        return "";
    }

    @Override
    protected void done() {
        parent.setVisible(false);
    }
};

I just create a SwingWorker which does a sleep as doWorkInBackground, and once that is concluded, it closes the parent frame, which is the splash screen.

Comments