我正在构建JavaFX应用程序,并且想知道是否有关于如何尽快加载当前Scene
中的新Stage
的建议(最佳实践)。
目前,我正在做的(或多或少)是这样的:
Parent root = (Parent)myFXLoader.load();
currentStage.setScene(new Scene (root);
当加载初始化
Scene
,TableView
es等更复杂的场景时,以上对于简单的Combobox
s来说足够好并且足够快,但是Scene
s之间的转换需要很多秒,这很烦人。我在
Controller
的initialize(URL url, ResourceBundle rb)
方法内部进行的所有初始化。在那里,我将项目添加到
Choice/Combo
框中,初始化TableView
等,但是正如我所说的,这花费了太多时间。难道我做错了什么?我应该在其他地方初始化吗?
谢谢你。
编辑:
任何对此有兴趣的人,甚至对他们的项目有想法的人,我都在google.com上上传了我的项目(Netbeans项目)的一部分。
您可以使用SVN进行检查。这是链接:
http://tabularasafx.googlecode.com/svn/trunk/
userName:tabularasafx只读
无需密码
运行项目后的说明:
第一个屏幕是登录屏幕,只需单击“确定”
第二个屏幕是“主页”,您可以在其中看到treeView菜单并导航到4个不同的屏幕
我的问题是类(class)->创建页面的加载时间。看看它,让我知道是否找到任何东西
编辑:
我进行了@jewelsea建议的3项更改。
1.我使用了HashMap来保留每个屏幕的所有 Controller
2.我仅更新场景的一部分,而不更新整个场景
3.我使用JavaFX2 - very poor performance when adding custom made (fxml)panels to gridpane dynamically的答案来帮助 Controller 如答案中所述更快地加载。
现在一切都快得多了!!!!
随意使用该项目作为指导
另外,我还更新了程序以浏览3个屏幕,以更好地理解
注意我的代码很乱
最佳答案
一些背景
我看了看你的项目Dimitris。
我将您的负载创建时间(OS X 10.9上的Java 8 b129,2012 Macbook Air)定为“类创建”页面的时间。对我来说只花了一秒钟多的时间。
为了简化测试,我删除了使用并发服务加载新FXML的部分,并仅在被请求时直接将FXML加载到JavaFX应用程序线程上,以这种方式使用它要容易得多。
很抱歉在这里长答案。像这样的事情通常不太适合StackOverflow,它们最终以教程或博客形式出现的最好,但是我很好奇发生了什么,所以我想我会花一些时间来研究和编写它向上。
不要为您加载的每个FXML创建新场景
每次加载FXML时都要设置一个新场景(具有新的大小)。无论出于什么原因,这都是一个非常昂贵的操作,您不需要这样做。您的舞台上已经有一个场景,只需重用即可。因此,替换以下代码:
stage.setScene(new Scene(service.getValue().getRoot(), service.getValue().getX(), service.getValue().getY()));
和:
stage.getScene().setRoot(service.getValue().getRoot());
这将节省一半多的加载时间,因此现在class-> create第一次运行大约需要400毫秒。
此更改是轻松获得性能的一个示例。
它还提供了更好的用户体验,因为在我的机器上,场景在更改场景时呈灰色闪烁,但是当您替换现有场景的场景根目录时,则没有灰色闪烁。
因为JVM使用Java的即时编译器运行,所以随后的显示类->创建的请求进行得更快,因此在打开场景两次或三次之后,大约需要250毫秒(或四分之一秒)。
FXMLLoader的速度很慢
在剩余的250毫秒加载时间中,约2毫秒花费在初始化代码中,JavaFX渲染控件花费2毫秒,FXMLLoader加载FXML并实例化节点进入场景花费了另外246毫秒。
使用UI代码的想法是,您希望获得转换到<16到30ms的目标时间。这将使用户快速平稳地过渡。
将您的UI代码与网络和数据库代码分开
最好通过JavaFX应用程序线程完成网络和数据库调用,因此您可以使用JavaFX并发工具来包装这些任务。但是我建议分开关注。使用并发服务来获取数据,但是一旦取回数据,请使用Platform.runLater或Task返回值将数据传输到JavaFX应用程序线程并在JavaFX应用程序线程上运行填充(因为填充任务将是还是很快)。
这样,您已将系统中的多线程划分为不同的逻辑组件-网络在其自己的线程上运行,而UI操作在不同的线程上运行。它使事物更易于推理和设计。可以将其想象为类似于Web编程的网络,其中ajax调用将数据并发地提取到UI,然后提供一个回调,该回调被调用以将数据处理到UI中。
这样做的另一个原因是,许多网络库无论如何都带有其自己的线程实现,因此您只使用它而不是生成自己的线程。
如何使FXML加载更快
您实际上并不需要多线程代码来加载FXML文件。 FXML的初始化函数运行得非常快(仅几毫秒)。 FXMLLoader需要250毫秒。我没有详细介绍它,以了解为什么会这样。但是塞巴斯蒂安(Sebastian)对JavaFX2 - very poor performance when adding custom made (fxml)panels to gridpane dynamically的回答中有一些迹象。我认为主要的性能问题是FXMLLoader非常依赖反射。
因此,在出现缓慢的FXMLLoader问题的情况下,最好的解决方案是使用FXMLLoader的替代方法,该方法性能更好且不依赖反射。我相信JavaFX团队正在研究FXMLLoader的等效二进制文件(例如,在构建阶段将FXML文件预先解析为二进制Java类文件,然后可以将其快速加载到JVM中)。但是该工作(如果存在)尚未由JavaFX团队发布。 Tom Schindl已经完成了类似的工作,它的pre-compiles the FXML to Java source可以被编译为Java类,因此您的应用程序仍在使用已编译的类,它应该又好又迅速。
因此,使FXML更快加载的解决方案目前正在开发中,但在生产系统上并不是真正稳定和可用。因此,您需要其他方法来解决此问题。
使您的表单更简单
在我看来,这似乎是一个解决方案,但是IMO为“创建类”场景设计的对象有点复杂。您可能要考虑用多阶段向导替换它。这样的向导通常会更快地加载,因为您只需要在每个向导屏幕上加载少量项目即可。但是更重要的一点是,这样的向导可能更易于使用,并且为您的用户提供了更好的设计。
只替换场景中需要的部分
您正在加载FXML文件,这些文件将为每个新页面创建整个应用程序UI。但是您不需要这样做,因为诸如顶部菜单,状态栏和导航侧栏之类的内容并不会因为用户加载新表单而发生变化,只有显示“创建类”表单的中央部分才会发生变化。因此,只需加载要更改的场景部分而不是整个场景内容的节点。
此外,这将通过在每个阶段替换整个UI来帮助解决应用程序遇到的其他问题。当您替换导航菜单时,该菜单不会自动记住并突出显示导航树中当前选中的项目-您必须明确记住它并在进行导航后再次将其重置。但是,如果您不替换整个场景内容,则导航菜单会记住上一次选择的内容并显示出来(因为导航菜单本身在导航时不会发生变化)。
缓存FXML加载节点树和 Controller
您在应用程序中一次只能显示一个“创建类”表单。因此,您只需要使用FXMLLoader一次加载“创建类”表单。这将为表单创建一个节点树。定义一个静态HashMap,将“创建类”映射到CreateClassesController对象(在应用程序中,该对象也只有一个)。当您导航到“创建类”屏幕时,通过从哈希映射中检索 Controller ,来查看您之前是否曾经去过那里。如果已经存在一个 Controller 类,请对其进行查询以获取表单的根 Pane ,并通过用新表单替换场景的中央面板来在场景中显示该表单。您可以在 Controller 上添加其他方法,以调用该方法以清除表单中的任何现有数据值或设置从网络获取任务中加载的任何数据值。
除了可以加快应用程序的速度外,您现在还可以保留“创建类”表单的状态,直到您或用户决定清除它为止。这意味着用户可以浏览并部分填写表单,然后再返回到应用程序中的其他位置,然后返回到表单,表单将保持与离开表单相同的状态,而不会忘记用户之前输入的所有内容。
现在,由于仅加载一次“创建类”表单,因此可以在启动时加载所有表单(并具有一个预加载器页面,该页面指示您的应用程序正在初始化)。这意味着该应用程序的初始启动将较慢,但该应用程序的操作将很快。
建议设计
查看SceneBuilder实现
遵循SceneBuilder implementation itself所使用的原则-这是合理大小的JavaFX项目的最新最佳设计示例,该项目使用FXML作为其UI。 SceneBuilder代码是开源的,并根据BSD样式许可进行分发,因此它很容易研究。
结果
我对该答案中提到的一些想法进行了原型(prototype)设计,这将“创建类”屏幕的初始加载时间从一秒以上减少到了约400ms(首次加载屏幕)。我没有用其他东西代替FXMLLoader(我相信它会大大降低400ms的值)。随后,基于刚刚重新添加到场景中的缓存节点树的“创建类”表单的加载大约花费了4毫秒-因此,就用户而言,操作性能是瞬时的。
其他问题的更新
Do you think that I should use Tom Schindl's solution for compiling FXML or is it "too Beta"?
我的猜测是(截止到今天)它是“Beta版”。但是,请自己尝试一下,看看它是否满足您的需求。要获得Tom's FXML => JavaFX编译器的支持,请将该项目发布到e(fx)clipse forums中,因为该项目属于e(fx)clipse project的较大保护范围。
And I tried 'stage.getScene().setRoot(service.getValue().getRoot());' but got OutOfMemoryError: Java heap space do you think that line caused it or it is not relevant?
作为创建此答案的一部分,我正在对您的代码进行概要分析(通过将NetBeans profiler附加到已在运行的应用程序实例中)。我确实注意到,每次您的程序加载“create class”场景时,内存使用量都会显着增加,并且似乎没有释放内存。我没有花时间试图找出原因,但这是在未经修改的情况下分析您的代码。因此,我怀疑系统内存不足的最终原因与换出场景还是换出场景根无关。我注意到CSS伪类消耗了大量内存,尽管我无法告诉您这样做的原因。我的猜测是,如果您遵循此答案中概述的原则,那么总的来说,您的应用程序将更加高效,并且您可以规避当前代码中存在的与内存相关的问题。如果没有,您可以继续分析应用程序的内存使用情况,以查看根本问题是什么。
关于java - 场景加载太慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22328087/