Подтвердить что ты не робот

Как обрабатывать java web start (jnlp), загружая прогресс в preloader?

Проблема

У меня есть предварительный загрузчик для моего приложения, который обрабатывает инициализацию приложения. Теперь я пытаюсь расширить это, чтобы предварительный загрузчик также показывал прогресс загруженных JAR приложений.


TL; DR

  • Почему предварительный загрузчик не загружается во время Phase 2, так как это должно обрабатывать PreloaderFx::handleProgressNotification(); для отслеживания загрузки JAR-пакетов, я полагаю?

  • Обновление 14 марта 2016 г.. Использует DownloadServiceListener способ решить эту проблему? Как подключить это к этапу JavaFX?


Документация

Согласно Oracle, при запуске приложения существует 4 этапа:

  • Этап 1: Инициализация: Инициализация Java Runtime и начальное обследование идентифицируют компоненты, которые необходимо загрузить и выполнить перед запуском приложения. На этом этапе отображается экран заставки. По умолчанию это:

Java начинается

  • Этап 2: Загрузка и подготовка. Необходимые ресурсы загружаются из сети или кэш-диска, а также выполняются процедуры проверки. Все режимы выполнения см. По умолчанию или пользовательский предварительный загрузчик. На этом этапе должен быть показан мой пользовательский прелоадер.

  • Этап 3: Инициализация, специфичная для приложения. Приложение запускается, но может потребоваться загрузить дополнительные ресурсы или выполнить другие длительные приготовления, прежде чем он станет полностью функциональным. На данный момент отображается мой пользовательский прелоадер:

preloader

  • Этап 4: Выполнение приложения: приложение отображается и готово к использованию. В моем случае отображается окно входа в систему, и пользователь может продолжить.

login


Мой случай

Первое, что я замечаю, это то, что в Фазе 2 предварительный загрузчик JavaFX по умолчанию, обрабатывающий загрузку JAR приложений, не отображается. Из-за этого пользователь получает ощущение, что программа не запускалась или не прерывалась преждевременно, заставляя их несколько раз открывать JNLP файл. Когда загружаются JAR, мы вводим Phase 3 и отображается предварительный загрузчик.

Однако, я бы хотел, чтобы мой пользовательский предварительный загрузчик обрабатывал прогресс загрузки в ProgressBar (Phase 2). Я сделал все как можно проще, чтобы отслеживать, какие события происходят во время запуска моего приложения. Это основано на примере примера Jewelsea и на примерах Oracle

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

Код, обрабатываемый в Фазе 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="#" onclick="location.href='http://java.sun.com/products/autodl/j2se'; return false;" />


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

Обновления 13 марта 2016 года:

  • Скорректированный код, поэтому этап, пройденный в методе, используется вместо создания нового и комментирует все, что связано с noLoadingProgress boolean (предложенным nhylated)
  • Добавлен дополнительный System.out.println() в MainApp

Решение

Простое добавление <jfx:javafx-runtime version="8.0+"/> в файл JNLP исправлено. С добавлением этой строки предварительный загрузчик показывает в фазе 2. Я также взял на себя смелость изменить j2se version="1.6+" на j2se version="1.8+" Результат:

Результат предварительного загрузчика

Первые 50% - это обработка загрузок JAR. Это делается с помощью метода handleProgressNotification(). Второй 50% - это фактическая инициализация MainApp (longstart(), которая уведомляет прелоадер), выполняемая handleApplicationNotification().

4b9b3361

Ответ 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]

указывающий, что пользовательский предварительный загрузчик еще не был проверен/запущен, но события загрузки уже вошли.

Что произойдет, если вы переключите <update check="background"/> на <update check="always"/>?

ИЗМЕНИТЬ

Это мой тестовый 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>

Ответ 2

Примечание. Я не тестировал и не выполнял код; мой ответ в основном основан на просмотре кода. Я не работал над JavaFX, но могу понять код, так как раньше работал над Swing и JNLP.

Первое, что я замечаю, это то, что в Фазе 2 JavaFX по умолчанию preloader, обрабатывающий загрузку приложения JARs не показ.

Это похоже на метод PreloaderFX.start(stage). stage, переданный как аргумент метода, остается пустым, потому что метод создает a new Stage() и добавляет к нему детей. Если вы добавите дочерние элементы в аргумент метода, он должен отобразить ваш пользовательский предустановитель/панель прогресса во время фазы 2.

Если после этого код по-прежнему не работает должным образом, я попробую отладить всю логику/код, связанный с флагом noLoadingProgress. Попробуйте прокомментировать все это (по существу, взяв noLoadingProgress из картинки) и посмотрите, исправляет ли он проблему.

Кроме того, хотя вы получили это право в своем коде, см. этот ответ - в соответствии с которым все ProgressNotification обрабатываются методом handleApplicationNotification.

Надеюсь, это поможет!