场景加载太慢

k97glaaz  于 2021-07-09  发布在  Java
关注(0)|答案(1)|浏览(375)

我正在构建一个javafx应用程序,我想知道是否有关于如何加载新文件的建议(最佳实践) Scene 在当前 Stage 尽可能快。
目前我所做的(或多或少)是这样的:

Parent root = (Parent)myFXLoader.load();
currentStage.setScene(new Scene (root);

以上工作很好,速度足够简单 Scene 但是当加载更复杂的场景时 TableView 是的, Combobox 等之间的过渡 Scene s需要很多秒,这很烦人。
我在里面做的所有初始化 Controllerinitialize(URL url, ResourceBundle rb) 方法。
在那里,我将项目添加到 Choice/Combo 框,初始化 TableView 但正如我所说,这需要太多的时间。
我做错什么了吗?我应该在别的地方初始化吗?
谢谢您。
编辑:
任何有兴趣帮助这个项目的人,或者想为他们的项目出主意的人,我已经在google.com上传了我的项目(netbeans项目)的一部分。
你可以使用svn检查它。这是链接:
http://tabularasafx.googlecode.com/svn/trunk/
用户名:tabularasafx只读
不需要密码
运行项目后的说明:
第一个屏幕是登录屏幕,只需单击“确定”
第二个屏幕是“主页”,在那里你可以看到一个树视图菜单,并导航到4个不同的屏幕
我的问题是类->创建页面的加载时间。你看一下,有什么发现就告诉我
编辑:
我做了3个@jewelsea建议的改变。
1.我使用hashmap保存每个屏幕的所有控制器
2.我只更新了部分场景而不是整个场景
3.我使用了javafx2的答案-在动态地向gridpane添加定制(fxml)面板以帮助控制器更快地加载时,性能非常差,如答案中所述。
现在一切都快多了!!!!
请随意使用该项目作为指导
另外,为了更好地理解,我还更新了程序以浏览3个屏幕
注意,我的代码很乱

fcipmucu

fcipmucu1#

一些背景
我看了你的项目迪米特里斯。
我为“classes create”页面计时了加载创建时间(OSX10.92012MacBookAir上的Java8B129)。我只花了一秒钟。
为了简化测试,我删除了使用并发服务加载新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());

