App performance

When you develop apps for the BlackBerry 10 OS, it's important that your apps don't consume unnecessary resources on BlackBerry 10 devices. Apps that don't follow these guidelines may be removed from the BlackBerry World storefront.

Want to get the best possible performance out of your Cascades apps? Make sure that your apps comply with the following performance checklist. While there are many things that you can do to increase the performance of your apps, follow these guidelines to improve your app's performance.

Compile resources

Compiling app resources is a way that you can decrease your app's loading times without changing your code. In an app that doesn't compile resources, QML files are stored within the app's assets folder in the file system. Accessing these QML files can be inefficient for apps that contain a lot of QML. To help speed things up, you can compile your QML files as a resource that's built in to your app's binary.

For more information about compiling QML, see Compile resources for Cascades apps.

Use QML efficiently

If your app is experiencing slow start times or sluggish response times when rendering the UI, there might be some optimizations that you can make to load QML more efficiently.

The most straightforward way of creating a UI in Cascades is to define one large, hierarchical structure in QML. Unfortunately, this approach can have a negative effect on start time and memory consumption. In addition, having too many controls linked to the scene graph at the same time can affect the overall UI rendering performance. A component is linked to the scene graph after it's added to the node (or a subnode) that is set as the root of the app's scene (set by Application::instance()->setScene()). To avoid these problems, there are a few strategies that you should consider when you're developing your UI:

Keep attachedObjects simple

Don't run time-consuming processes in constructors when you're using attachedObjects to expose C++ objects to QML. These processes, which include SQL queries, blocking calls to services, and so on, are run when the QML is loaded.

Instead of running these processes in the constructor, create a Q_INVOKABLE function containing those processes that you can call from QML. See Using C++ classes in QML for more details on exposing C++ classes to QML.

Use deferred loading

At any given time, only the minimum amount of required QML should be loaded into memory. When your app starts, load what the user sees first. After your app has started, asynchronously load the content that's required for the next possible interactions, and continue this pattern for each subsequent interaction. When components are no longer needed, destroy them. For example, if your app contains a TabbedPane or a NavigationPane, you should load only the QML that's required for the first page, with empty stubs for the subsequent pages. After the first page loads, you can load additional pages asynchronously.

Another approach is to load content on demand. This approach works well if you're not loading large amounts of content. If possible, try to load components to prepare for a possible interaction so that the user isn't left waiting for it.

One way to handle deferred loading in Cascades is to use the ComponentDefinition and ControlDelegate classes. ComponentDefinition represents an imperative way of creating dynamic QML components, while ControlDelegate is the declarative way. For more information about ControlDelegate and ComponentDefinition, see Dynamic QML components.

Using the visible property on controls isn't an efficient way to handle deferred loading. Even though the visible property for a control might be set to false, the control is still loaded into memory, affecting both start time and page creation time. Generally, the visible property should exclusively be used when controls are temporarily hidden. If there's a good chance that a control is not shown at all, it's better to defer its creation altogether.

Create similar items using ComponentDefinition

If you have multiple items that are similar to create, you should create them dynamically from a ComponentDefinition instead of reusing the QML code throughout the file. In this example, a Container is created multiple times and added to the scene. The index property is used to identify the different versions of the container and apply the appropriate background color.

import bb.cascades 1.0

Page {
    Container {
        id: rootContainer
        attachedObjects: [    
            ComponentDefinition {
                id: component
                
                // Simple container without a background color
                Container {
                    property int index;
                    preferredWidth: 100    
                    preferredHeight: 100
                    
                    // Colors the background based on the index
                    background: switch (index) {
                        case 0: Color.Black; break;           
                        case 1: Color.DarkGray; break;          
                        case 2: Color.Gray; break;           
                        case 3: Color.LightGray; break;       
                    } 
                }    
            }    
        ]
    }
    onCreationCompleted: {
        for (var i = 0; i < 4; i++) {  
            // Creates the container and sets its index 
            var item = component.createObject();            
            item.index = i;            
            rootContainer.add(item);   
        }
    }
}

Reuse text styles when possible

You should try to reuse text styles when possible, even though your app automatically caches text style settings. Creating multiple TextStyleDefinition objects in QML can increase load time, though it may be sometimes necessary for the maintainability of your code.

Do this:

