I have a preloader for my application that handles the Application-specific initialization. Now I'm trying to extend this so that the preloader also shows the progress of the downloaded application JARs.
Why is the preloader not getting loaded during Phase 2, as this should handle the PreloaderFx::handleProgressNotification();
to track the downloading of the JARs I suppose?
Update 14 March 2016: Is using DownloadServiceListener the way to solve this? How to connect this to a JavaFX stage?
According to Oracle, there are 4 phases when an application gets launched:
Phase 2: Loading and preparation: The required resources are loaded from either the network or a disk cache, and validation procedures occur. All execution modes see the default or a custom preloader. During this phase, my custom preloader should be shown.
Phase 3: Application-specific initialization: The application is started, but it may need to load additional resources or perform other lengthy preparations before it becomes fully functional. At the moment, my custom preloader is shown:
The first thing I notice, is that in Phase 2, the default JavaFX preloader handling the downloading of the application JARs is not showing. Because of this, the user gets the feeling the program did not start or terminated prematurely, making them to open the JNLP file multiple times. Once the JARs are downloaded, we enter Phase 3 and the preloader is shown.
However, I'd like my custom preloader to handle the downloading progress in the ProgressBar as well (Phase 2). I made everything as simple as possible to track down which events are happening during the start-up of my application. This is based on an example of Jewelsea and on Oracle examples:
Preloader:
public class PreloaderFX extends Preloader { Stage stage; //boolean noLoadingProgress = true; public static final String APPLICATION_ICON = "http://cdn1.iconfinder.com/data/icons/Copenhagen/PNG/32/people.png"; public static final String SPLASH_IMAGE = "http://fxexperience.com/wp-content/uploads/2010/06/logo.png"; private Pane splashLayout; private ProgressBar loadProgress; private Label progressText; private static final int SPLASH_WIDTH = 676; private static final int SPLASH_HEIGHT = 227; @Override public void init() { ImageView splash = new ImageView(new Image( SPLASH_IMAGE )); loadProgress = new ProgressBar(); loadProgress.setPrefWidth(SPLASH_WIDTH - 20); progressText = new Label("Loading . . ."); splashLayout = new VBox(); splashLayout.getChildren().addAll(splash, loadProgress, progressText); progressText.setAlignment(Pos.CENTER); splashLayout.setStyle( "-fx-padding: 5; " + "-fx-background-color: white; " + "-fx-border-width:5; " ); splashLayout.setEffect(new DropShadow()); } @Override public void start(Stage stage) throws Exception { System.out.println("PreloaderFx::start();"); //this.stage = new Stage(StageStyle.DECORATED); stage.setTitle("Title"); stage.getIcons().add(new Image(APPLICATION_ICON)); stage.initStyle(StageStyle.UNDECORATED); final Rectangle2D bounds = Screen.getPrimary().getBounds(); stage.setScene(new Scene(splashLayout)); stage.setX(bounds.getMinX() + bounds.getWidth() / 2 - SPLASH_WIDTH / 2); stage.setY(bounds.getMinY() + bounds.getHeight() / 2 - SPLASH_HEIGHT / 2); stage.show(); this.stage = stage; } @Override public void handleProgressNotification(ProgressNotification pn) { System.out.println("PreloaderFx::handleProgressNotification(); progress = " + pn.getProgress()); //application loading progress is rescaled to be first 50% //Even if there is nothing to load 0% and 100% events can be // delivered if (pn.getProgress() != 1.0 /*|| !noLoadingProgress*/) { loadProgress.setProgress(pn.getProgress() / 2); /*if (pn.getProgress() > 0) { noLoadingProgress = false; }*/ } } @Override public void handleStateChangeNotification(StateChangeNotification evt) { //ignore, hide after application signals it is ready System.out.println("PreloaderFx::handleStateChangeNotification(); state = " + evt.getType()); } @Override public void handleApplicationNotification(PreloaderNotification pn) { if (pn instanceof ProgressNotification) { //expect application to send us progress notifications //with progress ranging from 0 to 1.0 double v = ((ProgressNotification) pn).getProgress(); System.out.println("PreloaderFx::handleApplicationNotification(); progress = " + v); //if (!noLoadingProgress) { //if we were receiving loading progress notifications //then progress is already at 50%. //Rescale application progress to start from 50% v = 0.5 + v / 2; //} loadProgress.setProgress(v); } else if (pn instanceof StateChangeNotification) { System.out.println("PreloaderFx::handleApplicationNotification(); state = " + ((StateChangeNotification) pn).getType()); //hide after get any state update from application stage.hide(); } } }
Code that is being handled in Phase 3 is from the main application who interacts with the preloader, this is what is being seen in the progressbar:
public class MainApp extends Application { BooleanProperty ready = new SimpleBooleanProperty(false); public static void main(String[] args) throws Exception { launch(args); } @Override public void start(final Stage initStage) throws Exception { System.out.println("MainApp::start();"); this.mainStage = initStage; longStart(); ready.addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> { if (Boolean.TRUE.equals(t1)) { Platform.runLater(() -> { System.out.println("MainApp::showMainStage();"); showMainStage(); }); } }); } private void longStart() { //simulate long init in background Task task = new Task<Void>() { @Override protected Void call() throws Exception { int max = 10; for (int i = 1; i <= max; i++) { Thread.sleep(500); System.out.println("longStart " + i); // Send progress to preloader notifyPreloader(new ProgressNotification(((double) i)/max)); //this moves the progress bar of the preloader } // After init is ready, the app is ready to be shown // Do this before hiding the preloader stage to prevent the // app from exiting prematurely ready.setValue(Boolean.TRUE); notifyPreloader(new StateChangeNotification( StateChangeNotification.Type.BEFORE_START)); return null; } }; new Thread(task).start(); } private void showMainStage() { //showing the login window } }
JNLP
<jnlp spec="1.0+" xmlns:jfx="http://javafx.com" codebase="<***>/preloadertest/jnlp" href="launch.jnlp"> <information> ... </information> <resources> <j2se version="1.6+" href="http://java.sun.com/products/autodl/j2se" /> ... //whole bunch of JARS <jar href="lib/preloader-1.1.1.jar" download="progress" /> </resources> <security> <all-permissions/> </security> <applet-desc width="1024" height="768" main-class="com.javafx.main.NoJavaFXFallback" name="JavaFX Client"> <param name="requiredFXVersion" value="8.0+"/> </applet-desc> <jfx:javafx-desc width="1024" height="768" main-class="GUI.MainApp" name="JavaFX Client" preloader-class="GUI.PreloaderFX" /> <update check="background"/> </jnlp>
I closely watched the Java Console when launching the file (with Show logging enabled, Show tracing disabled) and noticed following things:
During Phase 2, nothing shows up in the Java Console (console closes after this phase)
During Phase 3, following output is generated (in a new console window):
PreloaderFx::start(); PreloaderFx::handleProgressNotification(); progress = 1.0 PreloaderFx::handleStateChangeNotification(); state = BEFORE_LOAD PreloaderFx::handleStateChangeNotification(); state = BEFORE_INIT PreloaderFx::handleStateChangeNotification(); state = BEFORE_START MainApp::start(); MainApp::longstart(); longStart 1 PreloaderFx::handleApplicationNotification(); progress = 0.1 longStart 2 PreloaderFx::handleApplicationNotification(); progress = 0.2 longStart 3 PreloaderFx::handleApplicationNotification(); progress = 0.3 longStart 4 PreloaderFx::handleApplicationNotification(); progress = 0.4 longStart 5 PreloaderFx::handleApplicationNotification(); progress = 0.5 longStart 6 PreloaderFx::handleApplicationNotification(); progress = 0.6 longStart 7 PreloaderFx::handleApplicationNotification(); progress = 0.7 longStart 8 PreloaderFx::handleApplicationNotification(); progress = 0.8 longStart 9 PreloaderFx::handleApplicationNotification(); progress = 0.9 longStart 10 PreloaderFx::handleApplicationNotification(); progress = 1.0 MainApp::showMainStage(); PreloaderFx::handleApplicationNotification(); state = BEFORE_START
Updates 13 March 2016:
noLoadingProgress
boolean (suggested by nhylated)System.out.println()
in the MainAppSimple adding <jfx:javafx-runtime version="8.0+"/>
to the JNLP file fixed it. With that line added, the preloader shows in Phase 2. I also took the liberty to change the j2se version="1.6+"
to j2se version="1.8+"
The result:
The first 50% is the handling of the JAR downloads. This is done by the handleProgressNotification()
method. The second 50% is the actual initializing of the MainApp (longstart()
which notifies the preloader), done by the handleApplicationNotification()
.
Oracle has announced that Java Applet and WebStart functionality, including the Applet API, The Java plug-in, the Java Applet Viewer, JNLP and Java Web Start (containing the javaws tool) are all deprecated in JDK 9 and will be removed in a future release.
Open Firefox and select 'Options'. 3. Under the 'General' category, scroll down to the 'Applications' section. Click the drop down menu for 'JNLP files' and select 'Use Java Web Start Launcher'.
Because JNLP files are program-specific, your computer may not properly recognize JNLP files to execute them using the Java Web Start application. If this happens, you need to modify your computer's file associations so that JNLP files open correctly with the Java Web Start application.
I have been fighting with this too, recently. I switched back to the (ugly) default preloader (as that one shows up nicely) until I find some more time to investigate this.
If you enable Java Webstart full tracing
"<JAVA_HOME>\bin\javaws.exe" -userConfig deployment.trace true "<JAVA_HOME>\bin\javaws.exe" -userConfig deployment.trace.level all
you should see preloader messages which should give you some information about what is going on. In my case I could see a lot of messages like these
preloader: Added pending event 2: DownloadEvent[type=load,loaded=0, total=62791, percent=0]
indicating that the custom preloader hasn't been verified/started yet but download events were already coming in.
What happens if you switch <update check="background"/>
to <update check="always"/>
?
This is my test JNLP. Seems like you're missing to specify the JavaFX runtime resource?
<?xml version="1.0" encoding="utf-8"?> <jnlp spec="1.0+" xmlns:jfx="http://javafx.com" codebase="http://localhost:8080/HelloWorldFX" href="HelloWorldFX.jnlp"> <information> <title>HelloWorldFX</title> <vendor>Unknown</vendor> <description>HelloWorldFX</description> <offline-allowed/> </information> <resources os="Windows"> <jfx:javafx-runtime version="8.0+"/> </resources> <resources> <j2se version="1.8+" href="http://java.sun.com/products/autodl/j2se"/> <jar href="HelloWorldPreloader.jar" size="10774" download="progress" /> <jar href="HelloWorldFX.jar" size="248884114" download="eager" main="true" /> </resources> <jfx:javafx-desc width="600" height="400" main-class="sample.Main" name="HelloWorldFX" preloader-class="HelloWorldPreloader"/> <update check="always"/> </jnlp>
Note: I haven't tested or executed the code; my answer is primarily based on looking at the code. I haven't worked on JavaFX but I can grasp the code since I've worked on Swing and JNLP before.
The first thing I notice, is that in Phase 2, the default JavaFX preloader handling the downloading of the application JARs is not showing.
This seems to be because of the PreloaderFX.start(stage)
method. The stage
passed as the method's argument remains empty because the method constructs a new Stage()
and adds children to it. If you add the child elements to the method's argument, it should display your custom preloader/progressbar during phase 2.
If the code still doesn't work as expected after this, I'd try debugging all the logic/code associated with the noLoadingProgress
flag. Try commenting all that out (essentially taking noLoadingProgress
out of the picture) and see if it fixes the issue.
Also, even though you've got this right in your code, see this answer - according to which all ProgressNotification
s are handled by the handleApplicationNotification
method.
Hope this helps!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With