场景加载太慢 [英] Scene loads too slow

查看:107
本文介绍了场景加载太慢的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



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


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

 父root =(父)myFXLoader.load(); 
currentStage.setScene(新场景(root);

以上工作正常且足够快for simple Scene s但是在加载更复杂的场景时,初始化 TableView s, Combobox es等场景之间的过渡需要很多秒才很烦人。


我在里面做的所有初始化控制器初始化(URL url,ResourceBundle rb)方法。

我添加了项目到选择/组合框,初始化 TableView 等,但正如我所说,这需要太多时间。


我做错了什么?我应该在其他地方初始化吗?


谢谢。



编辑:

任何有兴趣帮助解决这个问题的人,甚至是他们项目的想法,我已经在google.com上传了我项目的一部分(Netbeans项目)。

您可以使用SVN查看。这是链接:

http://tabularasafx.googlecode.com/svn/trunk/

userName:tabularasafx-read-only

无需密码

运行项目后的说明:

首屏是登录界面,只需单击确定


第二个屏幕是homePage,在那里你可以看到一个treeView菜单并导航到4个不同的屏幕

我的问题是类的加载时间 - >创建页面。看看它,让我知道你是否发现任何东西


编辑:

我做了3次更改@jewelsea建议。 >
1.我使用HashMap来保存每个屏幕的所有控制器

2.我只更新场景的一部分而不是整个场景

3.我使用了 JavaFX2的答案 - 在向网格面板动态添加自定义制作(fxml)面板时性能非常差,以帮助控制器加载更快,如上所述在答案中。


现在一切都快得多!!!!

随意使用该项目作为指导方针

另外我更新程序通过3个屏幕导航以便更好地理解

请注意我的代码很乱

一些背景



我看了你的项目Dimitris。



我计划了你的时间加载创建时间(OS X 10.9,2012 Macbook A上的Java 8 b129) ir)用于类创建页面。我花了一秒多的时间。



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



很抱歉这里的答案很长。这样的事情通常不适合StackOverflow,它们在教程或博客形式中最好,但我很好奇发生了什么,所以我想我需要一些时间来研究它并写下它up。



不为每个加载的FXML创建新场景



每次加载FXML时都设置一个新场景(使用新的大小)。无论出于何种原因,这是一项相当昂贵的操作,您无需这样做。你已经在你的舞台上有一个场景,只需重复使用它。所以替换下面的代码:

  stage.setScene(new Scene(service.getValue()。getRoot(),service.getValue ()。getX(),service.getValue()。getY())); 

with:

  stage.getScene()setRoot(service.getValue()getRoot()。)。 

这将在加载时间上节省超过半秒,所以现在class-> create需要第一次运行时为400毫秒。



此更改是一个简单的性能获胜的示例。



它还提供了更好的用户体验,如上所示我的机器在你改变场景时舞台闪烁灰色,但当你只是替换现有场景的场景根时,没有灰色闪光。



因为JVM运行使用Java的即时编译器,后续显示类 - > create的请求变得更快,因此在打开场景两三次后需要大约250ms(或四分之一秒)。



FXMLLoader很慢



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



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



将您的UI代码与网络和数据库代码分开



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



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



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



如何使FXML加载更快



您不应该真正需要多线程代码加载FXML文件。 FXML的初始化功能运行得非常快(只需几毫秒)。 FXMLLoader需要250ms。我没有详细介绍它,看看为什么会这样。但是,Sebastian对 JavaFX2的回答有一些迹象 - 在向网格面板动态添加自定义制作(fxml)面板时性能非常差。我认为主要的性能问题是FXMLLoader在很大程度上依赖于反射。



因此,在FXMLLoader较慢的情况下,最佳解决方案是使用FXMLLoader的替代方案,该方案性能更好,不依赖于反射。我相信JavaFX团队正在研究FXMLLoader的二进制等价物(例如,FXML文件在构建阶段被预先解析为二进制Java类文件,可以快速加载到JVM中)。但JavaFX团队尚未发布该工作(如果存在)。 Tom Schindl做了类似的工作,将FXML预先编译为Java源代码,然后可以将其编译为Java类,因此您的应用程序再次使用已编译的类,这应该是很好的和快速的。



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



