Battery life

When you develop an app, you should always consider whether your application is being a good citizen. An application is a good citizen when it is efficient, doesn't consume unnecessary resources, and minimally impacts the life of the device battery.

Being conservative with power consumption and having a great user experience aren't mutually exclusive concepts. In fact, being conservative with power usage might increase your user's satisfaction with your application.

Continue reading below to learn more about how you can make your app a good citizen and help to preserve battery life.

Stop animations when they're not needed

Although graphics and animations are important to the look and feel of most applications, they can affect the life of the device battery if they're overused. As a general rule, you should always stop graphics and animations when they're no longer needed. When a Cascades animation runs (either implicitly or explicitly), the rendering thread redraws the scene at a rate of about 60 FPS. A significant amount of processing power is being used when the app could be inactive.

For example, consider an application that frequently requests remote data, or performs intensive operations, such as running SQL queries. Although it's important to provide a visual cue indicating that this process is underway, you need to consider how often the animation will run (whether it's an ActivityIndicator, ProgressIndicator, or your own animation). If it turns out that the animation is running constantly, you might want to reconsider how you present this information to your users.

In most cases, you can replace an animation with a static image that the user recognizes and associates with a particular process. For example, in a mapping application, an image of a compass rose or an arrow might indicate that the application is currently receiving location updates.

Cascades stops rendering visual updates when the target node is no longer visible on the screen. This includes instances where the application moves to the background, and (most) instances where the node becomes hidden by another node (for example, if a Page is pushed on top of a Page that has a running animation). However, it's still a good idea to stop animations manually instead of relying on the framework to know when to stop rendering visual updates.

For more information about graphics, see Graphics & Multimedia.

For more information about Cascades animations, see Animations.

Use moderation when polling sensors

Retrieving the sensor readings from the device's hardware can be costly for battery life if updates are received by the application too frequently. Despite the impact on battery life, receiving sensor updates at short intervals is necessary for many apps and games. For example, in a racing game that allows the user to steer the vehicle by rotating the device, you probably need to receive a steady stream of accelerometer updates when the app is running.

In Cascades, an application receives sensor updates in the same way regardless of the type of sensor. To capture updates, you create an instance of the QSensor subclass, you set the active property to true, and you connect to the readingChanged() signal. For some sensors, such as the holster, orientation, and proximity sensors, this is a very efficient approach. Because these sensors have a small set of predefined values, the number of updates that are reported to the application is usually small (for example, the holster sensor returns only a value of true or false). This also makes the sensors straightforward to manage because you don't need to worry about polling the sensors yourself.

This approach doesn't work very well when you are accessing sensors that are able to monitor very small changes in the physical environment, such as the accelerometer, gyroscope, and magnetometer sensors. These sensors can potentially send a very large number of updates.

There are some ways that you can reduce the number of updates sent to the application when you work with these sensors. You can use the QSensor::skipDuplicates property, which, when it's set to true, omits and skips successive readings with the same or very similar values. You can also use a sensor filter to specify the frequency or number of new readings that the sensor delivers to your application, instead of having each new reading delivered.

For more information about how to access sensors efficiently, see Sensor efficiency.

Be conservative with location requests

Unless your application needs to know the device's precise geographic coordinates at all times (such as in a mapping or navigation app), you should be conservative with how often you send location requests.

For example, consider a social networking application that tags updates with a user's current location. Or, consider a restaurant finder app that is designed to search for restaurants near the user's current location. In these instances, retrieving the location once when it's required is probably sufficient. While it might seem useful to have the user's precise location always available, this approach can cause a significant drain on the device battery.

For information about how you can retrieve the location of the device, see Retrieving a single fix and Retrieving multiple fixes.

Stop background processes

Many applications depend on direct interaction from the user, so that all of their processing can be done while the application is open and being used. Applications that run in the background are generally those that need the ability to listen for a particular event and notify the user of the event, even while the application isn't in use.

Depending on the configuration of the app, running in the background can mean different things. All applications are considered to be running in the background while they're visible as an Active Frame (also called a cover) on the home screen. Even though an application can continue running processes in this state, it should stop any extraneous processing and use only the resources it needs to update the cover. To listen for this event, you can connect a slot to the bb::Application::thumbnail() signal.

import bb.cascades 1.0
 
