当前位置: 首页 > 面试题库 >

场景加载太慢

阎嘉荣
2023-03-14
问题内容

我正在构建JavaFX应用程序,我想知道是否有关于如何尽快加载Scene当前新内容的建议(最佳实践)Stage

当前我正在做的(或多或少)是这样的:

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

对于Scene加载简单的TableViews,Comboboxes等的复杂场景而言,上面的方法对于简单的s BUT
来说足够好且快速Scene

我在Controllerinitialize(URL url, ResourceBundle rb)方法中进行的所有初始化。
在那里,我将项目添加到Choice/Combo框中,进行初始化TableView等,但是正如我所说的,这花费了太多时间。
难道我做错了什么?我应该在其他地方初始化吗?

谢谢。

编辑:
任何对此有帮助的人,甚至对他们的项目有想法的人,我都在google.com上上传了我的项目(Netbeans项目)的一部分。
您可以使用SVN进行检查。这是链接:
http://tabularasafx.googlecode.com/svn/trunk/
用户名:tabularasafx-只读
不需要密码
运行项目后的说明:
第一个屏幕是登录屏幕,只需单击OK,
第二个屏幕是“主页”,您可以看到一个treeView菜单并导航到4个不同的屏幕
我的问题是类->创建页面的加载时间。看看它,让我知道是否找到任何内容。

编辑:
我进行了@jewelsea建议的3处更改。
1.我使用了HashMap来保留每个屏幕的所有控制器
2.我只更新了场景的一部分,而不是整个场景
。3.我使用了JavaFX2的答案-将自定义(fxml)面板添加到gridpane时,性能很差。动态地如答案中所述,以帮助控制器更快地加载。

现在一切都快得多了!!!!
随意使用该项目作为指导。
此外,我还更新了程序以浏览3个屏幕,以更好地理解
请注意,我的代码很乱


问题答案:

一些背景

我看了看你的项目Dimitris。

我为“类创建”页面计时了负载创建时间(OS X 10.9,2012 Macbook Air上的Java 8 b129)。对我来说只花了一秒钟。

为了简化测试,我删除了使用并发服务加载新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的回答中有一些迹象-将定制(fxml)面板动态添加到gridpane时,性能非常差。我认为主要的性能问题是FXMLLoader非常依赖反射。

因此,在出现缓慢的FXMLLoader问题的情况下,最好的解决方案是使用FXMLLoader的替代方法,该方法性能更好且不依赖于反射。我相信JavaFX团队正在研究FXMLLoader的二进制等效项(例如,在构建阶段将FXML文件预先解析为二进制Java类文件,然后可以将其快速加载到JVM中)。但是该工作(如果存在)尚未由JavaFX团队发布。Tom
Schindl已经完成了类似的工作,它可以将FXML预编译为Java源,然后可以将其编译为Java类,因此,您的应用程序仍在使用已编译的类,该类应该又好又迅速。

因此,使FXML更快加载的解决方案目前正在开发中,但在生产系统上并不是真正稳定和可用。因此,您需要其他方法来解决此问题。

使表格更简单

在我看来,这似乎是一个解决方案,但是IMO为“创建类”场景设计的内容有点复杂。您可能要考虑用多阶段向导替换它。这样的向导通常会更快地加载,因为您只需要在每个向导屏幕上加载少量项目即可。但更重要的一点是,这样的向导可能更易于使用,并且为用户提供了更好的设计。

仅替换场景中需要的部分

您正在加载FXML文件,这些文件将为每个新页面创建整个应用程序UI。但是您不需要这样做,因为诸如顶部菜单,状态栏和导航侧栏之类的内容并不会因为用户加载新表单而发生变化,只有显示“创建类”表单的中央部分才会发生变化。因此,只需加载要更改的场景部分而不是整个场景内容的节点。

此外,这将通过在每个阶段替换整个UI来帮助解决应用程序遇到的其他问题。替换导航菜单时,该菜单不会自动记住并突出显示导航树中当前选中的项目-
您必须去明确地记住它,并在进行导航后再次将其重置。但是,如果您不替换整个场景内容,则导航菜单会记住上一次选择的内容并显示出来(因为导航菜单本身在导航时不会发生变化)。

缓存FXML加载节点树和控制器

您在应用程序中一次只能显示一个“创建类”表单。因此,您只需要使用FXMLLoader一次加载“创建类”表单。这将为表单创建一个节点树。定义一个静态HashMap,将“创建类”映射到CreateClassesController对象(在应用程序中,该对象也只有一个)。当您导航到“创建类”屏幕时,通过从哈希映射中检索控制器,来查看您以前是否曾经去过那里。如果已经存在一个控制器类,请对其进行查询以获取表单的根窗格,并通过用新表单替换场景的中央面板来在场景中显示该表单。

除了可以加快应用程序的速度外,您现在还可以保持“创建类”表单的状态,直到您或用户决定清除它为止。这意味着用户可以浏览并部分填写表单,然后再返回到应用程序中的其他位置,然后返回到表单,表单将保持与离开表单相同的状态,而不会忘记用户之前输入的所有内容。