使表单更简单



<这对我来说可能看起来像是一个副作用,但IMO对你的创建类场景的设计有点复杂。您可能需要考虑使用多阶段向导替换它。这样的向导通常会加载更快,因为您只需要在每个向导屏幕上加载一些项目。但更重要的一点是,这样的向导可能更容易使用,并为用户提供更好的设计。



仅替换场景中的部分需要



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



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



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



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



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



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



推荐设计


  1. 为您应用中的不同面板部分创建表单(导航栏,创建课程表单,主屏幕等)。

  2. 仅在JavaFX UI线程上创建和操作UI元素。

  3. 仅替换导航上的面板部分,而不是整个场景。

  4. 将FXML预编译为类文件。

  5. 如有必要,请使用启动画面预加载器。

  6. 抽象网络和数据将代码提取到自己的线程中。

  7. 重用为面板表单创建的缓存节点树而不是重新创建它们。

  8. 当新网络数据可用时,将其传输到UI线程并填充它进入缓存节点树。

查看SceneBuilder实施



遵循 SceneBuilder实现本身 - 它是一个合理规模的JavaFX项目的最佳设计示例,该项目利用FXML进行UI。 SceneBuilder代码是开源的,并以BSD风格许可证分发,因此很适合学习。



结果



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



更新其他问题


您认为我应该使用Tom Schindl的解决方案进行编译吗? FXML还是太Beta了?


我的猜测是(截至今天)它是太Beta。但是亲自尝试一下,看看它是否符合您的需求。有关Tom的FXML => JavaFX编译器的支持,请发布到 e(fx) clipse论坛,因为该项目属于 e(fx)clipse项目的大范围。


我试过'stage.getScene()。setRoot(service.getValue()。getRoot());'但得到OutOfMemoryError:Java堆space你认为这行是什么导致它还是不相关?


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



I am building a JavaFX application and I am wondering if there is a recommendation (best practice) on how to load new Scene in current Stage as fast as possible.

Currently what I am doing is (more or less) this:

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

The above works fine and fast enough for simple Scenes BUT when loading more complicated scenes that initialize TableViews, Comboboxes etc the transition between Scenes takes many seconds which is annoying.

All the initialization I do in inside the Controller's initialize(URL url, ResourceBundle rb) method.
There I add the items to the Choice/Combo boxes, initialize TableView etc but as I said, it takes too much time.
Am i doing something wrong? Should I initialize somewhere else?

Thank you.

EDIT:
Anyone interested in helping with this, or even get ideas for their project, I have uploaded a part of my project (Netbeans project) at google.com.
You can check it out using SVN. This is the link:
http://tabularasafx.googlecode.com/svn/trunk/
userName: tabularasafx-read-only
no password required
Instructions after you run the project:
First screen is login screen, just click OK
Second screen is "homePage", there you can see a treeView menu and navigate to 4 different screens
My issue is the loading time of classes->create page. Take a look at it and let me know if you find anything

EDIT:
I made 3 changes that @jewelsea suggested.
1. I used a HashMap to keep all the controllers for each screen
2. I update only part of the Scene and not the whole Scene
3. I used the answer of JavaFX2 - very poor performance when adding custom made (fxml)panels to gridpane dynamically to help the controllers load faster as described in the answer.

Everything is much much faster now!!!!
Feel free to use the project as a guideline
Also I update the program to navigate through 3 screens for better understanding
note that my code is a messy

解决方案

Some Background

I took a look at your project Dimitris.

I timed your load creation time (Java 8 b129 on OS X 10.9, 2012 Macbook Air) for the "classes create" page. It took just over a second for me.