Page {
    // Once this object is created, attach the signal to a
    // JavaScript function.
    onCreationCompleted: {
        Application.thumbnail.connect(onThumbnailed);
    }
     
    function onThumbnailed() {
        // Perform an action once the app is thumbnailed.
    }
}
BackgroundTest::BackgroundTest(bb::cascades::Application *app)
: QObject(app)
{

    // If any Q_ASSERT statement(s) indicate that the slot failed to connect to 
    // the signal, make sure you know exactly why this has happened. This is not
    // normal, and will cause your app to stop working!!
    bool connectResult;
    
    // Since the variable is not used in the app, this is added to avoid a 
    // compiler warning.
    Q_UNUSED(connectResult);
    
    connectResult = QObject::connect(app, SIGNAL(thumbnail()),this, SLOT(onThumbnail()));
    
    // This is only available in Debug builds.
    Q_ASSERT(connectResult);
    
    // Create the UI and set the scene.
}

void BackgroundTest::onThumbnail()
{
	// Perform an action once the app is thumbnailed.
}

When the user opens another application or the backlight turns off, apps that are running as Active Frames automatically enter the Stopped state, at which point they can't perform any operations. This approach helps to minimize battery power consumption and maximize the performance of the device.

However, applications that request the run_when_backgrounded permission continue to run in the background, even after the user opens another app or the backlight turns off. If your application does require this permission, you should still make sure to stop any operations that aren't absolutely essential. To listen for this event, you can connect a slot to the bb::Application::invisible() signal. The implementation is similar to the example above.

For more information about application states and running applications in the background, see Application states.

Update Active Frames efficiently

Active Frames (also known as application covers) are useful for providing your users with information without requiring them to reopen your application. A cover can be updated dynamically at any time, but you should always be conservative with the frequency that you make updates.

The image to the right shows a few examples of covers used in the BlackBerry 10 OS. Even though these covers contain dynamic content (BBM displays recent contact updates and the Calendar app displays upcoming events), the content remains somewhat static after it's displayed.

Screen showing an Active Frame example.

Avoid doing this:

The code sample below is a basic implementation of a cover that displays a single integer. After the cover is created, an update() function is called, which increments the integer and starts a timer to call the function again after 4 seconds have passed.

#include "ActiveFrameInefficient.h"

#include <bb/cascades/SceneCover>
#include <bb/cascades/Container>
#include <bb/cascades/Application>

ActiveFrameInefficient::ActiveFrameInefficient(QObject *parent)
	: SceneCover(parent)
{
	Container *root = new Container();
	m_theLabel = Label::create().text("0");
	root->add(m_theLabel);
	setContent(root);

	update();
}

void ActiveFrameInefficient::update() {

	QTimer::singleShot(4000, this, SLOT(update()));

	int labelNum = m_theLabel->text().toInt() + 1;
	m_theLabel->setText(QString::number(labelNum));
}

There are a couple of issues with this approach. The cover should never need to be updated with this frequency. If updates are necessary, a frequency of every 30 seconds is probably sufficient. Another issue is that updates will continue to occur even while the application is running in the foreground. Even though the visual updates aren't rendered, the application still increments the number every 4 seconds. In this example, the updates don't seem too costly, but with more elaborate covers this could translate to a significant amount of processing power.

Do this:

The code sample below shows how you should implement your covers. Instead of calling the update() function when the cover is created, you can connect to the thumbnail() signal so that updates occur only when the app becomes an Active Frame. When the application moves to the foreground, a Boolean variable called isActiveFrame is set to false, letting the application know that it can stop updating the cover.

#include "ActiveFrame.h"

#include <bb/cascades/SceneCover>
#include <bb/cascades/Container>
#include <bb/cascades/Application>

ActiveFrame::ActiveFrame(QObject *parent)
	: SceneCover(parent)
	, isActiveFrame(false)
{
	Container *root = new Container();
	m_theLabel = Label::create().text("0");
	root->add(m_theLabel);
	setContent(root);

    // If any Q_ASSERT statement(s) indicate that the slot failed to connect to 
    // the signal, make sure you know exactly why this has happened. This is not
    // normal, and will cause your app to stop working!!
    bool connectResult;
    
    // Since the variable is not used in the app, this is added to avoid a 
    // compiler warning.
    Q_UNUSED(connectResult);
    
    connectResult = QObject::connect(Application::instance(), SIGNAL(thumbnail()), 
                                     this, SLOT(backgrounded()));
                                     
    // This is only available in Debug builds.
    Q_ASSERT(connectResult);
                                     
    connectResult = QObject::connect(Application::instance(), SIGNAL(fullscreen()), 
                                     this, SLOT(foregrounded()));

    // This is only available in Debug builds.
    Q_ASSERT(connectResult);
}

void ActiveFrame::foregrounded() {
	isActiveFrame = false;
}

void ActiveFrame::backgrounded() {
	isActiveFrame = true;
	update();
}

void ActiveFrame::update() {

	if (isActiveFrame) {
		QTimer::singleShot(3000, this, SLOT(update()));
		qDebug() << "Cover updated!";

		int labelNum = m_theLabel->text().toInt() + 1;
		m_theLabel->setText(QString::number(labelNum));
	}
}

To see the complete code for this example, expand the files listed below.

#include <bb/cascades/Application>

#include "applicationui.hpp"

#include <Qt/qdeclarativedebug.h>

using namespace bb::cascades;

Q_DECL_EXPORT int main(int argc, char **argv)
{
    Application app(argc, argv);

    new ApplicationUI(&app);

    return Application::exec();
}
#ifndef ACTIVEFRAME_H_
#define ACTIVEFRAME_H_


#include <bb/cascades/Label>
#include <bb/cascades/SceneCover>
#include <QObject>

using namespace ::bb::cascades;

class ActiveFrame: public SceneCover {
	Q_OBJECT

public:
	ActiveFrame(QObject *parent=0);

public slots:
	void update();
	void foregrounded();
	void backgrounded();

private:
	bb::cascades::Label *m_theLabel;
	bool isActiveFrame;
};

#endif /* ACTIVEFRAME_H_ */
#include "ActiveFrame.h"

#include <bb/cascades/SceneCover>
#include <bb/cascades/Container>
#include <bb/cascades/Application>

ActiveFrame::ActiveFrame(QObject *parent)
	: SceneCover(parent)
	, isActiveFrame(false)
{
	Container *root = new Container();
	m_theLabel = Label::create().text("0");
	root->add(m_theLabel);
	setContent(root);

    // If any Q_ASSERT statement(s) indicate that the slot failed to connect to 
    // the signal, make sure you know exactly why this has happened. This is not
    // normal, and will cause your app to stop working!!
    bool connectResult;
    
    // Since the variable is not used in the app, this is added to avoid a 
    // compiler warning.
    Q_UNUSED(connectResult);
    
    connectResult = QObject::connect(Application::instance(), SIGNAL(thumbnail()), 
                                     this, SLOT(backgrounded()));
                                     
    // This is only available in Debug builds.
    Q_ASSERT(connectResult);
	
	connectResult = QObject::connect(Application::instance(), SIGNAL(fullscreen()), 
            this, SLOT(foregrounded()));
            
    // This is only available in Debug builds.
    Q_ASSERT(connectResult);
}

void ActiveFrame::foregrounded() {
	isActiveFrame = false;
}

void ActiveFrame::backgrounded() {
	isActiveFrame = true;
	update();
}

void ActiveFrame::update() {

	if (isActiveFrame) {
		QTimer::singleShot(3000, this, SLOT(update()));
		qDebug() << "Cover updated!";

		int labelNum = m_theLabel->text().toInt() + 1;
		m_theLabel->setText(QString::number(labelNum));
	}
}
import bb.cascades 1.0

Container {    
    Container {        
        layout: DockLayout {}
        background: Color.Black
        
        ImageView {
            imageSource: "asset:///images/application-cover.png"
            scalingMethod: ScalingMethod.AspectFit
        }
        
        Container {
            bottomPadding: 31
            horizontalAlignment: HorizontalAlignment.Center
            verticalAlignment: VerticalAlignment.Bottom
            
            Container {
                preferredWidth: 300
                preferredHeight: 42
                background: Color.create("#121212")
                layout: DockLayout {}
                
                Label {
                    horizontalAlignment: HorizontalAlignment.Center
                    verticalAlignment: VerticalAlignment.Center
                    text: "QML"
                    textStyle.color: Color.create("#ebebeb")
                    textStyle.fontSize: FontSize.PointValue
                    textStyle.fontSizeValue: 6
                }
            }
        }
    }
}
Screen showing an application cover.

For more information about creating application covers, see Active Frames.

Push data to your apps

