【问题标题】:JavaFX Preloader never called from mainJavaFX Preloader 从未从 main 调用
【发布时间】:2017-06-06 11:55:26
【问题描述】:

我的 IDE 是 IntelliJ。我尝试使用this 文档来学习预加载器,但由于某种原因,我的主类永远不会调用预加载器,甚至不会调用它的方法。

这是我的主要课程:

public class LongInitApp extends Application {
Stage stage;
BooleanProperty ready = new SimpleBooleanProperty(false);
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(200);
                notifyPreloader(new ProgressNotification(((double) i)/max));
            }
            ready.setValue(Boolean.TRUE);

            notifyPreloader(new StateChangeNotification(
                StateChangeNotification.Type.BEFORE_START));
           return null;
        }};
    new Thread(task).start();
}

 public static void main(String args[]) {
    launch(args);
  }

@Override
public void start(final Stage stage) throws Exception {
    longStart();
    stage.setScene(new Scene(new Label("Application started"),400, 400));
    ready.addListener(new ChangeListener<Boolean>(){
        public void changed(
            ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) {
                if (Boolean.TRUE.equals(t1)) {
                    Platform.runLater(new Runnable() {
                        public void run() {
                            stage.show();
                        }});}}});;}}

还有我的预加载器类:

public class LongAppInitPreloader extends Preloader {
ProgressBar bar;
Stage stage;
boolean noLoadingProgress = true;

private Scene createPreloaderScene() {
    bar = new ProgressBar(0);
    BorderPane p = new BorderPane();
    p.setCenter(bar);
    return new Scene(p, 300, 150);
}

public void start(Stage stage) throws Exception {
    this.stage = stage;
    stage.setScene(createPreloaderScene());
    stage.show();
}

@Override
public void handleProgressNotification(ProgressNotification pn) {
    if (pn.getProgress() != 1.0 || !noLoadingProgress) {
      bar.setProgress(pn.getProgress()/2);
      if (pn.getProgress() > 0) {
          noLoadingProgress = false;
      }
    }
}

@Override
public void handleStateChangeNotification(StateChangeNotification evt) {
    //ignore, hide after application signals it is ready
}

@Override
public void handleApplicationNotification(PreloaderNotification pn) {
    if (pn instanceof ProgressNotification) {
       double v = ((ProgressNotification) pn).getProgress();
       if (!noLoadingProgress) {           
           v = 0.5 + v/2;
       }
       bar.setProgress(v);            
    } else if (pn instanceof StateChangeNotification) {
        stage.hide();
    }
}  
 }

您也可以查看此http://docs.oracle.com/javafx/2/deployment/preloaders.htm 文档。此代码属于示例 9-11 和 9-12。

【问题讨论】:

  • 你是如何执行应用程序的?它需要与指定的预加载器一起打包(或使用非公共 API 类进行启动,如上面链接的问题中所示)。在这里简单地执行主类不会导致预加载器被加载。
  • 我以为我会在使用 notifyPreloader 方法时启动它。我是否必须像“LauncherImpl.launchApplication(App.class,TestPreloader.class,args)一样启动它;” ?
  • 所以我已将 LauncherImpl 方法添加到我的 LongInitApp 类中。它打开但立即关闭。我想将此预加载器用于我编写的 csv 阅读器程序,它在解析时应该被视为正在加载。我相信我不明白预加载器是如何工作的。提前感谢您的帮助
  • 您预计如何部署此应用程序?你打算通过 web start 部署它吗?或者您只是要创建一个 jar 文件或自包含的应用程序包?如果您不打算使用 web start,我可能根本不会使用预加载器:只需在后台线程中启动一个任务,显示一个窗口,当任务完成时显示主窗口并隐藏原始窗口。我认为当真正的应用程序需要很长时间通过网络连接下载时,预加载器机制确实可以提供帮助:桌面应用程序不需要这种复杂性。

标签: java intellij-idea javafx javafx-2 javafx-8


【解决方案1】:

您需要在应用程序启动时为其指定预加载器类。一种“快速而肮脏”的方法是使用非公共 API 类com.sun.javafx.application.LauncherImpl。请注意,不保证此类在 JavaFX 的未来版本中可用,因此您应该仅将其用于快速测试(如果有的话)。在 LongInitApp 类中使用以下主要方法:

public static void main(String[] args) {
    LauncherImpl.launchApplication(LongInitApp.class, LongAppInitPreloader.class, args);
}