Container {
    attachedObjects: [
        TextStyleDefinition {
            id: myStyle
            base: SystemDefaults.TextStyles.BigText
        }
    ]
    Label {
        text: "This is a label."  
        textStyle {
            base: myStyle.style
            color: Color.Red
        }
    }     
    Label {
        text: "This is another label."          
        textStyle {
            base: myStyle.style
            color: Color.Black
        }
    }       
}

Avoid doing this:

Container {   
    Label {
        text: "This is a label."  
        textStyle {
            base: SystemDefaults.TextStyles.BigText
            color: Color.Red
        }
    }     
    Label {
        text: "This is another label."          
        textStyle {
            base: SystemDefaults.TextStyles.BigText
            color: Color.Black
        }
    }
}

For more information about text styles, see Text styles.

Avoid declaring variant properties

The variant type in QML is useful because it allows your app to store values from any of the basic Qt types. While the variant type is versatile, the versatility comes at a price. Using a variant type when you can use a more specific type means that you get less compile-time help (the compiler may not recognize certain type-related errors) and your app pays a performance penalty for converting the value into and from a QVariant each time. If the variable is always a number, you should use the int or real types. If the variable is always text, use a string type.

Do this:

property int aNumber : 100
property bool aBool : true
property string aString : "Hello!"

Avoid doing this:

property variant aNumber : 100
property variant aBool : true
property variant aString : "Hello!"

The variant type can also be used to store more complex objects, such as controls, or any other class type that is exposed to QML. Here's an example of how to declare a property of the Container type. When you initialize the Container object, you must use null instead of 0, because 0 represents a literal integer.

property Container myContainer : null

However, there are some instances when you must use the variant type to store object references. For example, you must use a variant to store an object reference if your app passes the object reference within a signal handler.

Avoid using JavaScript files to store properties

Using JavaScript files to store properties is inefficient and unsafe. When you import a JavaScript file into QML, property types are lost, so you must reassign your properties in QML before you can use them. Changing the value of a JavaScript property can also cause issues. When you load a JavaScript file into QML, a new object is created for each QML file that you import the JavaScript file into. If you change any of the JavaScript property values, your data might become out of sync across your app.

To create safer and more efficient code, you should define JavaScript properties in QML instead.

Do this:

// Constants.qml

// QtObject is the most basic non-visual type
QtObject { 
    property int a: 123;
    property real b: 4.5;
    property string c: "6";
}
// QML file where the Constants.qml properties are used

import bb.cascades 1.0
import "../common"

Container {
    attachedObjects: [ 
        Constants { 
            id: constants 
        }
    ]
    property int a: constants.a
    property real b: constants.b
    property string c: constants.c
}

Avoid doing this:

// Constants.js

var a = 123;
var b = 4.5;
var c = "6";
// QML file where the Constants.js properties are used

import bb.cascades 1.0
import "../common/Constants.js" as Constants

// Reassigning the property types
Container {
    property int a: Constants.a
    property real b: Constants.b
    property string c: Constants.c
}

Don't block the UI thread

Does your overall app performance seem sluggish or unresponsive? The poor performance might be due to your app running too many resource-intensive processes on the UI thread. Here are a few things to consider when loading data or running other resource-intensive operations:

Load data asynchronously

You should always try to avoid loading data in response to a user's request for it. If possible, large amounts of data should be preloaded to prepare for a request. Loading data asynchronously is especially important when using a ListView. Preemptively loading data may not be as important when using small sets of data. When handling large sets of data, you need a DataModel that's able to continually provide data to a scrolling ListView. For more information about lists and providing asynchronous data, see Asynchronous data providing.

Run resource-intensive processes on a separate thread

Many resource-intensive processes in Qt are asynchronous in nature and don't require the creation of separate threads. For example, many of the QtNetwork APIs automatically process networking requests on a separate thread. When you do have resource-intensive processes that are affecting UI performance, you can use QThread to run these processes on a separate thread. And if you don't want to set up a separate thread, you can use QtConcurrent::run to run functions asynchronously. For more information about QThread, see Thread support.

Start the event loop right away

For apps that request a large amount of data over the network or perform heavy operations when they start, you should always start the event loop before these tasks are initiated. If you don't start the event loop by calling Application::exec(), the user may experience a noticeable lag between starting the app and seeing the UI.

Here's an example of how to start the event loop before you start any other operations.

Not applicable