这将节省超过半秒的加载时间,因此现在classes->create在第一次运行时大约需要400毫秒。
这一变化是一个简单的性能胜利的例子。
它还提供了更好的用户体验,因为在我的机器上,当您更改场景时,舞台会闪烁灰色,但当您仅替换现有场景的场景根时,就没有灰色闪烁。
因为jvm使用的是java的即时编译器,所以后续的显示类->创建的请求速度更快,所以打开场景两到三次后大约需要250ms(或者四分之一秒)。
fxmlloader速度慢
在剩下的250ms加载时间中,大约2ms用于初始化代码,另外2ms用于javafx渲染控件,另外246ms用于fxmloader加载fxml并示例化节点以进入场景。
用户界面代码的想法是,你想把转换的目标时间降到<16到30ms,这将使用户的转换快速而平稳。
将用户界面代码与网络和数据库代码分开
网络和数据库调用最好在javafx应用程序线程之外完成,因此可以使用javafx并发工具 Package 这些任务。但我建议将关注点分开。使用并发服务来获取数据,但是一旦您恢复了数据,就可以使用platform.runlater或任务返回值将数据传输到javafx应用程序线程,并在javafx应用程序线程上运行填充(因为填充任务将非常快)。
通过这种方式,您将系统中的多线程划分为不同的逻辑组件—网络在自己的线程上运行,ui操作在不同的线程上运行。它使东西更容易推理和设计。把它想象成有点像web编程,ajax调用将数据并发地提取到ui,然后提供一个回调,调用回调将数据处理到ui中。
这样做的另一个原因是,许多网络库都有自己的线程实现,所以您只需使用它,而不是生成自己的线程。
如何使fxml加载更快
加载fxml文件不需要多线程代码。fxml的initialize函数运行得非常快(只有几毫秒)。fxmlloader需要250毫秒。我还没有详细分析它以了解为什么会这样。但是sebastian对javafx2的回答中有一些迹象表明,在动态地向gridpane添加定制(fxml)面板时,性能非常差。我认为主要的性能问题是fxmloader严重依赖于反射。
因此,在fxmlloader速度慢的情况下,最好的解决方案是使用一些替代fxmlloader的方法,这种方法性能更好,而且不依赖于反射。我相信javafx团队正在开发一个二进制等价的fxmloader(例如,fxml文件在构建阶段被预解析为二进制java类文件,可以快速加载到jvm中)。但是javafx团队还没有发布这项工作(如果存在的话)。tom schindl也做了类似的工作,它将fxml预编译成java源代码,然后可以将其编译成java类,所以你的应用程序也只是处理编译过的类,这应该是很好和快速的。
因此,使fxml加载速度更快的解决方案目前正在研究中,但在生产系统上并不稳定和可用。所以你需要其他方法来处理这个问题。
让你的表格更简单
在我看来,这似乎是一种逃避,但在我看来,你的“创建类”场景的设计有点复杂。您可能需要考虑将其替换为多阶段向导。这样的向导通常会加载得更快,因为您只需要在每个向导屏幕上加载少数项目。但更重要的一点是,这样的向导可能更易于使用,并且为用户提供了更好的设计。
只替换场景中需要修改的部分
您正在加载为每个新页面创建整个应用程序ui的fxml文件。但是您不需要这样做,因为像顶部菜单、状态栏和导航侧栏这样的东西不会仅仅因为用户加载了一个新表单而改变——只有显示“create classes”表单的中心部分在改变。因此,只需为正在更改的场景部分加载节点,而不是整个场景内容。
此外,通过在每个阶段替换整个ui,这将有助于解决应用程序中的其他问题。当您替换导航菜单时,菜单不会自动记住并突出显示导航树中当前选定的项目-您必须明确记住它,并在执行导航后再次重置它。但是,如果不替换整个场景内容,导航菜单将记住上次选择的内容并显示它(因为导航菜单本身在导航时没有更改)。
缓存fxml加载节点树和控制器
在应用程序中一次只能显示一个“createclasses”窗体。因此,只需使用fxmlloader加载“create classes”表单一次。这将为窗体创建一个节点树。定义一个静态hashmap,将“create classes”Map到createclassescontroller对象(应用程序中也只有一个)。当您导航到“createclasses”屏幕时,通过从hashMap中检索控制器来查看您以前是否去过那里。如果已有一个控制器类,请查询它以获取窗体的根窗格,并通过用新窗体替换场景的中心面板在场景中显示窗体。您可以在控制器上添加额外的方法,可以调用这些方法来清除窗体中的任何现有数据值或设置从网络获取任务加载的任何数据值。
除了加快应用程序的速度外,您现在还有一个优势,即“createclasses”窗体的状态将一直保持到您或用户决定清除它为止。这意味着用户可以浏览并部分填写表单,然后在应用程序中的其他地方返回表单,表单将处于与他们离开表单时相同的状态,而不是忘记用户之前输入的所有内容。
现在,因为您只加载了一次“createclasses”表单,所以您可以在启动时加载所有表单(并且有一个预加载程序页面,指示您的应用程序正在初始化)。这意味着,应用程序的初始启动速度将较慢,但应用程序的操作将很快。
建议设计
为应用程序中的不同面板部分创建窗体(导航栏、“创建类”窗体、“主屏幕”等)。
仅在javafxui线程上创建和操作ui元素。
仅替换导航上的面板部分,而不是整个场景。
将fxml预编译到类文件。
如有必要,使用闪屏预加载程序。
抽象网络和数据获取代码到自己的线程中。
重用为面板窗体创建的缓存节点树,而不是重新创建它们。
当新的网络数据可用时,将其传输到ui线程并填充它

相关问题