做过Java的同学都用过Eclipse或者IDEA,这两款编辑器在启动的时候都会有一个预加载的画面,有一个专业的术语形容它——SplashScreen。当然在JavaFx中也提供了这种机制,实现这样的东西我们需要用到一个类Preloader这里称之为预加载器。
Preloader
预加载器是在主应用程序之前启动可以定制启动体验的小应用程序。
预加载有以下几个关键:
- 可以获取加载主应用程序资源的进度通知消息
- 可以获取错误通知消息
- 可以获取应用程序初始化和启动的通知
- 决定主应用程序是否可见
默认情况下预加载器显示在应用程序的顶部,在预加载程序可见之前,它是不可见的。预加载器需要隐藏自己以使应用程序可见。好的做法是不要比application.start()
被调用之前调用,否则应用程序本身就不可见。
预加载器还可以与主应用程序协作以实现高级视觉效果或共享数据(例如实现登录屏幕)。预加载器获得对应用程序的引用,并且如果应用程序实现预加载器知道并依赖的接口,则可以从应用程序中提取所需的用于合作的数据。通常不推荐以应用程序会直接调用它们的方式设计预加载器,因为如果应用程序预加载器不存在或者主应用程序已签名,这将导致用户体验不佳。
如果主应用程序没有指定预加载器,就会使用默认的预加载器,此预加载器可以被用户定制。
自定义预加载器的实现应遵循以下规则:
- 设计一个类并继承自Preloader
- 预加载器所需的类需要打包在单独的jar中。我们建议这个jar是不签名的。
- JNLP部署描述符应该具有预载入类属性,在JavaFX-DESC元素中以类的全名作为值,并且需要进度的JAR需要
download=“progress”
类型
应用程序还可以使用notifyPreloader
方法向预加载器发送自定义通知。这样,预加载器也可以显示应用程序初始化进度。
注意:预加载器与其他JavaFX应用程序(包括FX线程规则)相同。特别是,类构造函数和init()方法将在非FX线程上调用,并在FX应用程序线程上执行start()。这也意味着应用程序构造函数init()
将与预加载器的start()
并行运行。
预加载器通知的回调将在FX应用程序线程上传递。
Preload详解
查看Preload源码可以发现,Preload其实本质上仍然是一个Application,因为它就继承自Application。区别在于在里面定义个一些列的通知类型和回调方法。下面分别介绍它的通知方法:
-
void handleProgressNotification(ProgressNotification info)
此方法是一个进度指示通知方法。该方法在JavaFx运行应用程序资源被载入时调用用来指示进度。通过ProgressNotification
中的getProgress()
方法获取进度,事实上此类中也就这一个方法可用。这个方法不会被notifyPreloader
调用传递。 -
void handleStateChangeNotification(StateChangeNotification info)
此方法当应用程序状态改变是通知。这个方法由FX运行时作为应用程序生命周期的一部分调用。 -
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;
}
};
}
}
网友评论