// Declare a class with an invokable init() method
class MyClass : public QObject {
    public:
    MyClass();
    ...
    private:
    Q_INVOKABLE void init();
}
// Create an instance of the class in main
int main(int argc, char **argv)
{
    Application app(argc, argv);
    MyClass c;
    return Application::exec();
}
// In the constructor of the instantiated class, invoke init() and
// do nothing else.
MyClass::MyClass() : QObject()
{
    QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection);
    // The Qt::QueuedConnection param specifies that the event
    // loop must start before init() can be executed.
}
// Invoked after the event loop starts
MyClass::init()
{
    // Time consuming operation such as a network request or
    // loading a really large UI
}

Immediately starting the event loop is critical with headless apps. If the event loop for a headless operation doesn't start within 6 seconds from when the app starts, the OS automatically terminates the app.

Optimize your lists

Although lists are powerful and flexible, they can also be intensive on resources when used incorrectly. There are a number of things you can do to ensure that you're getting the best possible performance from your lists.

Define multiple ListItemComponent objects

If your ListView features different types of list items, always create a specialized ListItemComponent for each variation. You should avoid using a single ListItemComponent and changing the visibility of the content depending on the data. The ListView creates every node in a list item, regardless of whether they're visible or not.

Keep list items simple

In general, it's best to keep list items simple. Unnecessary controls within a ListItemComponent can slow down performance. For large list items, consider using Dynamic QML components.

Load images asynchronously

Always load images asynchronously in your list so it can scroll while images are still loading. To load an image asynchronously, you must use the file:/// prefix with the absolute path to the image.

For more information about loading images asynchronously, see Images.

Use assets and colors efficiently

There are several best practices that you should follow to ensure that your app uses assets efficiently.

Avoid using duplicate images

Using duplicate image assets in your app might seem harmless, but it can have a negative impact on performance, memory, and disk space. After an image is loaded into memory, Cascades stores it in a texture cache. When the same image is loaded again, it loads faster and doesn't take up any extra memory. However, if you have a duplicate image in your assets folder, it's treated as a new image and has to be created and stored in memory again.

Unused images don't affect memory consumption at all, but they do affect disk space.

Nine-slice scale your images

Nine-slice scaling is typically used for background and border images since it allows an image to scale to virtually any size while keeping its corners sharp and borders uniform. Using nine-slice scaling can help minimize the size and number of image assets that you bundle with your app. For more information about nine-slice scaling, see  Nine-slice scaling.

Delete transparent pixels

If your images use transparent pixels to provide padding, delete the transparent pixels and create the space in your app using padding or margins instead. Trimming the transparent pixels can reduce the size of the image.

Do this:

Screen showing an image with no padding.
ImageView {
    imageSource: "asset:///tatLogo.png"
    leftMargin: 10
    rightMargin: 10
    topMargin: 10
    bottomMargin: 10
}

Avoid doing this:

Screen showing an image with padding.
ImageView {
    imageSource: "asset:///tatLogo.png"
}

Use tiling

For backgrounds, use the tiling functionality in ImagePaintDefinition to repeat textures within a Container.

Do this:

Checkerboard tile
// checkerboard_40x40.amd

#RimCascadesAssetMetaData version=1.0
source: "checkerboard_40x40.png"
repeatable: true
Container {
    preferredWidth: 200
    preferredHeight: 100
    background: tile.imagePaint
    
    attachedObjects: [
        ImagePaintDefinition {
            id: tile
            repeatPattern: RepeatPattern.XY
            imageSource: "asset:///images/checkerboard_40x40.amd"    
        }    
    ]
}

Avoid doing this:

Full checkerboard
ImageView {
    imageSource: "checkerboard_200x100.png"    
}

Use images of the correct size

Always make sure that you're using images that are the correct size for your app. If you have an image that is 100 px wide and you want to render it at 50 px, scale the actual image instead of scaling it in the app.

In addition, consider using a third-party app to reduce the size of PNG images that you use in your app. Smaller file sizes result in quicker loading times and use less disk space. Even if your compressed PNG is only a couple of kilobytes, it takes up at least (width x height x 4) bytes of graphics memory. Gradients are compressed in PNG format but still use the same amount of graphics memory.

Replace images with colors

Are you using any single-color images in your app? If possible, you should replace these single-color images with containers that have their background colors set.

Do this:

Container {
    background: Color.Red
}

Avoid doing this:

Container {
    ImageView {
        imageSource: "asset:///bigRedImage.png"
    }
}

Reuse colors