To simplify testing I removed the section where you load new FXML using a concurrent service and just loaded the FXML directly on the JavaFX application thread when it was requested - its a lot easier to work with that way.

Sorry for the long answer here. Things like this usually don't fit well into StackOverflow, they end up best in a tutorial or blog kind of form, but I was curious what was going on, so I thought I'd take some time to look into it and write it up.

Don't create a new Scene for every FXML you load

You set a new scene (with a new size) every time you load the FXML. For whatever reason, this is a pretty expensive operation and you don't need to do it. You already have a scene in your stage, just reuse that. So replace the following code:

stage.setScene(new Scene(service.getValue().getRoot(), service.getValue().getX(), service.getValue().getY()));

with:

stage.getScene().setRoot(service.getValue().getRoot());

This will save just over half a second on the load time, so now classes->create takes about 400 milliseconds the first time it is run.

This change is an example of an easy performance win.

It also provides a nicer user experience as on my machine the stage flashed gray while you were changing scenes, but when you just replace the scene root of an existing scene, there was no gray flash.

Because the JVM runs with a just in time compiler for Java, subsequent requests to display classes->create go faster, so after opening the scene two or three times it takes about 250ms (or quarter of a second).

The FXMLLoader is slow

Of the remaining 250ms to load, about 2ms is spent in your initialization code, another 2ms is spent by JavaFX rendering the controls and the other 246ms are spent by the FXMLLoader loading up the FXML and instantiating the nodes to go into your scene.

The idea with UI code is you want to get the target time for a transition down to < 16 to 30ms. That will make the transition quick and smooth for the user.

Separate your UI code from your Network and Database Code

Network and database calls are things which are best done off of the JavaFX application thread, so you can use the JavaFX concurrency tools to wrap those tasks. But I'd recommend separating concerns. Use concurrent services to fetch data, but once you have the data back, use Platform.runLater or a Task return value to transfer the data the JavaFX application thread and run the population on the JavaFX application thread (because that population task is going to be pretty quick anyway).

This way you have compartmentalized the multithreading in the system to different logical components - networking runs on its own thread and UI operations run on a different thread. It makes stuff easier to reason about and design. Think of it a bit like web programming, where an ajax call fetches data concurrently to the UI, then provides a callback that is invoked to process the data into the UI.

The other reason to do this is that many networking libraries come with their own threading implementations anyway, so you just use that rather than spawning your own threads.

How to make FXML Load Quicker

You shouldn't really need multi-threaded code for loading FXML files. The initialize function of your FXML runs extremely quickly (just a couple of milliseconds). The FXMLLoader takes 250ms. I haven't profiled it in detail to see why that is the case. But there are some indications in Sebastian's answer to JavaFX2 - very poor performance when adding custom made (fxml)panels to gridpane dynamically. I think the main performance issue is that the FXMLLoader relies so heavily on reflection.

So the best solution in situations where a slow FXMLLoader is an issue would be to use some alternative to the FXMLLoader which performs better and doesn't rely on reflection. I believe the JavaFX team are working on a binary equivalent of the FXMLLoader (e.g. the FXML files are pre-parsed in the build stage into binary Java class files which can be quickly loaded into the JVM). But that work (if it exists) is not released by the JavaFX team yet. A similar piece of work has been done by Tom Schindl, which pre-compiles the FXML to Java source, which can then be compiled to Java classes, so again your app is just working with compiled classes, which should be nice and speedy.

So the solutions to make FXML load quicker are currently in the works, but not really stable and usable on a production system. So you need other ways to deal with this issue.

Make your forms simpler

This may seem like a cop-out on my part, but IMO the design you have for your "create classes" scene is a bit complicated. You might want to consider replacing it with a multi-stage wizard. Such a wizard will generally load faster as you only need to load a handful of items on each wizard screen. But the more important point is that such a wizard is probably easier to use and a better design for your users.

Replace only the sections of your scene that you need to

