Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
265 views
in Technique[技术] by (71.8m points)

java - Swing application initialization and loading screen approach

I have made quite a lot of various Swing apps and their loading time usually vary between just a few seconds and minutes depending on application UI/data size. Also in some cases application data loading is mixed with UI loading.

A few seconds load time is not an issue, but when it is longer than let's say 10 seconds - it is obvious that some kind of loading screen should be displayed until UI/data is fully initialized.

What you would usually do - create some kind of loading screen first (for example window with logo and some label that is being updated while application is loading) and update it from various loading "points" in your application.

The problem is - application is usually loaded within single call queued into EDT and it is hard to separate it into multiply calls to EDT without complicating the application's code. So since application loading is performed in a single call queued to EDT you simply cannot update the loading screen properly - update won't be displayed until the application is initialized since the EDT is busy with loading the application.

So to implement loading screen in some cases I have moved application UI initialization outside of the EDT and have designed them in the way that it won't do any UI update stuff while loading is performed. Application's frame display and all application UI actions would still be performed in EDT. This is not too good generally, but after lots of testing and looking through the Swing code I know for sure that it doesn't cause any issues even on large applications. Still, that isn't a good to do generally even if it doesn't cause any issues.

So the question is: What approaches can be used to display and update application loading screen properly while keeping application initialization in EDT?

Hopefully it isn't too broad.

Here is a "dummy" application that showcases a "bad" approach:

import javax.swing.*;
import java.awt.*;

public class DummyApplication extends JFrame
{
    private static JDialog loadingDialog;
    private static JLabel loadingProgress;

    public DummyApplication ()
    {
        super ( "Dummy application" );

        dummyProgressUpdate ( "Loading content...", 3000 );

        final JLabel label = new JLabel ( "Custom content" );
        label.setBorder ( BorderFactory.createEmptyBorder ( 100, 100, 100, 100 ) );
        getContentPane ().add ( label );

        dummyProgressUpdate ( "Loading settings...", 3000 );

        setDefaultCloseOperation ( WindowConstants.EXIT_ON_CLOSE );
        pack ();
        setLocationRelativeTo ( null );

        dummyProgressUpdate ( "Opening application...", 1000 );
    }

    private static void dummyProgressUpdate ( final String status, final int time )
    {
        SwingUtilities.invokeLater ( () -> loadingProgress.setText ( status ) );
        dummyLoadTime ( time );
    }

    private static void dummyLoadTime ( final long time )
    {
        try
        {
            Thread.sleep ( time );
        }
        catch ( final InterruptedException e )
        {
            e.printStackTrace ();
        }
    }

    public static void main ( final String[] args ) throws Exception
    {
        // Displaying loading screen from EDT first
        SwingUtilities.invokeAndWait ( () -> {
            loadingDialog = new JDialog ( ( Window ) null, "Loading screen" );
            loadingProgress = new JLabel ( "Initializing application...", JLabel.CENTER );
            loadingProgress.setBorder ( BorderFactory.createLineBorder ( Color.LIGHT_GRAY ) );
            loadingDialog.getContentPane ().setLayout ( new BorderLayout () );
            loadingDialog.getContentPane ().add ( loadingProgress );
            loadingDialog.setUndecorated ( true );
            loadingDialog.setAlwaysOnTop ( true );
            loadingDialog.setModal ( false );
            loadingDialog.setSize ( 400, 100 );
            loadingDialog.setLocationRelativeTo ( null );
            loadingDialog.setVisible ( true );
        } );

        // Initializing application outside of the EDT
        final DummyApplication applicationFrame = new DummyApplication ();

        // Displaying application from the EDT
        SwingUtilities.invokeLater ( () -> {
            loadingDialog.setVisible ( false );
            applicationFrame.setVisible ( true );
        } );
    }
}

Also some time ago I found this interesting stuff implemented in JDK7:
http://sellmic.com/blog/2012/02/29/hidden-java-7-features-secondaryloop/

That SecondaryLoop feature allows to block futher code execution in EDT thread without causing the UI to stuck. It is basically the same what modal JDialog does when opened within EDT.

