As everybody who is interested in JavaFX knows by now, JavaFX Mobile was released in February 2009. This article captures lessons we learned while preparing the release and provides some hints on how to improve the performance of JavaFX Mobile applications. DISCLAIMER: The tips I am giving here are true for the current version of JavaFX Mobile at the time of this writing, which is part of the JavaFX 1.1 SDK. In future versions, the behavior may change, and so the current undesirable performance of the mentioned artifacts will be optimized away or at least significantly improved. Everything I am writing about here is a snapshot for the current release only. Avoid unnecessary bindingsBindings are very convenient, without any doubt one of the most valuable innovations in JavaFX Script. Unfortunately they come with a price. The generated boiler-plate code is usually not as small and fast as a manual implementation would be. Very complex dependency-structures tend to impose a severe penalty on performance and footprint. For this reason, avoid bindings when possible. Often the same functionality can be implemented with triggers. One should not use bindings to avoid the hassle of dealing with the initialization order. And it certainly makes no sense to bind to a constant value. Lazy bindings are usually (but not always) faster if a bound variable is updated more often then read, but they are still not as fast as manual implementations. Code SamplesA common use-case is a scene, in which the position and size of the elements depend on the stage-size. A typical implementation uses bindings to achieve that. The following simple example resembles such a situation: The scene consists of three rectangles that are laid out diagonally from the top-left to the bottom-right. The size of each rectangle is a quarter of the screen-size. Code Sample 1 shows an implementation with bindings. Code Example 1: Layout calculated with bindings 1 def rectangleWidth: Number = bind stage.width * 0.25; 2 def rectangleHeight: Number = bind stage.height * 0.25; 3 4 def stage: Stage = Stage { 5 scene: Scene { 6 content: for (i in [0..2]) 7 Rectangle { 8 x: bind stage.width * (0.125 + 0.25*i) 9 y: bind stage.height * (0.125 + 0.25*i) 10 width: bind rectangleWidth 11 height: bind rectangleHeight 12 } 13 } 14 } Consider the question of whether the bindings are really necessary. On a real device ,the screen-size changes only when the screen orientation is switched (provided that the device supports this functionality). If our application does not support screen rotation, the layout can be defined as constant. One way to reduce the number of bindings is shown in Code Sample 2. Two variables (width and height) are introduced and bound to stage.width and stage.height respectively. Their only purpose is to provide triggers for stage.width and stage.height, since we do not want to override the original triggers. The position and size of the rectangles are calculated manually in the triggers. Code Sample 2: Layout calculated in trigger 1 def r = for (i in [0..2]) Rectangle {} 2 3 def stage = Stage { 4 scene: Scene {content: r} 5 } 6 7 def height = bind stage.height on replace { 8 def rectangleHeight = height * 0.25; 9 for (i in [0..2]) { 10 r[i].height = rectangleHeight; 11 r[i].y = height * (0.125 + 0.25*i) 12 } 13 } 14 15 def width = bind stage.width on replace { 16 def rectangleWidth = width * 0.25; 17 for (i in [0..2]) { 18 r[i].width = rectangleWidth; 19 r[i].x = width * (0.125 + 0.25*i) 20 } 21 } Without a doubt, the code in Code Sample 1 is more elegant. But when measuring the performance of both snippets in the emulator, it turned out the code in Code Sample 2 is almost twice as fast. Keep the scenegraph as small as possibleBehind the scenes of the runtime, a lot of communication takes place to update the variables of the nodes in a scenegraph. The more elements a scenegraph has, the more communication is required. Therefore it is critical to keep the scenegraph as small as possible. Animation performance especially tends to suffer from a large scenegraph. It is poor practice to keep a node in the scenegraph at all times and make it invisible via the visible-flag or its opacity. Invisible nodes in the scenegraph are still part of the communication-circus in the background. Instead, remove nodes from the scenegraph and add them only when required. This approach has one drawback however. Adding or removing nodes takes longer than setting the visibility. Therefore it might not be appropriate in situations where immediate responses are critical. Code SamplesYou often have a set of nodes of which only one is visible. These can be, for example different pages, or nodes to visualize different states of an element. One might be tempted to add all nodes to the scenegraph and set only the current as visible. Code Sample 1 shows a simplified version of this approach. Three colored circles (red, yellow, green) are created to visualize some kind of state. Only one node is visible at any time. (Let's ignore for a second that this could simply be achieved by changing the fill-color of a single circle. In real-life applications one would probably have images or more complex shapes for visualizations and simply changing the color would not work.) Code Sample 3: Using visibility to switch between nodes 1 def colors = [Color.GREEN, Color.YELLOW, Color.RED]; 2 3 var state: Integer; 4 5 Stage { 6 scene: Scene { 7 content: for (i in [0..2]) 8 Circle { 9 centerX: 10 10 centerY: 10 11 radius: 10 12 fill: colors[i] 13 visible: bind state == i 14 } 15 } 16 } This results in three nodes in the scenegraph although only one is shown. This should be refactored to ensure that only the visible node is in the scenegraph. The following code sample shows one possible implementation. Code Sample 4: Adding and removing nodes when required 1 def colors = [Color.GREEN, Color.YELLOW, Color.RED]; 2 3 var state: Integer on replace oldValue { 4 insert nodes[state] into stage.scene.content; 5 delete nodes[oldValue] from stage.scene.content; 6 } 7 8 9 def nodes = for (i in [0..2]) 10 Circle { 11 centerX: 10 12 centerY: 10 13 radius: 10 14 fill: colors[i] 15 } 16 17 def stage = Stage {scene: Scene{}} The code in the first code sample is more compact, but the second sample reduced the number of nodes in the scenegraph from three to one. While tuning some of the demos for the JavaFX Mobile release, we were able to reduce the number of nodes in the scenegraph by 50% and more, simply by ensuring that only visible nodes are part of it. Code SamplesIf nodes are shown and hidden with some kind of animation, adding and removing the node to the scenegraph becomes extremely simple. You only need to implement an action at the beginning of the fadeIn-animation and at the end of the fadeOut-animation respectively to add and remove the node. The next sample shows such a usage where a simple message-box is shown and hidden by changing the opacity. Code Sample 5: Using fadeIn- and fadeOut-animations to add and remove nodes 1 def msgBox = Group { 2 opacity: 0.0 3 content: [ 4 Rectangle {width: 150, height: 40, fill: Color.GREY}, 5 Text {x: 20, y: 20, content: "Hello World!"} 6 ] 7 } 8 9 def fadeIn = Timeline { 10 keyFrames: [ 11 KeyFrame { 12 action: function() {insert msgBox into stage.scene.content} 13 }, 14 at (1s) {msgBox.opacity => 1.0 tween Interpolator.LINEAR} 15 ] 16 } 17 18 def fadeOut = Timeline { 19 keyFrames: KeyFrame { 20 time: 1s 21 values: msgBox.opacity => 0.0 tween Interpolator.LINEAR 22 action: function() {delete msgBox from stage.scene.content} 23 } 24 } 25 26 def stage = Stage {scene: Scene{}} Use simple shapes instead of images, but use small images instead of complex shapesThese two items seem to contradict each other. Unfortunately there is no single answer; sometimes using shapes is better, sometimes using an image is better. To help making the right decision, here are some points one should consider:
Important: The variable cache of Use the prescaling functionalityIf an image needs to be scaled and the scaling factor does not change later, you should use the prescaling functionality. This can be done by setting the width and height of an Image object, which will scale the image while it is loaded. Using prescaling has two benefits. Firstly, it results in better performance. If prescaling is used, the scaling is definitely calculated only once. By contrast, the scaling of an Scaling can be calculated to be even faster if the flag Code SamplesThis example generates thumbnails for a number of images. It creates a sequence of thumbnails, using the scaling functionality of Code Sample 6: Scaling within ImageView 1 def thumbnails = for (i in [0..11]) 2 ImageView { 3 image: Image {url: "{__DIR__}images/img{i}.png"} 4 preserveRatio: true 5 fitWidth: 30 6 fitHeight: 20 7 } The same can be achieved using the prescaling functionality of the Image class as shown in the following example. Displaying the thumbnails is usually faster with this approach and the memory usage is much smaller. Code Sample 7: Prescaling with Image 1 def thumbnails = for (i in [0..11]) 2 ImageView { 3 image: Image { 4 url: "{__DIR__}images/img{i}.png" 5 preserveRatio: true 6 width: 30 7 height: 20 8 } 9 } Use background loadingThe class Image provides a useful but easily overlooked feature: the ability to load images asynchronously in the background. This will not speed up the runtime performance or reduce the footprint of an application, but it can significantly improve startup time. To enable it, the flag I Background loading has two consequences, which need to be considered during implementation. If an image that is loaded in the background is supposed to be displayed shortly after creation, it is necessary to check the progress of the download. Otherwise an empty image will be shown first. Another option is to set the variable placeholder to display a substitute-image until the real image is finished loading. This approach is used in the example below. The second consequence is that the width and height of an image are not set until the image is completely loaded. This will probably spoil any layout, which depends on the size of the used images. Again, a placeholder image can be used to overcome this, if placeholder image and final image have the same size. Or width and height can be set manually, which would prescale the image to the given size. The last option is to recalculate the layout once the image is finished loading. Code SamplesThe next code sample extends the example from above to load the thumbnails in the background and displays them. While the images are loaded a placeholder image (logo.png) is displayed, which has the same size as the thumbnails. Note that the logo is not loaded in the background to make sure we can display it immediately. Code Sample 8: Loading thumbnails in the background 1 def logo = Image {url: "{__DIR__}images/logo.png"} 2 3 def thumbnails = for (i in [0..11]) 4 ImageView { 5 image: Image { 6 url: "{__DIR__}images/img{i}.png" 7 preserveRatio: true 8 width: 30 9 height: 20 10 backgroundLoading: true 11 placeholder: logo 12 } 13 x: i mod 4 * 50 + 20 14 y: ((i/4) as Integer) * 40 + 20 15 } 16 17 Stage {scene: Scene {content: thumbnails}} On the emulator, one has to look really closely to notice the background loading. On a real device, loading the images usually takes longer. With background loading enabled, the screen is displayed quickly, first showing only the placeholders, which are one after the other replaced with the real images. If background loading is disabled, the application would show a blank screen until all images are completely loaded and displayed. Define variables with def instead of var (make them script-private)When defining instance variables, it is good practice to limit the accessibility as much as possible. Also if a variable is immediately initialized and not reassigned afterwards, one should use the keyword Besides resulting in clearer and less error-prone code, following these suggestions also improves performance. The more hints we can provide to the compiler, the more aggressively it can optimize our code. Let's take a look at an example. Code Sample 9: A sample script with public, private defs, and vars 1 class Main { 2 def i1: Integer = 0; 3 var i2: Integer; 4 public def i3: Integer = 0; 5 public var i4: Integer; 6 } The preceding code sample defines a small class with four members (i1, i2, i3, and i4). The variables i1 and i2 are script-private; i3 and i4 are public. The variables i1 and i3 are defined with Code Sample 10: Parts of the generated Java code from Code Sample 1 1 class Main extends java.lang.Object implements Main$Intf,com.sun.javafx.runtime.FXObject{ 2 public int $Main$i1; 3 public int $Main$i2; 4 public int $i3; 5 public final com.sun.javafx.runtime.location.IntVariable $i4; 6 ... 7 } What is remarkable about the generated Java code is the fact that all variables but i4 became simple Use Integer instead of NumberOperations on integers are always faster than operations on floating-point values. On limited devices, which usually do not have a mathematical coprocessor like desktop computers, the difference is exorbitant. Therefore it is good practice to use Integers whenever possible. The type-inference mechanism of the JavaFX compiler usually does a very good job in determining the correct type of a variable. But if in doubt, it will chose Use functions of class SequencesThe class For more information
We welcome your participation in our community. Please keep your comments civil and on point. You can optionally provide your email address to be notified of repliesyour information is not used for any other purpose. By submitting a comment, you agree to these Terms of Use. | ||||||||||
|
| ||||||||||||