如何在预加载程序中处理JavaWebStart(jnlp)下载进度? 问题

如何在预加载程序中处理JavaWebStart(jnlp)下载进度? 问题,java,javafx-8,java-web-start,jnlp,Java,Javafx 8,Java Web Start,Jnlp,我的应用程序有一个预加载程序,用于处理特定于应用程序的初始化。现在我尝试扩展它,以便预加载程序也显示下载的应用程序JAR的进度 TL;博士 为什么在阶段2期间未加载预加载程序,因为这应该处理预加载程序fx::handleProgressNotification()跟踪JAR的下载,我想是吧 2016年3月14日更新:使用DownloadServiceListener是解决此问题的方法吗?如何将其连接到JavaFX阶段 文档 ,启动应用程序时有4个阶段: 阶段1:初始化:Java运行时的

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


TL;博士
  • 为什么在阶段2期间未加载预加载程序,因为这应该处理
    预加载程序fx::handleProgressNotification()
    跟踪JAR的下载,我想是吧

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


文档 ,启动应用程序时有4个阶段:

  • 阶段1:初始化:Java运行时的初始化和初始检查确定启动应用程序之前必须加载和执行的组件。 在此阶段,将显示一个启动屏幕。默认值为:

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

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

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


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

但是,我希望我的自定义预加载程序也能处理ProgressBar中的下载进度(第2阶段)。我尽可能简单地跟踪应用程序启动期间发生的事件。这是基于和的:

预加载程序:

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阶段处理的代码来自与预加载程序交互的主应用程序,这是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
    }
}

2016年3月13日更新:

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

解决方案 只需将
添加到JNLP文件,即可修复此问题。添加该行后,预加载程序将显示在第2阶段。我还冒昧地将
j2se version=“1.6+”
更改为
j2se version=“1.8+”
结果:


前50%是JAR下载的处理。这是通过
handleProgressNotification()
方法完成的。第二个50%是由
handleApplicationNotification()

完成的MainApp(
longstart()
通知预加载程序)的实际初始化。注意:我没有测试或执行代码;我的答案主要基于查看代码。我没有使用过JavaFX,但是我可以掌握代码,因为我以前使用过Swing和JNLP

我注意到的第一件事是,在第2阶段,默认的JavaFX 不支持预加载程序处理应用程序JAR的下载 展示

这似乎是因为preforerfx.start(stage)方法。作为方法参数传递的
stage
仍然为空,因为该方法构造了一个
newstage()
,并向其中添加子级。如果将子元素添加到方法的参数中,则在第2阶段,它应该显示自定义预加载程序/进度条

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

此外,即使您在代码中正确地做到了这一点,请参见答案-根据答案,所有
ProgressNotification
s都由
handleApplicationNotification
方法处理


希望这有帮助

最近,我也一直在为此而斗争。我切换回了(丑陋的)默认预加载程序(因为它显示得很好),直到我找到更多的时间来研究这个问题

如果启用Java Webstart完整跟踪

"<JAVA_HOME>\bin\javaws.exe" -userConfig deployment.trace true
"<JAVA_HOME>\bin\javaws.exe" -userConfig deployment.trace.level all
表示自定义预加载程序尚未验证/启动,但下载事件已进入

如果将
切换到
,会发生什么情况

编辑 这是我的测试JNLP。您似乎没有指定JavaFX运行时资源

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

HelloWorldFX
不为人知
HelloWorldFX

首先,感谢您花时间研究这个问题。我根据你的建议和我的发现编辑了这个问题。使用方法传递的阶段并注释掉
noLoadingProgress
boolean之后,结果仍然相同。我在MainApp中添加了一些额外的
System.out.println()
行,以便在第3阶段开始时检出。我仍然相信
handlapplicationnotification
应该只用于处理来自MainApp的
notifypreload()
操作。这个
preloader: Added pending event 2: DownloadEvent[type=load,loaded=0, total=62791, percent=0]
<?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>