Desktop Java

Best Practices for JavaFX Mobile Applications, Part 2

WARNING: The tips I am giving here are true for the current version of JavaFX Mobile, which is part of the JavaFX 1.1 SDK. In future versions the behavior will change, the current bad performance of the mentioned artifacts will be optimized away or at least significantly improved. Everything I am writing about here is a snap-shot, nothing should be understood as

final!

Item 3: Use simple shapes instead of images
Item 4: Use small images instead of complex shapes

These two items seem to contradict each other. Unfortunately there is no easy answer available here: sometimes using shapes is better, sometimes using an image is better. To help making the right decision, here are some points one should consider:

  • Complexity Single basic shapes like rectangles or circles are almost always faster than images. But the higher the number of shapes, which are assembled to get the desired artifact, or the more complex a user-defined path is, the more expensive operations on these shapes are. And the advantage shrinks. Important note: A javafx.text.Text object is a very complex shape.
  • Size The performance of most operations on images behaves quadratic, which means the operations become 4 times as slow if width and height are doubled, 9 times as slow if they are tripled etc. Therefore the bigger an element is, the better is using shapes.
  • Transformations Rotating or scaling does not only look better when using shapes, but it is usually also faster than transforming images. Especially if rotation and scaling are animated, shapes will perform better.
  • Startup time Loading an image and setting up an ImageView is usually slower then setting up shapes.
  • Footprint Static and dynamic footprint is almost always higher when using images.

Important: The variable cache of javafx.scene.Node is currently NOT used in the runtime. Setting it makes no difference!

Now we are going to focus on image loading

Item 5: Use the prescaling functionality
If an image needs to be scaled and the scaling factor does not change later, it is recommended to use the prescaling functionality. This can be done by setting width and height of an Image object, which will scale the image while it is loaded.

Using prescaling has two benefits. First of all it results in better performance. If prescaling is used, the scaling is definitely calculated only once. In contrast the scaling of an ImageView object is recalculated each time its transformation is changed by something else then a translation. For example changing the rotation will result in recalculating the scaling. Secondly, if an image is down scaled, memory usage is much smaller if prescaling is used.
Scaling can be calculated even faster if the flag Image.smooth is false. But the quality of the scaled image must be checked.

Example
This example generates thumbnails for a number of images. Code Example 1 creates a sequence of thumbnails, using the scaling functionality of ImageView.

def thumbnails = for (i in [0..11])
     ImageView {
         image: Image {url: "{__DIR__}images/img{i}.png"}
         preserveRatio: true
         fitWidth: 30
         fitHeight: 20
     }

Code Sample 1: Scaling within ImageView
The same can be achieved using the prescaling functionality of the Image class as shown in Code Example 2. Displaying the thumbnails is usually faster with this approach and the memory usage is much smaller.

def thumbnails = for (i in [0..11])
     ImageView {
         image: Image {
             url: "{__DIR__}images/img{i}.png"
             preserveRatio: true
             width: 30
             height: 20
         }
     }

Code Sample 2: Prescaling with Image

Item 6: Use background loading
The class Image provides a nice, but easily overlooked feature, 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 Image.backgroundLoading must be set.
Background loading has two consequences, which need to be considered during implementation. If an image which 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 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.

Example
Code Sample 3 extends the example from above to load the thumbnails in the background and displays them. While the images are loaded a placeholder (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.

def logo = Image {url: "{__DIR__}images/logo.png"}

 def thumbnails = for (i in [0..11])
     ImageView {
         image: Image {
             url: "{__DIR__}images/img{i}.png"
             preserveRatio: true
             width: 30
             height: 20
             backgroundLoading: true
             placeholder: logo
         }
         x: i mod 4 * 50 + 20
         y: ((i/4) as Integer) * 40 + 20
 }

 Stage {scene: Scene {content: thumbnails}}

Code Sample 3: Loading thumbnails in the background
On the emulator one has to look really close 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.

Item 7: 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 def to define it. This is true for almost all bound variables, because bound variables cannot be reassigned (there is no such thing as an unbound-operation) and usually one already knows what they are bound to when they are defined.

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 aggressive it can optimize our code. Let’s take a look at an example in Code Sample 1.

class Main {
     def i1: Integer = 0;
     var i2: Integer;
     public def i3: Integer = 0;
     public var i4: Integer;
 }

Code Sample 1: A sample script with public, private defs and vars
Code Sample 1 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 def, i2 and i4 are defined with var. Code Sample 2 shows part of the generated Java code.

class Main extends java.lang.Object implements Main$Intf,com.sun.javafx.runtime.FXObject{
     public int $Main$i1;
     public int $Main$i2;
     public int $i3;
     public final com.sun.javafx.runtime.location.IntVariable $i4;
     ...
 }

Code Sample 2: Parts of the generated Java code from Code Sample 1
What is remarkable about the generated Java code is the fact that all variables but i4 became simple ints; only the variable i4 is translated into an IntVariable, because it needs to provide more functionality. An int-variable requires less memory and performs faster than an instance of IntVariable.

Item 8: Use Integer instead of Number
Operations on integers are always faster than operations on floating-point values. On limited device, 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 Number. Therefore one should always set the type of Integer variables explicitly.

Item 9: Use functions of class Sequences
The class Sequences in the package javafx.util provides a large number of useful functions for dealing with sequences. One should be familiar with the provided functions and use them instead of implementing them by oneself. The functions in Sequences have been tested thoroughly and the performance is at least as good, if not better than that of own implementations.

Reference: Best Practices for JavaFX Mobile Applications 3 & Best Practices for JavaFX Mobile Applications 4 & Best Practices for JavaFX Mobile Applications 5  from our JCG partner Michael Heinrichs at the Mike’s Blog.

Michael Heinrichs

Java, Android, JavaFX developer. Interested in agile management and public speaking. Loves his family and cooking.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button