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.

General app behavior

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

Best practices

Perform clean-up of system resources upon exiting. You can free allocated memory, stop all processes used by your app, remove temporary files, close file descriptors, and so on.

Stop the rendering of graphics when they can't be seen. For example, when your app isn't active or when the device enters sleep mode, your app shouldn't continue rendering graphics.

React in a timely manner to events that affect your app. When your app is minimized or deactivated, your app should respond quickly. Also, your app should ignore events that aren't applicable. In future releases of the BlackBerry 10 OS, if new events are generated, your app is protected.

Handle cases where other apps consume device resources.

Handle cases where the audio buffer underruns to avoid audio PCM playback issues. For example, see the PlayWav sample app.

Save the state of your app in the sandbox. When you save the state, your app can resume quickly from the previous state.

Handle app life cycle events to ensure a longer battery life. For example, your app 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. You should ensure that the 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.

Best practices

Don't read or write files outside of the app sandbox. If your app makes calls to public APIs, you need to ignore this best practice.

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.

Don't prevent the device from entering sleep mode.

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. For example, don't send information to Internet servers or listen 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 app against the system libraries. Statically linked system libraries may cause your app 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.

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 in 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 you build into 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:

Best practices

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, and 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 be used exclusively 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.

Recommended

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
        }
    }       
}

Not recommended

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. Although 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.

Recommended

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

Not recommended

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 in 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.

Recommended

// 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
}

Not recommended

// 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:

Best practices

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.

// 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
}

Starting the event loop immediately 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 that you can do to ensure that you're getting the best possible performance from your lists.

Best practices

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 in 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 that 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.

Best practices

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 must 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 because 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.

Recommended

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

Not recommended

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

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

Recommended

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"    
        }    
    ]
}

Not recommended

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.

Recommended

Container {
    background: Color.Red
}

Not recommended

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

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

Recommended

Container {
     background: Color.White
}

Not recommended

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:
    // Function 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.

Best practices

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.

Last modified: 2015-04-30



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

comments powered by Disqus