JavaFx 之 Preloader

2018-05-16  本文已影响358人  Ggx的代码之旅

做过Java的同学都用过Eclipse或者IDEA,这两款编辑器在启动的时候都会有一个预加载的画面,有一个专业的术语形容它——SplashScreen。当然在JavaFx中也提供了这种机制,实现这样的东西我们需要用到一个类Preloader这里称之为预加载器。

Preloader

预加载器是在主应用程序之前启动可以定制启动体验的小应用程序。
预加载有以下几个关键:

  1. 可以获取加载主应用程序资源的进度通知消息
  2. 可以获取错误通知消息
  3. 可以获取应用程序初始化和启动的通知
  4. 决定主应用程序是否可见

默认情况下预加载器显示在应用程序的顶部,在预加载程序可见之前,它是不可见的。预加载器需要隐藏自己以使应用程序可见。好的做法是不要比application.start()被调用之前调用,否则应用程序本身就不可见。

预加载器还可以与主应用程序协作以实现高级视觉效果或共享数据(例如实现登录屏幕)。预加载器获得对应用程序的引用,并且如果应用程序实现预加载器知道并依赖的接口,则可以从应用程序中提取所需的用于合作的数据。通常不推荐以应用程序会直接调用它们的方式设计预加载器,因为如果应用程序预加载器不存在或者主应用程序已签名,这将导致用户体验不佳。

如果主应用程序没有指定预加载器,就会使用默认的预加载器,此预加载器可以被用户定制。

自定义预加载器的实现应遵循以下规则:

  1. 设计一个类并继承自Preloader
  2. 预加载器所需的类需要打包在单独的jar中。我们建议这个jar是不签名的。
  3. JNLP部署描述符应该具有预载入类属性,在JavaFX-DESC元素中以类的全名作为值,并且需要进度的JAR需要download=“progress”类型

应用程序还可以使用notifyPreloader方法向预加载器发送自定义通知。这样,预加载器也可以显示应用程序初始化进度。

注意:预加载器与其他JavaFX应用程序(包括FX线程规则)相同。特别是,类构造函数和init()方法将在非FX线程上调用,并在FX应用程序线程上执行start()。这也意味着应用程序构造函数init()将与预加载器的start()并行运行。

预加载器通知的回调将在FX应用程序线程上传递。

Preload详解

查看Preload源码可以发现,Preload其实本质上仍然是一个Application,因为它就继承自Application。区别在于在里面定义个一些列的通知类型和回调方法。下面分别介绍它的通知方法:

  1. void handleProgressNotification(ProgressNotification info)
    此方法是一个进度指示通知方法。该方法在JavaFx运行应用程序资源被载入时调用用来指示进度。通过ProgressNotification中的getProgress()方法获取进度,事实上此类中也就这一个方法可用。这个方法不会被notifyPreloader调用传递。

  2. void handleStateChangeNotification(StateChangeNotification info)
    此方法当应用程序状态改变是通知。这个方法由FX运行时作为应用程序生命周期的一部分调用。

  3. void handleApplicationNotification(PreloaderNotification info)
    此方法是应用程序生成的通知。这个方法由FX运行时调用,以发送通过notifyPreloader发送的通知。应用程序不应该直接调用这个方法,而是应该使用notifyPreloader来避免代码的混乱。

发送预加载器通知notifyPreloader

在主应用程序中Application下有一个notifyPreloader方法,和预加载器之间的交互主要通过它来实现。正如上面所说一样这个方法只会传递Preloader中的handleApplicationNotification()方法

Example

下面有一个示例,用来演示启动应用过程中预加载耗时资源的例子:

/**
 * 这是预加载器界面
 */
public class TestLongInitAppPreloader extends Preloader {
    private Stage stage;
    private ProgressBar bar;

    @Override
    public void start(Stage primaryStage) throws Exception {
        this.stage=primaryStage;
        //这里可以看到和Application一样,也有舞台,我们可以定制自己的界面
        BorderPane p = new BorderPane();
        ImageView iv=new ImageView();
        iv.setImage(new Image(ClassLoader.getSystemResourceAsStream("images/d.jpg")));
        p.setCenter(iv);
        bar = new ProgressBar(0);
        p.setBottom(bar);
        primaryStage.initStyle(StageStyle.UNDECORATED);
        primaryStage.setScene(new Scene(p));
        primaryStage.show();
    }

    @Override
    public void handleProgressNotification(ProgressNotification info) {
        System.out.println("handleProgressNotification="+info.getProgress());
        if (info.getProgress() != 1.0) {
            bar.setProgress(info.getProgress() / 2);
        }
    }


    /**
     * 重载这个方法可以处理应用通知
     * @param info
     */
    @Override
    public void handleApplicationNotification(PreloaderNotification info) {
        if (info instanceof ProgressNotification) {
            //提取应用程序发送过来的进度值
            double v = ((ProgressNotification) info).getProgress();
            System.out.println("handleApplicationNotification="+v);
            bar.setProgress(v);
        } else if (info instanceof StateChangeNotification) {
            //隐藏/或者关闭preloader
//            stage.hide();
            stage.close();
        }
    }

}
/**
 * 主应用程序
 */
public class TestLongInitApp extends Application {

    BooleanProperty ready=new SimpleBooleanProperty(false);

    @Override
    public void start(Stage primaryStage) throws Exception {
        //这里可能处理一些耗时操作
        new Thread(longTask()).start();

        primaryStage.setTitle("主界面");
        primaryStage.setScene(new Scene(new Label("Main Application started"),
                400, 400));
        primaryStage.setMaximized(true);

        ready.addListener((observable, oldValue, newValue) -> {
            if(Boolean.TRUE.equals(newValue)){
                Platform.runLater(primaryStage::show);
            }
        });
    }

    private Task longTask(){
        return new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                //模拟准备耗时数据
                int max = 10;
                for (int i = 1; i <= max; i++) {
                    Thread.sleep(1000);
                    // 发送进程给预加载器主要通过Application中的notifyPreloader(PreloaderNotification)方法
                    Preloader.ProgressNotification notification=new Preloader.ProgressNotification(((double) i)/max);
                    notifyPreloader(notification);
                }
                // 这里数据已经准备好了
                // 在隐藏预加载程序之前防止应用程序过早退出
                ready.setValue(Boolean.TRUE);

                notifyPreloader(new Preloader.StateChangeNotification(
                        Preloader.StateChangeNotification.Type.BEFORE_START));
                return null;
            }
        };
    }
}

欢迎共同探讨更多安卓,java,c/c++相关技术QQ群:392154157

上一篇下一篇

猜你喜欢

热点阅读