【问题标题】:How to handle java web start (jnlp) downloading progress in a preloader?如何处理预加载器中的 java web start (jnlp) 下载进度?
【发布时间】:2016-03-07 17:18:56
【问题描述】:

问题

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


TL;DR

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

  • 2016 年 3 月 14 日更新:使用 DownloadServiceListener 可以解决这个问题吗?如何将它连接到 JavaFX 阶段?


文档

According to Oracle,应用程序启动有 4 个阶段:

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

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

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

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


我的情况

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

但是,我希望我的自定义预加载器也能处理 ProgressBar 中的下载进度(第 2 阶段)。我使一切尽可能简单,以跟踪在我的应用程序启动期间发生了哪些事件。这是基于example of JewelseaOracle examples

预加载器:

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

调试

我在启动文件时仔细观察了 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()

解决方案

简单地将&lt;jfx:javafx-runtime version="8.0+"/&gt; 添加到 JNLP 文件即可修复它。添加该行后,预加载器显示在第 2 阶段。我还冒昧地将 j2se version="1.6+" 更改为 j2se version="1.8+" 结果:

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

【问题讨论】:

  • 它在 JRE8u144 上对我不起作用。你用的是什么版本?
  • 您遇到的行为是什么?目前,预加载器似乎只在我下载完成时显示。所以基本上在上面示例中的初始化 50% 阶段。
  • 没错。预加载器仅在下载完成后显示。
  • @Eng.Fouad "预加载器仅在下载完成后才会显示。" 如果它以前可以工作但不再工作,则表明 JRE 中存在错误。错误数据库中有任何报告吗?如果没有,请提出一个并让制造商参与进来。

标签: java javafx-8 java-web-start jnlp


【解决方案1】:

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

如果启用 Java Webstart 完整跟踪

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

您应该会看到预加载器消息,这些消息应该会为您提供一些关于正在发生的事情的信息。就我而言,我可以看到很多这样的消息

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

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

如果您将&lt;update check="background"/&gt; 切换为&lt;update check="always"/&gt;,会发生什么情况?

编辑

这是我的测试 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>

【讨论】:

  • &lt;update check="background"/&gt; 更改为&lt;update check="always"/&gt; 并没有带来任何可悲的改变。您看到默认的 JavaFX 预加载器了吗?因为我没有出于某种原因......
  • 是的,我愿意。但是,如果您不指定自定义预加载器类,您只会看到它。如果您甚至没有看到默认的预加载器,我很确定您的 JNLP 有问题。完整的跟踪会有所帮助...
  • 我认为我们正在取得进展,将&lt;jfx:javafx-runtime version="8.0+"/&gt; 添加到资源中确实显示了我现在处于第 2 阶段的自定义预加载器,并且正在调用 PreloaderFx::handleProgressNotification();。但是,&lt;jfx:javafx-runtime version="8.0+"/&gt; 应该是自 Java 8 以来过时的:bugs.openjdk.java.net/browse/JDK-8096671。今天下午我会更深入地研究它! :)
  • 通过添加 javafx-runtime 版本就像一个魅力!非常感谢!
  • 使用&lt;jfx:javafx-runtime version="8.0+"/&gt; 似乎不适用于最新版本的 Java 8 :(
【解决方案2】:

注意:我没有测试或执行过代码;我的回答主要是基于查看代码。我没有研究过 JavaFX,但我可以掌握代码,因为我之前研究过 Swing 和 JNLP。

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

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

如果在此之后代码仍然无法按预期工作,我会尝试调试与noLoadingProgress 标志关联的所有逻辑/代码。尝试将所有这些注释掉(基本上将noLoadingProgress 排除在外),看看它是否能解决问题。

此外,即使您在代码中正确设置了这一点,请参阅 this 答案 - 根据该答案,所有 ProgressNotifications 都由 handleApplicationNotification 方法处理。

希望这会有所帮助!

【讨论】:

  • 首先,感谢您花时间研究这个问题。我根据您的建议和我的发现编辑了这个问题。使用方法传递的阶段并注释掉noLoadingProgress布尔值后,结果仍然相同。我在 MainApp 中添加了一些额外的 System.out.println() 行,以检查第 3 阶段何时开始。我仍然坚信 handleApplicationNotification 应该只用于处理来自 MainApp 的 notifyPreloader() 操作。搜索继续:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-05-02
  • 1970-01-01
  • 1970-01-01
  • 2010-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多