包含预加载器的“官方”方式是将其指定为 JavaFX 部署过程的一部分。部署过程的完整文档位于 http://docs.oracle.com/javase/8/docs/technotes/guides/deploy/,但最小的方法如下。

  1. 编译应用程序(您的 IDE 通常会在您保存时执行此操作)
  2. 从命令行,使用createjar 命令运行javapackager 工具:

    javapackager -createjar -outfile myapp.jar -appclass my.package.LongInitApp \
      -preloader my.package.LongAppInitPreloader -srcdir dir
    

    其中my.package 是包含您的应用程序和预加载器类的包(它们可以在不同的包中),dir 是包含所有类的目录结构的根(例如,如果 my.package 真的是您的包名, 那么dir 将有一个子目录my,该子目录将有一个子目录package,其中将包含.class 文件)。

    这将生成一个myapp.jar 文件,该文件是可执行的并且知道您的预加载器,因此您可以使用java -jar myapp.jar 执行它。如果您有兴趣,可以从 jar 文件中提取生成的清单,并使用jar xf myapp.jar META-INF/MANIFEST.MF 查看其中的内容(然后查看文件META-INF/MANIFEST.MF)。 (简而言之,它的作用是将主类声明为旨在启动 JavaFX 应用程序的内部类。清单文件包含指定 JavaFX 应用程序类(在您的情况下为LongInitApp)和预加载器类(如果存在)的属性. 启动应用程序的内部类检索这些属性,它的 main 方法基本上会启动您定义的应用程序,如果有预加载器,则使用它。)

    请注意,如果您使用这种方法,您的应用程序类 LongInitApp 不需要(并且可能不应该有main 方法。

大多数 IDE 对此都有某种形式的支持。例如。如果您使用带有 E(fx)clipse 插件的 Eclipse,并创建一个 JavaFX 项目,它将为您生成一个 build.fxbuild 文件。双击该文件将打开一个可视化编辑器,您可以在其中设置上面javapackager 命令中定义的属性。点击“Generate ant build.xml and run”会创建jar文件。

除非您需要预加载器提供的特定功能,并且超出您可以轻松自己编程的功能,否则可能不值得为此付出努力。具体来说,如果您通过 Java Web Start 部署应用程序,预加载器特别有用,并且主 jar 文件的下载需要很长时间。发生这种情况时可以显示预加载器。如果您使用的是独立应用程序(或自包含应用程序包),那么您可以很容易地自己创建“预加载器”功能。

例如,除非我使用 Java Web Start 并且主 jar 文件很大,否则我可能会重构您的示例代码,使“预加载器”只是一个普通的旧类:

import javafx.scene.Scene;
import javafx.beans.property.DoubleProperty ;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class LongAppInitPreloader {
    private ProgressBar bar;

    private Stage stage;

    public LongAppInitPreloader() {
        this.stage = new Stage();
        stage.setScene(createPreloaderScene());
    }

    private Scene createPreloaderScene() {
        bar = new ProgressBar(0);
        BorderPane p = new BorderPane();
        p.setCenter(bar);
        return new Scene(p, 300, 150);
    }

    public void show() {
        stage.show();
    }

    public void hide() {
        stage.hide();
    }

    public DoubleProperty progressProperty() {
        return bar.progressProperty();
    }
}

然后您的应用程序类就可以使用任务并更新其进度:

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;

public class LongInitApp extends Application {

    @Override
    public void start(final Stage stage) throws Exception {
        Task<Void> task = createLongStartTask();

        stage.setScene(new Scene(new Label("Application started"), 400, 400));

        LongAppInitPreloader preloader = new LongAppInitPreloader();
        preloader.progressProperty().bind(task.progressProperty());

        task.setOnSucceeded(e -> {
            stage.show();
            preloader.hide();
        });
        preloader.show();

        new Thread(task).start();
    }

    private Task<Void> createLongStartTask() {
        // simulate long init in background
        Task<Void> task = new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                int max = 10;
                for (int i = 1; i <= max; i++) {
                    Thread.sleep(200);
                    updateProgress(i, max);
                }

                return null;
            }
        };
        return task ;
    }

    // just here so I can run directly from Eclipse:
    public static void main(String args[]) {
        launch(args);
    }
}

【讨论】:

  • 谢谢你,但是在你发表评论后我改变了主意,我的应用程序对于预加载器来说太简单了。所以,我尝试了 thread.start() 方法,但它仍然等待第一个应用程序继续。然后我尝试如下:
  • 我试过 Platform.runlater 在 for 循环之前和循环之后运行一个 simpleLoader 类。 Loop 只是用来模拟进度。我想要的是当它在 for 循环 sp(simpleLoader) 中应该被视为加载动画时。但相反,它打开 sp 但不加载内部的任何节点(它包含一个栏和标签)并在 for 循环后关闭。我做错了什么?
  • 为什么要在 Fx 应用程序线程上运行“简单加载程序”?这将阻止 UI 在完成之前更新。
  • @Jeredriq 我问题的最后一部分准确地展示了如何在不使用预加载器的情况下执行此操作。你跑了吗?为什么不这样做呢?
  • 我用线程调用它,但现在我用 Task 尝试你的方法。顺便说一句,我在线程中调用一个类并发送错误警报,但是当我从线程中调用它时,警报不会出现。你有什么建议?
【解决方案2】:

您在 LongInitApp 类中缺少 Main 方法

public static void main(String args[]) {
    launch(args);
}

【讨论】:

  • 我添加了它没有任何改变。
猜你喜欢
  • 2013-02-09
  • 2015-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多