如何在预加载器中处理java web start(jnlp)下载进度?

问题

我的应用程序的预加载器处理特定于应用程序的初始化。 现在我正在尝试扩展它,以便预加载器还显示下载的应用程序JAR的进度。


TL; DR

  • 为什么预加载器在阶段2期间没有加载,因为这应该处理PreloaderFx::handleProgressNotification(); 我想跟踪JAR的下载?

  • 2016年3月14日更新 :使用DownloadServiceListener解决这个问题的方法是什么? 如何将其连接到JavaFX阶段?


文档

根据Oracle的说法 ,启动应用程序有4个阶段:

  • 阶段1:初始化 :Java Runtime的初始化和初始检查确定在启动应用程序之前必须加载和执行的组件。 在此阶段,将显示启动画面。 默认是这样的:

Java开始了

  • 阶段2:加载和准备 :从网络或磁盘缓存加载所需的资源,并进行validation过程。 所有执行模式都会看到默认或自定义预加载器。 在此阶段,应显示我的自定义预加载器。

  • 阶段3:特定于应用程序的初始化 :应用程序已启动,但可能需要在其完全正常运行之前加载其他资源或执行其他冗长的准备工作。 目前,我的自定义预加载器显示如下:

预载

  • 阶段4:应用程序执行 :显示应用程序并准备使用。 在我的例子中,显示了一个登录窗口,用户可以继续。

登录


我的情况

我注意到的第一件事是,在阶段2中 ,没有显示处理应用程序JAR下载的默认JavaFX预加载器。 因此,用户感觉程序没有提前启动或终止,使他们多次打开JNLP文件。 下载JAR后,我们进入阶段3并显示预加载器。

但是,我希望我的自定义预加载器能够处理ProgressBar中的下载进度(阶段2)。 我使一切尽可能简单,以便在应用程序启动期间跟踪发生的事件。 这是基于Jewelsea和Oracle示例的示例 :

预载:

 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(); } } } 

阶段3中正在处理的代码来自与预加载器交互的主应用程序,这是进度条中显示的内容:

 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 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() { @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">  ...    ... //whole bunch of JARS            

调试

我在启动文件时密切关注Java控制台(启用了显示日志记录,禁用了显示跟踪)并注意到以下事项:

阶段2期间,Java控制台中没有任何内容显示(控制台在此阶段后关闭)

阶段3期间,生成以下输出(在新的控制台窗口中):

 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 

更新2016年3月13日:

  • 调整代码,以便使用方法中传递的阶段而不是创建新的代码并注释掉与noLoadingProgress布尔相关的所有内容(由nhylated建议)
  • 在MainApp中添加了一些额外的System.out.println()

简单地将到JNLP文件中修复它。 添加该行后,预加载器显示在第2阶段。我也冒昧地将j2se version="1.6+"更改为j2se version="1.8+"结果:

预加载器结果

前50%是JAR下载的处理。 这是通过handleProgressNotification()方法完成的。 第二个50%是实际初始化MainApp( longstart()通知预加载器),由handleApplicationNotification()

最近我也一直在与这个斗争。 我切换回(丑陋的)默认预加载器(因为那个很好地显示),直到我找到更多的时间来调查这个。

如果启用Java Webstart完整跟踪

 "\bin\javaws.exe" -userConfig deployment.trace true "\bin\javaws.exe" -userConfig deployment.trace.level all 

您应该看到预加载器消息,它应该为您提供有关正在发生的事情的一些信息。 在我的情况下,我可以看到很多这样的消息

 preloader: Added pending event 2: DownloadEvent[type=load,loaded=0, total=62791, percent=0] 

表示自定义预加载器尚未validation/启动但下载事件已经进入。

如果您将切换为什么?

编辑

这是我的测试JNLP。 您似乎缺少指定JavaFX运行时资源?

    HelloWorldFX Unknown HelloWorldFX              

注意:我没有测试或执行过代码; 我的回答主要是基于查看代码。 我还没有参与过JavaFX,但是我之前已经使用过Swing和JNLP,因此我可以掌握这些代码。

我注意到的第一件事是,在阶段2中,没有显示处理应用程序JAR下载的默认JavaFX预加载器。

这似乎是因为PreloaderFX.start(stage)方法。 当方法的参数保持为空时传递的stage因为该方法构造一个new Stage()并向其添加子元素。 如果将子元素添加到方法的参数中,则应在阶段2期间显示自定义预加载器/进度条。

如果在此之后代码仍然无法按预期工作,我将尝试调试与noLoadingProgress标志关联的所有逻辑/代码。 尝试评论所有这些(基本上从图片中取出noLoadingProgress ),看看它是否解决了这个问题。

此外,即使您已在代码中使用此权限,请参阅此答案 – 根据该答案, handleApplicationNotification方法将处理所有ProgressNotification

希望这可以帮助!