场景加载太慢

时间:2014-03-11 14:04:50

标签: java netbeans javafx javafx-2


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

目前我所做的是(或多或少)这个:

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

在加载更复杂的场景(初始化Scene s,TableView等等Combobox s之间的转换时,上述工作正常且速度足够简单Scene但是很多秒都很烦人。

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

谢谢。

修改
任何有兴趣帮助解决这个问题的人,甚至是他们的项目想法,我都在google.com上传了我项目的一部分(Netbeans项目)。
你可以使用SVN查看它。这是链接:
http://tabularasafx.googlecode.com/svn/trunk/
userName:tabularasafx-read-only
无需密码
运行项目后的说明:
第一个屏幕是登录屏幕,只需单击确定
第二个屏幕是“homePage”,在那里你可以看到一个treeView菜单并导航到4个不同的屏幕
我的问题是类的加载时间 - >创建页面。看看它,如果你发现任何东西,请告诉我 修改
我对@jewelsea建议进行了3次修改 1.我使用HashMap保留每个屏幕的所有控制器 2.我只更新场景的一部分而不是整个场景
3.我使用JavaFX2 - very poor performance when adding custom made (fxml)panels to gridpane dynamically的答案来帮助控制器加载更快,如答案所述。

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

3 个答案:

答案 0 :(得分:20)

一些背景

我看了你的Dimitris项目。

我为你的负载创建时间(OS X 10.9,2012 Macbook Air上的Java 8 b129)计划了#34;类创建"页。我花了一秒多的时间。

为了简化测试,我删除了使用并发服务加载新FXML的部分,并在请求时直接在JavaFX应用程序线程上加载FXML - 使用这种方式更容易。

很抱歉这里的答案很长。这样的事情通常不适合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的即时编译器运行,后续显示类 - >创建的请求变得更快,所以在打开场景两到三次之后它需要大约250ms(或四分之一秒)。

FXMLLoader很慢

在剩余的250ms加载中,大约2ms用于初始化代码,JavaFX渲染控件花费另外2ms,FXMLLoader花费其他246ms加载FXML并实例化节点进入场景

使用UI代码的想法是,您希望将转换的目标时间降低到< 16到30ms。这将使用户快速顺利地过渡。

将您的用户界面代码与网络和数据库代码分开

网络和数据库调用最好是通过JavaFX应用程序线程完成的,因此您可以使用JavaFX并发工具来包装这些任务。但我建议将问题分开。使用并发服务来获取数据,但是一旦获得数据,使用Platform.runLater或Task返回值来传输JavaFX应用程序线程的数据并在JavaFX应用程序线程上运行填充(因为该填充任务将是反正很快)。

通过这种方式,您可以将系统中的多线程划分为不同的逻辑组件 - 网络在其自己的线程上运行,UI操作在不同的线程上运行。它使事情更容易推理和设计。可以认为它有点像Web编程,其中ajax调用同时向UI提取数据,然后提供调用以将数据处理到UI中的回调。

执行此操作的另一个原因是许多网络库无论如何都带有自己的线程实现,因此您只需使用它而不是生成自己的线程。

如何使FXML加载更快

您不应该需要多线程代码来加载FXML文件。 FXML的初始化功能运行得非常快(仅几毫秒)。 FXMLLoader需要250ms。我没有详细介绍它,看看为什么会这样。但塞巴斯蒂安对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。但是你不需要这样做,因为顶级菜单,状态栏和导航侧边栏之类的东西不会因为用户加载新表单而改变 - 只有“#34;创建类& #34;表格正在变化。因此,只需加载正在更改的场景部分的节点,而不是整个场景内容。

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

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

您只会展示单个"创建课程"在申请表内的表格。因此,您只需要使用FXMLLoader来加载"创建类"形成一次。这将为表单创建一个节点树。定义一个静态HashMap,映射"创建类"到CreateClassesController对象(在应用程序中也只有一个)。导航到"创建类"通过从哈希映射中检索控制器,查看您之前是否已经访问过屏幕。如果已存在现有控制器类,则查询它以获取表单的根窗格,并通过用新表单替换场景的中心面板在场景中显示表单。您可以在控制器上添加额外的方法,您可以调用这些方法来清除表单中的任何现有数据值,或者设置从网络提取任务加载的任何数据值。

除了加速您的应用程序之外,您现在还具有以下优势:"创建类"表格一直保留到您或用户决定清除它。这意味着用户可以通过并部分填写表单到应用程序中的其他位置然后返回到表单,它将处于与它们离开时相同的状态,而不是忘记用户之前输入的所有内容。

现在因为你加载"创建类"只有一次表单,你可以在启动时加载所有表单(并有一个预加载页面,表明你的应用程序正在初始化)。这意味着应用程序的初始启动速度会较慢,但应用程序的操作会很快。

推荐设计

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

    遵循SceneBuilder implementation itself使用的原则 - 这是一个合理规模的JavaFX项目的最佳设计示例,该项目使用FXML作为其UI。 SceneBuilder代码是开源的,并以BSD样式许可证分发,因此很适合学习。

    <强>结果

    我对这个答案中提到的一些想法进行了原型设计,这减少了&#34;创建类的初始加载时间&#34;屏幕从一秒钟到大约400毫秒(第一次加载屏幕)。我没有用其他东西替换FXMLLoader(我相信它会大大降低400ms的值)。随后加载&#34;创建类&#34;基于刚刚重新添加到场景中的缓存节点树的表单大约需要4ms - 因此,就用户而言,操作性能是即时的。

    更新其他问题

      

    您是否认为我应该使用Tom Schindl的解决方案来编译FXML,或者它也是&#34;太过Beta&#34;?

    我的猜测是(截至今天)它是&#34;太过Beta&#34;。但请亲自尝试一下,看看它是否符合您的需求。为了支持Tom的FXML =&gt; JavaFX编译器发布到e(fx)clipse forums,因为该项目属于e(fx)clipse project的更大范围。

      

    我试过了stage.getScene()。setRoot(service.getValue()。getRoot());&#39;但得到了OutOfMemoryError:Java堆空间你认为该行导致它还是不相关?

    我正在对您的代码进行一些分析,作为创建此答案的一部分(通过将NetBeans profiler附加到已运行的应用程序实例)。我注意到每次&#34;创建课程&#34;场景被你的程序加载,内存使用量会显着增长,内存似乎没有被释放。我没有花时间试图找出原因是什么,但那是未修改的代码分析。所以我怀疑系统耗尽内存的最终原因与你是换掉一个场景还是换掉一个场景根无关。我注意到CSS伪造类消耗了大量内存,但我无法告诉你原因。我的猜测是,如果您遵循本答案中概述的原则,那么总的来说,您的应用程序将更加高​​效,并且您可以规避当前代码中存在的与内存相关的问题。如果没有,您可以继续分析应用程序内存使用情况,以查看根本问题。

答案 1 :(得分:1)

场景的速度将与您从一开始就尝试在场景中初始化的对象数量有很大关系。因此,如果您可以将该数字降低到最小值并在场景运行时创建其余部分,那么一切都将运行得更顺畅。如果在您的情况下这是不可能的,可以尝试限制其他事项以减轻系统的压力。

答案 2 :(得分:0)

如果使用的是Scene Builder,请以管理员身份在外部(而不是从IDE端)运行它。 问题将会解决。

相关问题