You are loading FXML files which create your whole application UI for each new page. But you don't need to do this because things like the top menu, status bar and navigation sidebar don't change just because the user loads a new form - only the central section where the "create classes" form is displayed is changing. So just load up the nodes for the part of the scene that is changing rather than the entire scene contents.

Additionally this will help fix other issues that you will have with your application by replacing the whole UI at each stage. When you replace the navigation menu, the menu doesn't automatically remember and highlight the currently selected item in the navigation tree - you have to go and explicitly remember it and reset it again after doing a navigation. But if you weren't replacing the whole scene contents, the navigation menu would remember what was last selected and display it (because the navigation menu itself isn't changing on navigation).

Cache FXML load node trees and controllers

You are only ever displaying a single "create classes" form at a time within the application. So you only need to use the FXMLLoader to load the "create classes" form once. That will create a tree of nodes for the form. Define a static HashMap that maps "create classes" to the CreateClassesController object (of which you also have only one in the application). When you navigate to the "create classes" screen, see if you have already been there before, by retrieving the controller from your hash map. If there is already an existing controller class, query it to get the root pane for form and display the form in your scene by replacing the center panel of your scene with the new form. You can add extra methods on the controller that you can call to clear any existing data values in the form or to set any data values which you have loaded from a network fetching task.

In addition to speeding up your application, you now have the advantage that the state of the "create classes" form is kept until you or the user decide to clear it. This means that the user can go through and partially fill out the form go somewhere else in the application then return to the form and it will be in the same state as they left it rather than forgetting everything the user entered before.

Now because you load the "create classes" form only once, you could load up all of the forms at startup (and have a preloader page which indicates that your application is initializing). This means that initial startup of the app will be slower, but operation of the app will be quick.

Suggested Design

  1. Create forms for different panel sections in your app (nav bar, "create class" form, "home screen", etc).
  2. Create and manipulate UI elements only on the JavaFX UI thread.
  3. Only replace panel sections on navigation, not entire scenes.
  4. Precompile FXML to class files.
  5. Use a Splash Screen pre-loader if necessary.
  6. Abstract networking and data fetching code into its own thread.
  7. Reuse cached node trees created for panel forms rather than recreating them.
  8. When new network data is available, transfer it to the UI thread and fill it into a cached node tree.

Review the SceneBuilder Implementation

Follow the principles used the SceneBuilder implementation itself - it is the best current design example for a reasonably sized JavaFX project that makes use of FXML for its UI. The SceneBuilder code is open source and distributed under a BSD style license, so its good to study.

Outcome

I prototyped some of the ideas mentioned in this answer and this cut the initial load time of the "create classes" screen down from over a second to about 400ms (for the first time the screen is loaded). I didn't replace the FXMLLoader with something else, (which I am sure would have greatly decreased the 400ms value). Subsequent loads of the "create classes" form based a cached node tree that was just re-added to the scene took about 4ms - so operational performance was instantaneous as far as the user was concerned.

Update for Additional Questions

Do you think that I should use Tom Schindl's solution for compiling FXML or is it "too Beta"?

My guess is that (as of today) it is "too Beta". But try it out for yourself and see if it meets your needs. For support on the Tom's FXML => JavaFX compiler, post to the e(fx)clipse forums, as the project falls under the larger umbrella of the 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?

I was doing some profiling of your code as part of creating this answer (by attaching the NetBeans profiler to an already running instance of your application). I did notice that every time the "create class" scene was loaded by your program, the memory usage would grow quite significantly and the memory did not seem to be released. I didn't spend time trying to track down what the reason for that was, but that was profiling your code unmodified. So I suspect that the ultimate cause of the system running out of memory is not to do with whether you swap out a scene or just swap out a scene root. I noticed a lot of memory was consumed by CSS psuedo-classes, though I couldn't tell you the reason for that. My guess is that if you follow the principles outlined in this answer, then overall, your application will be a lot more efficient and you may circumvent the memory related issues present in your current code. If not, you could continue to profile the application memory usage to see what the root issues are.

这篇关于场景加载太慢的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