Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle java web start (jnlp) downloading progress in a preloader?

Issue

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.


TL;DR

  • 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?


Documentation

According to Oracle, there are 4 phases when an application gets launched:

  • Phase 1: Initialization: Initialization of Java Runtime and an initial examination identifies components that must be loaded and executed before starting the application. During this phase, a splash screen is shown. Default this is this:

Java starts

  • 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:

preloader

  • Phase 4: Application execution: The application is displayed and is ready to use. In my case, a login window is shown and the user can proceed.

login


My Case

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> 

Debugging

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:

  • Adjusted the code so the stage passed in the method is used rather than creating a new one and commented out everything related to the noLoadingProgress boolean (suggested by nhylated)
  • Added some extra System.out.println() in the MainApp

Solution

Simple 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:

Preloader 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().

like image 255
Perneel Avatar asked Mar 07 '16 17:03

Perneel


People also ask

Is JNLP still supported?

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.

How do I associate JNLP with Java Web Start?

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'.

How do I fix a JNLP file?

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.


2 Answers

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"/>?

EDIT

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> 
like image 108
jansohn Avatar answered Sep 22 '22 22:09

jansohn


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 ProgressNotifications are handled by the handleApplicationNotification method.

Hope this helps!

like image 26
nhylated Avatar answered Sep 22 '22 22:09

nhylated