So with this feature I found probably a better way to solve the case:

import javax.swing.*;
import java.awt.*;

public class DummyApplication extends JFrame
{
    private static JDialog loadingDialog;
    private static JLabel loadingProgress;

    public DummyApplication ()
    {
        super ( "Dummy application" );

        dummyProgressUpdate ( "Loading content...", 3000 );

        final JLabel label = new JLabel ( "Custom content" );
        label.setBorder ( BorderFactory.createEmptyBorder ( 100, 100, 100, 100 ) );
        getContentPane ().add ( label );

        dummyProgressUpdate ( "Loading settings...", 3000 );

        setDefaultCloseOperation ( WindowConstants.EXIT_ON_CLOSE );
        pack ();
        setLocationRelativeTo ( null );

        dummyProgressUpdate ( "Displaying application...", 1000 );
    }

    private static void dummyProgressUpdate ( final String status, final int time )
    {
        // Use SecondaryLoop to block execution and force loading screen update
        final SecondaryLoop loop = Toolkit.getDefaultToolkit ().getSystemEventQueue ().createSecondaryLoop ();
        SwingUtilities.invokeLater ( () -> {
            loadingProgress.setText ( status );
            loop.exit ();
        } );
        loop.enter ();

        // Perform dummy heavy operation
        dummyLoadTime ( time );
    }

    private static void dummyLoadTime ( final long time )
    {
        try
        {
            Thread.sleep ( time );
        }
        catch ( final InterruptedException e )
        {
            e.printStackTrace ();
        }
    }

    public static void main ( final String[] args ) throws Exception
    {
        // Displaying loading screen from EDT first
        SwingUtilities.invokeAndWait ( () -> {
            loadingDialog = new JDialog ( ( Window ) null, "Loading screen" );
            loadingProgress = new JLabel ( "Initializing application...", JLabel.CENTER );
            loadingProgress.setBorder ( BorderFactory.createLineBorder ( Color.LIGHT_GRAY ) );
            loadingDialog.getContentPane ().setLayout ( new BorderLayout () );
            loadingDialog.getContentPane ().add ( loadingProgress );
            loadingDialog.setUndecorated ( true );
            loadingDialog.setAlwaysOnTop ( true );
            loadingDialog.setModal ( false );
            loadingDialog.setSize ( 400, 100 );
            loadingDialog.setLocationRelativeTo ( null );
            loadingDialog.setVisible ( true );
        } );

        // Initializing and displaying application from the EDT
        SwingUtilities.invokeLater ( () -> {
            final DummyApplication applicationFrame = new DummyApplication ();
            loadingDialog.setVisible ( false );
            applicationFrame.setVisible ( true );
        } );
    }
}

As you can see - in dummyProgressUpdate method I've done some tricky workaround for my case - basically I am blocking execution that performed in EDT and waiting for a separate call queued into EDT that would update loading screen.

And that actually works - though I am not sure that this is a good thing to do and whether it might cause any side-effects on a larger scale. And also the loading screen in that case will only be updated when that force update is made which means that if the loading screen has (for example) some animation running - it won't display properly and will only be updated together with text.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

If you're loading data or having to connect to remote services or other lengthy applications, then a SwingWorker is the simpler of the available approches, as it provides progress support via its setProgress method and PropertyChangeListener support.

It provides UI synchronisation via it's process/publish methods, allowing you to safely update the UI and the done method and/or the PropertyChangeListener can be used to determine when the worker is done

If all you're doing is trying to loading UI elements then you should consider a lazy loading approach.

So rather then loading ALL the UI elements at once, especially when the user may not actually look at sections of the UI, it is a much better idea to load those elements only when you absolutely have to and save yourself the time.

When using things like CardLayout or JTabbedPane, this is a much better to only initialise the UI and data of the views when they actually become visible.

If you really want, you could also have it unload when the view is no longer visible, disconnecting listeners from the data source, stopping it form responding to changes to the data source that the user will not actually see...


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...