Every time the Color.create() function is called, new memory for the color is allocated. The simple solution to this problem is to use the predefined color constants whenever possible.

Do this:

Container {
     background: Color.White
}

Avoid doing this:

Container {
     background: Color.create("#ffffff") 
}

This solution is efficient, except that there isn't a default constant for every possible color. In these cases, you can define the colors as properties and reference them multiple times.

property variant myColor : Color.create("#8072ff");

Container {
    background: myColor
}

But what about when you need to reference the same color in multiple QML files? You can define the color in C++ and expose it to all your QML files.

Declare the color:

Q_PROPERTY(bb::cascades::Color niceColor READ niceColor CONSTANT)

public:
    // Getter that retrieves the color      
    bb::cascades::Color niceColor();

private:
    // Variable for the color
    bb::cascades::Color m_nicecolor;

Create the color and expose it to QML:

m_nicecolor = Color::fromARGB(0xff00a8df);
qml->setContextProperty("MyApp", this);

Reference the color in QML:

Container {
    background: MyApp.niceColor
}

Consider these additional optimizations

If you're app is still experiencing performance issues, here are a few more strategies that you can follow to improve its performance.

Create the UI using C++ instead of QML

When creating an app, it's almost always recommended that you use QML for the UI. However, creating UI components using C++ is slightly faster. The time it takes to parse and process a QML file is slightly more than that for compiled C++ code. If you need to squeeze that last bit of performance out of your app, then you may want to create your UI with C++ instead of QML.

Remove usage of stderr and stdout

Although stderr and stdout are useful for debugging your app, usage of these output streams should be removed before you package your app for release. These operations are costly to run and can negatively impact the performance of your app.

Best practices for app behavior

The following list provides examples of best practices for app behavior.

  • Change the app orientation when the BlackBerry 10 device rotates.
  • Show the app menu when the user swipes down from the top bezel.
  • Perform clean-up of system resources upon exiting, such as freeing allocated memory, stopping all processes used by your app, removing temporary files, closing file descriptors, and so on.
  • Stop the rendering of graphics when your app isn't active or when the device enters sleep mode.
  • React in a timely manner to events such as being minimized or deactivated. In addition, ignore events that aren't applicable to your app. This practice protects your app if new events are generated in future releases of the OS.
  • Handle cases where other apps consume device resources.
  • Handle cases where the audio buffer underruns in your app to avoid audio PCM playback issues. For example, see the PlayWav sample app.
  • Save the state of your app in the sandbox, where applicable, so your app can resume from the previous state.
  • Handle app life-cycle events to ensure a longer battery life. For example, you should handle the WINDOW_INACTIVE event to place the app in a suspended state.
  • Use a unique window group name when using Screen APIs.
  • Check the audio hardware block sizes to ensure that block sizes match when audio data is written for playback. Don't assume that the block size remains static between releases.

Prohibited app behavior

Your app must not disrupt service to, or prevent the operation of, other apps. In addition, your app must not gather information that leads to exploitation or loss of privacy. Your app must not gain unauthorized access to system resources or otherwise operate in any manner that may be considered intrusive or abusive. The following list identifies some prohibited app behavior.

  • Don't read or write files outside of the app sandbox, unless your app makes calls to public APIs.
  • Don't manipulate process information. Process information includes priority, thread priority, process user ID, group ID, process group, parent process, and so on.
  • Don't store nonessential files in the shared directory. For example, don't store executable code (including libraries and interpreted code), temporary files, and private files (such as files that only your app reads).
  • Don't change the file permissions.
  • Don't prevent the dimming of the backlight or prevent the device from entering sleep mode unnecessarily.
  • Don't show disturbing or adult graphics or play sounds without user consent.
  • Don't create busy loops.
  • Don't try to acquire information by phishing. For example, don't ask for device password, PIN, or other confidential information.
  • Don't operate in a manner that could breach user privacy without user consent or authorization, such as sending information to Internet servers or listening to sockets.
  • Don't manipulate Persistent Publish/Subscribe (PPS) objects directly. The use of PPS objects with the BlackBerry 10 Native SDK isn't supported. Instead, use the BlackBerry Platform Services (BPS) library APIs.
  • Don't statically link your apps against the system libraries. Statically linked system libraries may cause your apps to stop responding.
  • Don't use undocumented APIs. Undocumented APIs are unsupported and can cause your app to crash unexpectedly when users upgrade the BlackBerry 10 OS.

Last modified: 2015-03-31



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus