How to improve your Connect IQ application performance
This guest post was written by Tomáš Slavíček, a developer from Prague, Europe. You can learn more about him on Twitter or view additional watch faces byTomáš on the Connect IQ Store.
When you create a watch face or application for a Garmin Connect IQ device, the development can sometimes be quite challenging. More complicated apps can consume a lot of resources and memory. If you implement too many features, your application can become slow and unresponsive. When you exceed the limits, your app can start crashing on some devices.
Garmin determined the limits for every device, and older and slower devices have typically more restrictive limits so that your application doesn’t affect the performance and reliability of other apps on the watch.
It’s good to know how to detect these issues and optimize the code. In this article, you will learn how to measure the code execution speed, count objects in the memory, or how to speed up drawing of texts and images.
Updating the interface
You can update your application and draw contents in the onUpdate method. On watch faces, this method is called once per minute, just to refresh the time. But when you look on your watch (do the wrist gesture), it is called once per second. The display is refreshed again and again while you are still looking on your watch. On newer Connect IQ 2.3 compatible devices, for example fēnix 5, you can also implement the onPartialUpdate method and update the seconds every time.
When you have more complicated graphics with lots of polygons and texts, calculating and drawing of one single screen can take 700 milliseconds. This consumes a lot of battery and causes the unresponsiveness. When the image is still drawn, you cannot navigate from the app. The system needs to wait for the code to finish, and only then it can switch to another app. So it feels like you have pressed a button, but nothing has happened.
To summarize the application lifecycle, when you switch from one application to another, or from the widget to a watch face, these methods are called:
In fact, the application is always created from scratch when it is displayed, and all these methods are called upon. If we need to load some resources, for example, images or fonts, we always need to do it in every application start-up.
When the display is refreshed, for example, when we are looking on the watch, only the onUpdate method is called.
To speed up the start, we can pre-calculate some values and save them into storage. For example, if we need to calculate the sunrise and sunset time or the BMI of the user, we don’t need to do it every time. We can calculate it just once when the app is first started and load it every other run:
Or we can compute it just once a day, or when some important event happened.
It can be especially useful if we need to calculate and save the array of values – the number of steps of each day in the history, the resting heart rate for every past hour or the latest temperature trend. It’s always better to load these values from storage and only display those values.
There is one catch – we can only save approximately 6 kB of data into this storage. In an array of 120 numbers, each item is 4B and takes almost 0.5 kB. So, if we want to save a few hundreds of values, we need to be very careful how much storage space is remaining.
If we connect our watch to a computer and search the Garmin/Apps/Data folder, we can locate how large the current file is for each application:
There was one change in the SDK 2.4 – new applications can finally save up to approximately 100 kB data with the new Storage API. Each item still needs to be lower than 8 kB, so you will need to split long arrays into multiple items. This API was implemented more efficiently, so only the values that you need are loaded into memory on start-up, not the whole collection with all values.
You should still test this new API on older devices, because all 100 kB may not be available there if the device has lower memory. You can catch the StorageFullException if there is no memory left.
So if our pre-calculated values cannot fit into storage and we cannot save them, we can calculate them once in the onLayout method, then keep them in the array and draw them in the onUpdate. Applying this method, the user interface refresh will be the fastest possible.
But we also do not have an unlimited amount of memory there. Each device has its own limit, which we can see at the bottom panel in the simulator:
This limit is also different for different types of applications. For example, fēnix 5 has a 92 kB memory limit for a watch face, but just 28 kB for a data field. You can find these limits in the device’s .xml file in the SDK folder.
Unfortunately, the compiled size of the application with all its loaded resources is also counted into this limit. If you have a lot of custom fonts, localization strings or parameters in the settings, there is a possibility that you are already over the limit on some devices. When your code is longer, it also takes more memory, so the long copy-pasted spaghetti code can be a problem.
The most useful information is the peak memory usage. It is visible in the simulator when you select the File→ View memory.
If you exceed this value, the app will be closed by the system and the default watch face will be displayed. You will also see this information logged in the Garmin/Logs/CIQ_LOG.txt file on the device.