If your application relies on updates from a server-side application, you have probably thought about how to design the application to keep data current for the user. One approach for keeping data current is to use a headless application to retrieve data in the background and notify the client application when new data arrives. In this scenario, the headless application would perform scheduled network requests to pull the data from a remote server. The problem with this approach is that scheduled queries require processing power and network access, and the headless app might make numerous requests even when the server has no data to provide.

A headless application is an app that has been created to run in the background and doesn't have a UI from which it can accept input.

The preferred approach for receiving remote data is to use the Push Service. Push technology allows a server-side app to notify a client app when new data is available by pushing an alert with a small payload to the device. Using this approach, you conserve both processor power and network usage because the client application sends requests only when it knows there's data available. The result is an application that not only conserves battery power, but also has the added benefit of near real-time updates.

For more information about using the Push Service with Cascades, see Push Service.

Reduce wakeups during standby mode

To reduce power consumption, you can decrease the number of times that a device wakes up when it is in standby mode. A device is in standby mode when it has been put to sleep or has timed out due to inactivity, and has a blank screen. During standby mode, the device tries to enter lower power states, and it can enter these states only when there is no activity.

A wakeup is an interrupt event that causes the kernel to schedule a thread to do some work. Misbehaving apps are usually the cause of wakeups, but hardware interrupts cause wakeups too. Even when apps are in standby mode, they need to receive push events from the network or poll network services for new content. As a developer, you need to ensure that your app is not waking up unnecessarily while the device is asleep.

The standby monitor runs in the background on all BlackBerry 10 devices. The standby monitor watches for misbehaving apps and writes to a log file when it detects them. To determine whether apps are misbehaving, the standby monitor considers wakeups and CPU sleep states. Typically, an app is misbehaving when it causes wakeups more frequently than once per minute while in standby mode.

You don't need to include any libraries to use the standby monitor. On the device, the standby monitor writes to a log file under /accounts/1000/shared/misc/standby.log. In the Momentics IDE for BlackBerry, you can use the Target File System Navigator to locate and open the log file.

For more information, see File system access.

To check the standby monitor log file:

  1. On the device, enable Development Mode.
  2. Start the app, let the device enter standby, and wait 5-10 minutes.
  3. Connect the device to your computer.
  4. Start the Momentics IDE.
  5. On the toolbar, from the Launch Target drop-down list, select the device.
  6. In Windows and Linux, on the Windows menu, select Show View > QNX Targets > Target File System Navigator. In Mac OS, on the Momentics menu, select Show View > QNX Targets > Target File System Navigator.
  7. Click OK.
  8. On the left side of the Target File System Navigator view, expand the device.
  9. In /system/accounts/1000/shared/misc, open standby.log. If the app is well behaved, it doesn't have any log file entries.

Here is sample output from the standby monitor, followed by information to help you interpret it.

standbymon ======== Begin analysis phase ========
standbymon Window stats: configured 60s, actual 60.15s, sleep time 60.15s, sleep percentage 100.00%
standbymon Module 'data usage' reported the following culprits:
standbymon       lo0: in    0/ 11M  out    0/ 11M
standbymon      bcm0: in  550/ 65M  out 1.1K/ 29M
standbymon    rndis0: in  29K/8.1M  out  25K/6.5M
standbymon      msm0: in 5.3K/ 15M  out 2.8K/6.4M
standbymon     bptp0: in  139/ 41K  out  282/ 85K
standbymon Module 'sleep states' reported the following culprits:
standbymon IDLE CPU 1 MODE [0x20 3/2/0] [0x15 60/0/60]
standbymon IDLE CPU 0 MODE [0x15 389/21/368]
standbymon DVFS CPU 1 MODE [1000 1/1/0]
standbymon DVFS CPU 0 MODE [1500 1/1/0] [1000 3/3/0] [800 2/2/0] [600 2/2/0] [300 1/1/0]
standbymon Module 'wakeups' reported 12 culprits:
standbymon [59, 3, 2/2ms, 2/2ms, 6/6ms] Process 6680723 (base/usr/bin/python3.2) Thread 22 (bbm)
standbymon [52, 8, 2/2ms, 11/29ms, 26/55ms] Process 204817 (proc/boot/battmgr) Thread 3 (Unknown)
standbymon [35, 6, 2/4ms, 4/13ms, 17/44ms] Process 196623 (proc/boot/lagavulin) Thread 2 (Unknown)
standbymon [21, 5, 2/4ms, 3/14ms, 12/43ms] Process 249874 (proc/boot/thermal) Thread 1 (Unknown)
standbymon [20, 0, 0/0ms, 0/0ms, 0/0ms] Process 6680723 (base/usr/bin/python3.2) Thread 47 (QuitableThread)
standbymon [8, 0, 0/0ms, 0/0ms, 0/0ms] Process 299029 (proc/boot/devb-sdmmc-rim-omap) Thread 9 (fsys_resmgr)
standbymon [5, 0, 0/0ms, 0/0ms, 0/0ms] Process 7458976 (accounts/1000/appdata/sys.navigator/app/navigator) Thread 1 (render)
standbymon [4, 0, 0/0ms, 0/0ms, 0/0ms] Process 204817 (proc/boot/battmgr) Thread 1 (Unknown)
standbymon [3, 0, 0/0ms, 0/0ms, 0/0ms] Process 20484 (proc/boot/devc-seromap) Thread 1 (Unknown)
standbymon [2, 0, 0/0ms, 0/0ms, 0/0ms] Process 2736171 (base/usr/sbin/coreServices) Thread 1 (Unknown)
standbymon [2, 0, 0/0ms, 0/0ms, 0/0ms] Process 57355 (proc/boot/i2c-omap35xx-omap4) Thread 1 (Unknown)
standbymon [2, 0, 0/0ms, 0/0ms, 0/0ms] Process 299029 (proc/boot/devb-sdmmc-rim-omap) Thread 15 (fsys_resmgr)
standbymon [213]