现在,由于仅加载一次“创建类”表单,因此可以在启动时加载所有表单(并具有一个预加载器页面,该页面指示您的应用程序正在初始化)。这意味着应用程序的初始启动将较慢,但应用程序的运行将很快。

建议设计

  1. 为应用程序中的不同面板部分创建表单(导航栏,“创建类”表单,“主屏幕”等)。
  2. 仅在JavaFX UI线程上创建和操作UI元素。
  3. 仅替换导航中的面板部分,而不替换整个场景。
  4. 将FXML预编译为类文件。
  5. 如有必要,请使用启动画面预加载器。
  6. 抽象网络和数据将代码提取到其自己的线程中。
  7. 重用为面板表单创建的缓存节点树,而不是重新创建它们。
  8. 当有新的网络数据可用时,将其传输到UI线程并将其填充到缓存的节点树中。

查看SceneBuilder实施

遵循使用SceneBuilder实现本身的原理-
对于使用FXML作为其UI的合理大小的JavaFX项目,这是当前最佳的设计示例。SceneBuilder代码是开源的,并根据BSD样式许可进行分发,因此它很容易研究。

结果

我对该答案中提到的一些想法进行了原型设计,这将“创建类”屏幕的初始加载时间从一秒以上减少到了约400毫秒(首次加载屏幕)。我没有用其他东西代替FXMLLoader(我相信这会大大降低400ms的值)。随后,基于刚刚重新添加到场景中的缓存节点树的“创建类”表单的加载大约花费了4毫秒-
因此,就用户而言,操作性能是瞬时的。

更新其他问题

您认为我应该使用Tom Schindl的解决方案来编译FXML还是“太Beta”?

我的猜测是(截至今天)它是“太Beta”。但是请自己尝试一下,看看它是否满足您的需求。要获得Tom’s FXML =>
JavaFX编译器的支持,请将该项目发布到e(fx)clipse论坛,因为该项目属于e(fx)clipse项目的大伞。

我尝试了’stage.getScene()。setRoot(service.getValue()。getRoot());’
但是出现OutOfMemoryError:Java堆空间是您认为该行引起的还是不相关的?

作为创建此答案的一部分,我正在对您的代码进行概要分析(通过附加NetBeans分析器到已经运行的应用程序实例)。我确实注意到,每次在您的程序中加载“创建类”场景时,内存使用量都会显着增加,并且似乎没有释放内存。我没有花时间试图找出原因,但这是在未经修改的情况下分析您的代码。因此,我怀疑系统内存不足的最终原因与换出场景还是换出场景根无关。我注意到CSS伪类消耗了大量内存,尽管我无法告诉您这样做的原因。我的猜测是,如果您遵循此答案中概述的原则,那么总的来说,您的应用程序将更加高​​效,并且您可以规避当前代码中存在的与内存相关的问题。如果不,



 类似资料:
  • 在 Cocos Creator 中,我们使用场景文件名(不包含扩展名)来索引指代场景。并通过以下接口进行加载和切换操作: cc.director.loadScene("MyScene"); 通过常驻节点进行场景资源管理和参数传递 引擎同时只会运行一个场景,当切换场景时,默认会将场景内所有节点和其他实例销毁。如果我们需要用一个组件控制所有场景的加载,或在场景之间传递参数数据,就需要将该组件所在节点

  • 在 Cocos Creator 3D中,我们使用场景文件名(不包含扩展名)来索引指代场景。并通过以下接口进行加载和切换操作: director.loadScene("MyScene"); 通过常驻节点进行场景资源管理和参数传递 引擎同时只会运行一个场景,当切换场景时,默认会将场景内所有节点和其他实例销毁。如果我们需要用一个组件控制所有场景的加载,或在场景之间传递参数数据,就需要将该组件所在节点标

  • 我有一个问题,很难解释,但我会尽我最大的努力以最好的方式描述它。我的项目中有这个FXML(场景)(请参见行李概述)。当我点击“编辑所选行李”时,它会打开一个新的FXML文件并显示为弹出窗口。然而,由于一些奇怪的原因,当我尝试使用。setText或其他任何内容单击“编辑选定行李”按钮后,若要更改窗口上显示的输入字段,则会出现以下错误: 这是我用来使场景弹出的方法: 控制器: 我的行李。FXML Lu

  • 问题内容: 我有2个fxml文件: 布局(标题,菜单栏和内容) Anchorpane(应该放在另一个fxml文件的内容中) 我想知道如何从“主”场景将第二个文件加载到内容空间内。在javaFX中工作是一件好事,还是加载一个新场景更好? 我正在尝试做这样的事情,但是不起作用: 谢谢您的帮助。 问题答案: 为什么您的代码不起作用 加载程序会创建一个新的AnchorPane,但是您绝不会将新窗格添加到场

  • 我正在netbeans中开发JavaFx应用程序,在netbeans中该项目正在构建并运行良好。 加载屏幕的屏幕控制器: 我的github回购:https://github.com/eszikk/examsupervisorserver

  • null 我正试着做这样的事情,但它不起作用: 谢谢你的帮助。