Examine data usage

The data usage module monitors data use on each interface. For each 60-second window, it reports the amount of data in and out during that window and since reboot. The data usage module runs regardless of whether the device is in standby.

The output format for the data usage module is:

                  1        2    3         4    5
standbymon    rndis0: in  29K/8.1M  out  25K/6.5M
  1. The interface name
  2. Total bytes in during the last window
  3. Total byes in since reboot
  4. Total bytes out during the last window
  5. Total bytes out since reboot

Examine trace events

The sleep states module looks at trace events that the CPU DLL emits to determine which IDLE and dynamic voltage and frequency scaling (DVFS) modes have been entered during the analysis window. It logs attempts, successes, and failures for each mode.

The output format for the sleep states module is:

              1     2       3    4 5 6
standbymon IDLE CPU 1 MODE [0x20 3/2/0] [0x15 60/0/60]
  1. IDLE or DVFS mode for the CPU
  2. CPU number
  3. Mode number entered (as set up in the .pm file for your platform, for example: /etc/system/config/london.pm)
  4. Attempts to enter this mode
  5. Successes entering this mode
  6. Failures entering this mode

Examine wakeups

The wakeup module uses event handlers to monitor wakeups and analyzes this data for each collection window. It emits a log message when a wakeup exceeds an allowable rate. The allowable wakeup rate is configurable system-wide, per process, and per thread.

The output format for the wakeup module is:

            1   2  3      4        5                6       7                  8         9
standbymon [52, 8, 2/2ms, 11/29ms, 26/55ms] Process 204817 (proc/boot/battmgr) Thread 3 (Unknown)
  1. Total number of wakeups
  2. Total number of wakeup clusters (a cluster is a collection of two or more wakeups that occur less than 10 milliseconds apart)
  3. Shortest cluster with total wakeups and time elapsed between the start and end of the cluster
  4. Longest cluster with total wakeups and time elapsed between the start and end of the cluster
  5. Total wakeups and cumulative time for all clusters
  6. PID of offending process
  7. Process name (or "Unknown")
  8. TID of offending thread
  9. Thread name (or "Unknown")

By default, the analysis window length is one minute and the allowed number of wakeups in that window is one. If a process exceeds one wakeup in a one-minute period, the wakeup module emits a log message.

The standby monitor analyzes trace data only if the device was in standby for 30% of its 60-second analysis window. If not enough time was spent in standby, you see a message in the log file output:

   May 15 09:13:42    2    22   100 standbymon ======== Begin analysis phase
      ========                  May 15 09:13:42    2    22   100
      standbymon Not enough time spent in user_standby during this window to perform
      analysis.

If there are no wakeups during the analysis window, you see a message in the log file output:

 May 15 09:03:44    2    22   100 standbymon ======== Begin analysis phase
      ========                  May 15 09:03:44    2    22   100
      standbymon Module 'wakeups' reported 0 culprits during the last 60 seconds:

Last modified: 2014-09-30



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

comments powered by Disqus