Refining your application
From UIQ Books
Refining Your Application
This chapter shows you how to make your application suitable for global deployment. It then presents some hints and tips to help you build applications that are more resilient, reliable and efficient.
Firstly, we develop the SignedAppPhase2 application by demonstrating how to support both Latin and non-Latin character based languages within a commercial grade application. This example exists in the Localization folder.
Next, we consider aspects of internationalization beyond the translation of application text. We must correctly format data to match the conventions used in various countries.
We then take a look at application performance. With our Performance example application we generate some figures that demonstrate the relative speed of some Symbian OS features. From these figures and an understanding of the functionality being performed, application developers can make informed decisions about certain aspects of application development within the Symbian OS environment.
Finally we look at some other considerations, such as backup and restore.
Application Localization: Language
This section shows you how to translate your application in to multiple languages. We take SignedAppPhase2 as the starting point for this example.
The Localization project contains the application changes, along with translated text files. This project is not intended to be complete but it does show you how to go about localization.
Overview
Mobile phones are used worldwide, creating the need for applications in many different languages. Symbian OS has considerable built-in support for multi-lingual applications; in particular, it is possible to build a single distributable that supports multiple languages. By adding extra languages, you can expand the market for your application and increase user satisfaction.
A specific language comprises a number of unique characters. The English alphabet is 26 characters. Swedish adds ä, å and ö to make a 29 character alphabet. These types of alphabet, including uppercase and lowercase, are easily defined within an 8-bit (0-255) character set. However, other languages such as Chinese require thousands of characters and a multi-byte approach is needed.
This first type is given the generic name of Single Byte Character Sets (SBCS) and the best known of which is ASCII. Strictly speaking, very few systems use the original ASCII set today. Commonly used single byte character sets include ISO-8859-1 and ISO-8859-15, which are super sets of ASCII and incorporate most western European characters.
To represent character sets with more than 256 characters, Double Byte Character Sets (DBCS) and Multi-Byte Character Sets (MBCS) were developed. For example Shift-JIS is a common MBCS used for Japanese. Over time many other character encodings were developed for different purposes on different computing devices. It was eventually recognized that a unified character encoding standard was required to be able to transfer data around without loss of information.
Unicode was developed to provide a single character set that covered all known characters. It is designed to inter-operate well with the ISO-8859-1 character set, as both character sets use the same numerical values to represent the same character.
Unicode defines a relatively small number of simple encoding schemes:
- UTF-32, each character is stored as 32-bits
- UTF-16, each character is stored as one or two 16-bit values
- UTF-8, each character is stored using between one and four 8-bit values.
UTF-16 is the default encoding for Unicode and is designed such that practically all characters can be represented with a single 16-bit value. Symbian OS uses this encoding and a TText is defined as an unsigned short int.
While UTF-8 is really a replacement for older MBCS encodings, its design enables compression of data, particularly if most characters belong to the original ASCII set (values below hex 128).
Translating our Application Text
When translating our application text we need to understand which character sets are used to display our text. It is also important to specify the formats required back from a translator.
If our required languages are supported within the ISO-8859-1 character set, then apart from actually translating the text, very little has to be done. In general we can use our favourite text editor; the RLS files within the language folders remain standard 8-bit text files and the resource compiler tools default options perform the correct task.
However, let us assume we wish to support Simplified Chinese. The characters in that language no longer belong to the ISO-8859-1 character set; most standard text editors cannot handle such characters and we cannot represent these within a standard 8-bit text file.
To support a language such as Simplified Chinese we need to:
- create the RLS file which contains the tokens and actual text as UTF-8 encoded files
- tell the resource compiler the RLS files should be interpreted as UTF-8 encoded files.
Numerous editors such as Notepad++, Scite and Windows Notepad are capable of creating UTF-8 files. The Symbian resource compiler is, however, very sensitive to the format of such files. In particular, when an editor such as Notepad generates a UTF-8 encoded file, it adds a three byte file type header to the text file which the Symbian resource compiler cannot manage.
Two solutions are available to you:
- use an editor that does not add the three byte file type header to the UTF-8 encoded file
- use a tool that removes the three byte header prior to passing the UTF-8 file to the resource compiler.
Since this is a common problem we wrote a very simple command line tool to remove the three byte header, the entire tool as source code is:
#include "stdafx.h"
#include "stdio.h"
int main(int argc, char* argv[])
{
puts("Utf8 conv");
puts(argv[1]);
puts(" to ");
puts(argv[2]);
FILE* src=fopen(argv[1],"r");
fseek(src,3,0); // Remove the first 3 chars - UTF8 hdr.
FILE* dest=fopen(argv[2],"w");
unsigned char bb[1024];
while(1)
{
size_t ret=fread(&bb[0],1,1024,src);
if (ret>0)
fwrite(&bb[0],1,ret,dest);
if (ret<1024)
break;
}
fclose(src);
fclose(dest);
return 0;
}
The source code is available within the Utf8ConvSrc folder of the Localization folder.
Example of a Translated Application
The Localization project demonstrates how you may go about producing a multi-lingual application. In this example we show how to support French and Simplified Chinese as well as English.
You should note that:
- the translations are not complete
- while the text is valid, it may not be correctly presented in all contexts
- the
Localizationproject is intended to demonstrate how you might go about adding languages to a project. Producing a fully localized Symbian Signed application is beyond the scope of this book.
Adding Language Folders
In our previous example applications we had a folder called English, containing two RLS files, SignedApp.rls and SignedApp_loc.rls. You should recall these files contain token-value pair statements such as:
rls_string STR_R_CMD_NEW "New"
To produce a language variant of an application we need to write an equivalent file, but replace the “value” component with its translated equivalent.
For example, the French may be translated to:
rls_string STR_R_CMD_NEW "Créer nouveau"
The Simplified Chinese may be translated to:
rls_string STR_R_CMD_NEW "新游戏"
| Pro tip:
We recommend splitting the language specific components into separate folders; in our example we have |
Use of RLS Files
The examples in the previous chapters placed all text directly within the application RSS file. While valid, this creates work for translators as they have to be able to read and understand such files to locate and translate the text elements. The examples in this chapter split the text into separate token-value RLS files. If your application is only going to support a single language this creates some additional work for little benefit. However as soon as you need to translate your application the power of RLS files should become evident.
| Pro tip:
It is not a requirement to split language content into RLS files. An equally valid approach is to have multiple RSS files containing inline text where the inline text is translated. Since the RSS files contain structure as well as language, the maintenance task of multiple RSS files is greater than using RSS with RLS files. For this reason it is recommended that you split the actual language text out of your RSS files. Using the RLS filename extension is not mandatory; no tools fail if your file does not have this extension. Even within the various Symbian development environments there are variations of filename extension in use. |
RLS Filenames
For our example application we have chosen to use Notepad to create the UTF-8 encoded RLS files containing our application text. To be able to differentiate our Notepad format UTF-8 source files we have chosen to prefix utf8_ to our filenames. These UTF-8 encoded files are converted to Symbian resource compiler compatible UTF-8 encoded files using our command line tool utf8_conv.exe, included in the Localization folder. The process is wrapped in a batch file utf8conv.bat.
If we used a different UTF-8 text editor we may be able to create the Symbian resource compiler compatible files directly. It is unlikely that such files would need any visual indication of their format; that is, no leading “utf8_” would be required.
Our utf8conv.bat file creates files without the leading “utf8_”. In this way, regardless of which mechanism is used to generate the RLS files, the resource compiler input filenames are the same.
Which Files to UTF-8 Encode?
UTF-8 is a multi-byte encoding scheme. Different values are encoded with a variable number of bytes. When decoding, a UTF-8 file reader has to know how to distinguish between values encoded with one, two, three or four bytes. UTF-8 defines the top bit of an 8-bit byte as a flag to indicate that a value needs a subsequent byte to decode correctly. The hex value 0x61 (the letter ‘a’) does not have the top bit set so a UTF-8 decoder uses the remaining bottom seven bits as the value. In contrast, the hex value 0xE0 (the letter ‘à’) has the top bit set. If a decoder sees such a value, it uses the bottom seven bits and reads the next byte as part of a single decoded value. The letter à cannot simply be stored as its hex value 0xE0; it needs to be encoded over two bytes.
The significance of this is that if we tell the resource compiler that it has to deal with UTF-8 encoded RLS files to handle Simplified Chinese, we also have to convert any RLS files that contain text outside the 0x00 to 0x7F hex value range.
| Pro tip:
In our example application we have chosen to convert the English RLS file to UTF-8. In practice all resource string files, including languages like English which use only basic characters, should be UTF-8 encoded. This greatly simplifies the process of building and packaging your application for distribution. |
Informing the Resource Compiler about UTF-8 Encoded Files
Now we can write UTF-8 encoded files and convert them to be compatible with the Symbian resource compiler, we need to inform the resource compiler that the input files are UTF-8 encoded. This is achieved using the CHARACTER_SET UTF8 statement inside the RSS files. Our application registration localization file now looks like:
#include <AppInfo.rh>
#include <Qikon.hrh>
CHARACTER_SET UTF8
#define EViewIdPrimaryView 0x00000001
#ifdef LANGUAGE_01
#include "..\English\SignedApp_loc.rls"
#endif
#ifdef LANGUAGE_02
#include "..\French\SignedApp_loc.rls"
#endif
#ifdef LANGUAGE_31
#include "..\SimplifiedChinese\SignedApp_loc.rls"
#endif
// This file localise the applications icons and caption.
RESOURCE LOCALISABLE_APP_INFO r_application_info
{
short_caption = STR_R_APP_SHORT_CAPTION;
caption_and_icon =
{
CAPTION_AND_ICON_INFO
{
caption = STR_R_APP_LONG_CAPTION;
number_of_icons = 3;
icon_file =
"\\Resource\\Apps\\SignedApp_icons_0x20000462.mbm";
}
};
view_list =
{
VIEW_DATA
{
uid=EViewIdPrimaryView;
screen_mode=0;
caption_and_icon =
{
CAPTION_AND_ICON_INFO
{
}
};
},
// By adding this in we tell P990s we can be run in flip closed.
VIEW_DATA
{
uid=EViewIdPrimaryView;
screen_mode=EQikScreenModeSmallPortrait;
caption_and_icon =
{
CAPTION_AND_ICON_INFO
{
}
};
}
};
}
Building a Multi-Lingual Application
To actually build the multi-lingual application we need to update our MMP file.
Currently we have statements associated with each resource file such as:
SOURCEPATH . START RESOURCE SignedApp_loc_0x20000462.rss HEADER LANG 01 TARGETPATH \Resource\Apps END
This informs the resource compiler to compile the RSS file once, define the macro LANGUAGE_01, and generate an output file that has the R01 filename extension. In Symbian OS the English language is assigned the value 01.
To add French and Simplified Chinese we need to update these directives. French has the language ID of 02; Simplified Chinese is 31. These values are defined by the TLanguage enum.
Our resource statements now look like:
SOURCEPATH . START RESOURCE SignedApp_loc_0x20000462.rss HEADER LANG 01 02 31 TARGETPATH \Resource\Apps END
This informs the resource compiler to compile the RSS three times, once for each of the three languages we have defined. The macros LANGUAGE_01, LANGUAGE_02 and LANGUAGE_31 are defined as appropriate. The result is three files, a R01 a R02 and a R31.
Looking back at the original RLS file the statements you see:
# ifdef LANGUAGE_01 # include "..\English\SignedApp_loc.rls" # endif # ifdef LANGUAGE_02 # include "..\French\SignedApp_loc.rls" # endif # ifdef LANGUAGE_31 # include "..\SimplifiedChinese\SignedApp_loc.rls" # endif
We include a different set of token-value pairs from a language specific RLS file dependent on which of the three times the file is compiled.
| Pro tip:
If you build a multi-lingual application using the CodeWarrior Testing Simplified Chinese on the emulator is quite difficult since the Chinese fonts are missing. Your application either displays blank lines, or displays lines containing a series of square boxes. |
Deploying a Multi-Lingual Application
Applications are deployed as SIS files. Such a file usually contains all the components for your application to operate correctly. SIS files are created by a tool called MakeSIS. They take as input a PKG file. One of the fields in the PKG file is the textual name of your application presented to the user when they install the application. If you have a multi-lingual application the name may vary between languages. As discussed above, regular 8-bit text files cannot contain characters such as Simplified Chinese. Therefore a PKG file can either be an 8-bit text file or, unlike RLS files, a 16-bit Unicode file.
The Unicode files created by Notepad and those handled by the MakeSIS tool are compatible so you can create Unicode PKG files with Notepad without any other tool being required.
Our Localization PKG file contains the following content:
; Specify the languages we support, items must appear in this order subsequently.
&EN,FR,ZH
; List of localised vendor names - one per language.
; In order EN,FR,ZH...
%{"MyCompany","MyCompany","MyCompany"}
; The non-localised, globally unique vendor name (mandatory).
: "ZingMagic Limited"
; What our app name is displayed as, in order EN,FR,ZH
; NOTE this is only intended to be example text, 'SignedApp' does not translate to
; the text presented here (Chess).
# {"Signed App","Échecs","国际象棋"},(0x20000462),1,00,01,TYPE=SA
; ProductID for UIQ 3.0
; Product/platform version UID, Major, Minor, Build, Product ID
[0x101F6300], 3, 0, 0, {"UIQ30ProductID","UIQ30ProductID"."UIQ30ProductID"}
; The lang independant files we install.
"..\..\..\epoc32\release\gcce\urel\SignedApp_0x20000462.exe"-"!:\sys\bin\SignedApp_0x20000462.exe"
"..\..\..\epoc32\data\z\resource\apps\SignedApp_icons_0x20000462.mbm"-"!:\resource\apps\SignedApp_icons_0x20000462.mbm"
"..\..\..\epoc32\data\z\resource\apps\SignedApp_0x20000462.mbm"-"!:\resource\apps\SignedApp_0x20000462.mbm"
"..\..\..\epoc32\data\z\private\10003a3f\apps\SignedApp_reg_0x20000462.rsc"-"!:\private\10003a3f\import\apps\SignedApp_reg_0x20000462.rsc"
; Backup registration.
"backup_registration.xml"-"!:\Private\20000462\backup_registration.xml"
; The language resource files, in order EN,FR,ZH
; The registration file localised component.
{
"..\..\..\epoc32\data\z\resource\apps\SignedApp_loc_0x20000462.r01"
"..\..\..\epoc32\data\z\resource\apps\SignedApp_loc_0x20000462.r02"
"..\..\..\epoc32\data\z\resource\apps\SignedApp_loc_0x20000462.r31"
}-"!:\resource\apps\SignedApp_loc_0x20000462.rsc"
; The primary app resource file.
{
"..\..\..\epoc32\data\z\resource\apps\SignedApp_0x20000462.r01"
"..\..\..\epoc32\data\z\resource\apps\SignedApp_0x20000462.r02"
"..\..\..\epoc32\data\z\resource\apps\SignedApp_0x20000462.r31"
}-"!:\resource\apps\SignedApp_0x20000462.rsc"
Two Character Language Codes
The PKG file defines what languages our application installation supports using two-character language codes, EN for English, FR for French and ZH for Simplified Chinese. The full set of language codes is defined in the Language code table section of the Package File Format chapter of the SDK documentation.
A customer is prompted to choose which language variant they wish to use on installation. Since our application supports three languages the customer effectively chooses between index values 0, 1 or 2. This index choice drives which components are extracted; that is, if the user chooses Simplified Chinese, since that option is index value, 2 the text at index 2 from the application name statement is used:
# {"Signed App","Échecs","国际象棋"},(0x20000462),1,00,01,TYPE=SA
Similarly the R31 files are chosen as they are at index 2 of the list of language files available to the installer.
| Pro tip:
If you choose Simplified Chinese when an application is installed, and your phone does not support Simplified Chinese, your menu options contains a number of blank lines or square boxes where the text is supposed to be. This is perfectly normal because the Simplified Chinese fonts are missing. We recommend that you test on the appropriate Chinese versions of your target mobile phones. |
Internationalization
Internationalization is more than simply translating the text strings in your application into different languages; it also involves changes in the way information is presented. For example, the date 03/04/07 is interpreted as 3rd April 2007 in the UK and 4th of March 2007 in the USA and 7th April 2003 in Sweden, China or Japan. Such ambiguities need to be resolved if an application is to present the intended meaning when used on different phones.
Strictly speaking, internationalization is the process of enabling your application to support locales. A locale is composed of a language plus standards and formats data. The Localization example showed you how to support different language text. This section focuses on how to support standards and formats.
The class TLocale encapsulates most of the standards and formats information. As a simple example, we might want to improve on the display of a number by adding separators.
The following code would produce a string "10200".
TInt val=10200; buf.Num(val);
An improvement to produce the string “10,200” might be:
TInt thousands=10;
TInt rest=200;
buf.Format(_L("%d,%d"),thousands,rest);
However, this code assumes that the ',' character is the correct thousands separator and that the concept of thousands separators is correct for all locales.
Looking at the TLocale class you can see the method ThousandsSeparator() which will report the character to use. Therefore an improvement might be:
TLocale locale;
TInt thousands=10;
TInt rest=200;
buf.Format(_L("%d%c%d"),thousands, locale.ThousandsSeparator(),rest);
The TLocale class shows that the following data items are dependent on the current locale:
- date and time formats
- clock display
- number display and formats
- currency display and formats
- workdays and start of week.
UIQ 3 phones are typically set to the correct locale as part of customization. The user can view and change locale settings in the Control Panel, Device, Number Formats and Time & Date settings.
If you are displaying these data items to users and you want them to be correctly understood, then you must apply locale information correctly.
Often a standard UIQ 3 control can be used to display your content for you. Such controls usually take the compact form of your information and format it correctly on your behalf.
Date and Time Formatting
Symbian OS provides extensive facilities to format date and time information in locale independent fashion.
TTime time;
time.HomeTime();
time.FormatL(bb,_L("%*D%*N%*Y%1 %2 %3, %-B%:0%J%:1%T%:3%+B"));
The above code fragment would produce “6 Jun 07, 7:30 pm” if run with a UK locale, “Jun 6 07, 7:30 pm” if run in a USA locale, and 07 Jun 6, 7:30 pm” if run in a Chinese locale. The application code does not have to change for this to happen. A full description and examples of usage of the format string is available in the UIQ 3 SDK documentation.
Number and Currency Display
TLocale also defines what character a particular locale is expecting an application to use as a thousands separator. Other characteristics such as how negative currency amounts should be displayed, where currency symbols should be placed and whether space characters are required between currency symbols and values can all be queried from the TLocale information. Unlike date and time formatting, Symbian OS does not provide any specific functionality to help with number and currency formatting. For example, TLocale tells you that negative currency amounts should be displayed as (£200) as opposed to, say, -£200, but your application must do the formatting itself.
Resource Files
In general we recommend that no more than a single string formatting entity is placed in a resource string. For example, different locales have different rules as to the placement of nouns and verbs within a sentence. Placing more than a single formatting entity within a resource implies the order of those two items is fixed across all locales. This is not universally true.
| Pro tip:
If you are familiar with the S60 environment you may have used the |
Application Performance
Computational Capability of Modern Mobile Phones
Mobile phones, like personal computers, get ever faster CPUs, more memory, better screens, longer battery life, and so on. In turn, software applications become more sophisticated. One example of this is the high quality video playback on the MOTO Z8 phone. In this section we run some test cases to get an idea of the capability of UIQ 3 mobile phones.
The Performance example application contains the source code used to generate the performance information that we present in this chapter. Numerous factors will dictate the exact numbers you obtain, such as which complier you use, which hardware revision you have and what other activities are occurring on the phone under test, say battery charging; after all, it’s a multi-tasking operating system. We used the standard GCCE complier, with no additional optimizations enabled. Our results were obtained by running the application numerous times, averaging the values obtained, on a standard Sony Ericsson W950i.
The objective of the exercise is to give a sense of scale to the various activities an application can perform. It is not intended to be a definitive set of data and will not accurately predict how a real world application will perform. Consequently the numbers presented are rounded up or down to aid in the comprehension of the results.
Our first set of performance information relates to how many function calls of varying types a mobile phone performs. We also include values for integer and floating point calculations to give a general feel for the performance.
| Test | Result |
| Integer operations: add / subtract | 23,000,000 operations/second |
| Floating point operations: multiply / square root | 360,000 operations/second |
| Calling a standard C like function | 5,500,000 calls/second |
| Calling a non virtual method belonging to a class | 5,200,000 calls/second |
| Calling a pure virtual method | 4,500,000 calls/second |
Calling a non virtual method under a TRAP harness
| 380,000 calls/second |
RunL()’s
| 70,000/second. |
Rather than placing too much emphasis on the exact values, most of the useful information is obtained by comparing the relative values. For example, the above figures inform us that you can perform approximately 70 method calls, 330 integer operations or five floating point operations in the same time as it takes to perform a single RunL().
Of particular significance from the above information is that if you partition a task using active objects but only perform a few method calls or calculations per partition, the vast majority of the time taken to perform a task is taken in scheduling the RunL() itself.
For example, in a spreadsheet type application, if a single cell were evaluated in each RunL(), and each cell contained an average of five floating point calculations, then 50% of the elapsed time would be spent calculating the cell content and the other 50% spent in the overhead of calling the RunL(). Apart from considerably slowing down the calculation, by executing twice as much code, you reduce the battery life. Both effects are undesirable. In this example we can easily reduce the RunL() overhead by calculating multiple cells in each RunL(). If we calculated 100 cells per RunL(),instead of one, then approximately 99% of the time is used calculating the result and only 1% in the RunL() overhead. The spreadsheet would be calculated at almost optimal speed.
The performance table suggests we can perform 500 floating point operations, or calculate 100 cells, in approximately 0.001 seconds. In other words we can check for other events, such as key presses (by virtue of partitioning the operation with active objects), approximately every 1/1000th second in such an environment.
The rate at which RunL()s are called effectively defines the responsiveness of your application. As we discussed earlier, being able to respond to user generated events within 1/10th second, when an otherwise compute-intensive task is running, is perfectly acceptable to the user. In our example spreadsheet application, we can calculate 10,000 cells in 1/10th second.
The above implementation analysis depends on your application-specific event sources. For example, if an application has a high frequency timer delivering events, such as an animation timer ticking every 1/25th second, you need to ensure that other operations within your application can complete in that timescale for the timer events to be processed at a uniform rate.
You should recall the RunL() of one active object cannot be interrupted by the RunL() of another active object belonging to the same active scheduler. If you had a spreadsheet recalculation whose RunL() method took 1/10th second to complete, regardless of the rate at which you set up any timer, the RunL() of the timer active object will not be called until your application returns control to the active scheduler. While a multi-threaded environment can help to avoid such blocking issues, if the task of the timer is to interact with the underlying data being recalculated, different synchronization issues arise. Active objects resolve many synchronization issues but rely on the programmer to partition long running tasks correctly.
| Pro tip:
The entire point of partitioning tasks is to allow other events to be processed in between the partitions. A core objective is to be able to respond to user generated events within an acceptable timeframe. To get optimal performance from an application, some thought and care needs to be exercised when partitioning tasks. |
You may wish to note the Pro and OEM editions of Carbide.c++ IDE contain profiling tools, which you may prefer to use in order to obtain better performance information about your application.
Performance Issues in Coding
Most of us are familiar with general programming practices and how to structure some code to increase performance at one level or another. For example, most programmers would recognize that repeatedly calling functions within loops to obtain the same value is inefficient.
For example, the first piece of code shown below should be replaced with the second:
for (TInt i=0;iCount();i++)
{
// Perform task.
}
const TInt count= aObj->Count();
for (TInt i=0;i<count;i++)
{
// Perform task.
}
We consider some of these general programming practices but this section is not exhaustive since these type of performance issues are well documented in many general purpose programming books. We focus more on aspects specific to Symbian OS.
As a general rule, many performance issues can be solved by reducing the amount of code being executed to achieve the same end result. This in turn means you need to understand the code you are writing and the consequences of the lines of code in your application. As with programming on any platform, it is unlikely that there is a single right or wrong way to achieve a given task. The points below are issues you should consider when writing your application.
For all of the points below there are situations where it is perfectly reasonable and very sensible not to use any suggested optimal route. You as a programmer need to examine your specific situation and apply any rules within the context of that situation.
Constructors
Even innocuous looking declarations such as the following cause code to be executed:
TRect rect;
In this case, the constructor will be called to set each of the four integers within the class to 0. If you happen to declare the TRect inside a loop then, for each iteration of the loop, the constructor will be called. In some cases this may be exactly what is required. However, in most cases an assignment of values to the four member variables quickly follows the object declaration. By simply declaring the object outside of the loop you get a 50% increase in performance since only half as many assignments occur.
For example, replacing the first piece of code with the second reduces the number of memory assignments within the loop by half:
for (TInt i=0;i<count;i++)
{
TRect rect; // Performs 4 memory assignments.
rect.iTl.iX=val1;
rect.iBr.iX=val1+width;
rect.iTl.iY=val2;
rect.iBr.iY=val2+height;
}
TRect rect;
for (TInt i=0;i<count;i++)
{
rect.iTl.iX=val1;
rect.iBr.iX=val1+width;
rect.iTl.iY=val2;
rect.iBr.iY=val2+height;
}
This example may be trivial but as a general rule you should have a clear understanding of exactly what tasks an object constructor performs, particularly objects that are instantiated through the Symbian two phase construction idiom.
Pass by Reference Compared to Pass by Value
Compare the following functions:
void CExample::PassByReference(TRect& aRect)
{
if (aRect.Width()>aRect.Height())
{ // Perform functionality.
}
}
void CExample::PassByValue(TRect aRect)
{
if (aRect.Width()>aRect.Height())
{ // Perform functionality.
}
}
Superficially the functions look identical and perform the same task; however, the PassByReference() method call is more efficient to set up. Only a single integer value has to be calculated and passed compared to four for the PassByValue() method. In practice there are few cases where PassByValue() is preferable to PassByReference(), especially since we can define PassByReference() as the following:
void CExample::PassByReference(const TRect& aRect);
This eliminates the chance that the implementation of PassByReference() can accidentally modify the contents of the passed parameters.
Return by Reference Compared to Return by Value
Compare the following functions:
_LIT(KExampleName,"c:\\ExampleFile.txt");
void CExample::ReturnByReference(TFileName& aName)
{
aName=KExampleName;
}
TFileName CExample::ReturnByValue()
{
return(KExampleName);
}
Again, both perform the same task, however the ReturnByReference() is usually more efficient. In both the above cases a significant amount of content is being moved from one place to another. In the case of the ReturnByValue() method, an entire object is being constructed and returned to the caller. A third, significantly more efficient variant would be:
const TFileName& CExample::ReturnByReference();
In this case, only a single 4-byte entity is being returned to the calling code. No copy of the content itself is occurring. While not applicable in every situation, this third case is often more than sufficient.
Use of const Keyword
By declaring variables, methods and parameters as const, you are informing the reader and the compiler about some attributes of the entity. Different compliers are able to use this type of information in different ways. Rather than simply assuming a compiler will process the code correctly, we recommend that you give as many hints as possible through the use of language keywords.
In the return by reference section one option was to use:
const TFileName& CExample::ReturnByReference();
Here the const keyword is being used to ensure the caller cannot easily change the content of the variable being returned.
In contrast, you should be careful about simply adding the const keyword to existing methods, particularly virtual ones, since doing so changes the method signature. Unless all overloads of the virtual method are changed at the same time any overloaded methods will not match the signature.
Kernel Executive Calls
A kernel executive call is one where user side code is allowed to enter the processor privileged mode in order to access kernel side resources in a controlled fashion. A significant amount of code is executed to change between modes. One of the
most common cases of this in Symbian OS applications is through the use of the TRAP() and TRAPD() macros.
As we saw in the function call table presented earlier, calling a method under a TRAPD results in a 93% decrease in the number of method calls that can be made per second. Given its overhead, the TRAP macro should be used sparingly and, as with object constructors, you should declare them outside of loops wherever possible. Conversely, you can execute approximately the same number of TRAPDs as floating point operations per second. In that context, it is perhaps not the slowest operation you can perform, and so adding complexity to the natural flow of an application simply to move a TRAPD outside of a loop may not generate much performance benefit.
Context Switches
As in most operating systems, a context switch between threads requires processor time. An operating system has to move the calling thread from the currently running list to either the ready to run or suspended lists. It then has to move the thread being called to the currently running list. A number of other housekeeping tasks, such as memory mapping, are also needed before the called thread can resume execution. As such, the number of context switches should be kept to a reasonable level. As with many aspects of programming this is often a trade-off between efficiency and resource usage. A good example of this trade-off is reading the content of a file. Reading a single byte at a time causes a large number of context switches; in contrast, reading the entire content in one read operation may require a lot of memory.
Our Performance application generates the following results:
| Test | Result |
| Writing content to file, 100 bytes per write request | 72 KB/second |
| Writing content to file, 10,000 bytes per write request | 235 KB/second |
| Reading content from file, 100 bytes per request | 270 KB/second |
| Reading content from file, 1000 bytes per read request | 1.5 MB/second |
| Reading content from file, 10,000 bytes per read request | 5.2 MB/second |
There is a significant overhead in performing context switches to the file server in both read and write operations. For example, reading 100 bytes at a time results in a throughput of only around 5% of that achieved compared to reading 10,000 bytes at a time.
On the other hand, you should note that reading files in very large blocks may adversely affect the performance of the rest of the phone, since no other application can access the disk while your request is being serviced.
| Pro tip:
Unless there are other conditions your application needs to consider, or it is convenient to structure your application as such, there is reduced value in using the asynchronous file read and write operations in most normal cases on modern UIQ phones, especially when low volumes of data are to be processed. Even with a modest 1k buffer, applications can read around 1.5MB per second from disk. |
Connections to Servers
The construction of an open connection between your application and a server within the system requires considerable resources. Assuming that the server is running then context switches are required to form the connection. Kernel resources are required to monitor and maintain the connection and the server needs to set up a sub-session to represent the new client connection. You should therefore avoid the frequent opening and closing of connections. As always, a trade-off is required between having a permanent open connection to a server, which uses resources, compared to only having a connection as and when required.
For example, most applications will have a permanent open connection to the file server and should reuse that single connection. This reuse is demonstrated within our Performance example where we obtain the file server handle via the Eikon environment. In contrast, a connection to the communications server is normally created and destroyed each time communications are required. This is demonstrated by the communications example applications presented in Communications chapter.
Memory Management
The Sony Ericsson P990i has 64 MB RAM and the Sony Ericsson P1i has 128 MB. Although this may seem large, a mobile phone needs to run many applications that users expect to be graphically and functionally rich. Memory is therefore one of the resources that your application must use very carefully.
Applications are still responsible for managing their own memory requirements, and should aim to use as little memory as possible to perform their tasks. Good memory management not only leads to less memory being used but also superior, application performance.
For example, you may have a word processor type application that can handle large documents. If you store the content in a single memory cell and then insert a character at the beginning of the document, it is likely that you would have to adjust every other character position by copying the memory. In contrast, if you store every character within its own memory cell you could eliminate the memory copying; however, a heap containing a large number of allocated cells brings its own performance problems. For this particular example, a memory management scheme half way between these two extremes is preferable. Indeed, Symbian OS has support for such a scheme, called segmented memory management, which is available through the CArrayX classes.
| Pro tip:
One of the biggest factors influencing the performance of an application is the algorithm design. A highly optimal implementation of a poor algorithm is no substitute for a superior algorithm. This is also true in memory management. Get the lower level data structures correct and the application tends to look after itself at a higher level. |
Heap usage and fragmentation
While minimizing the amount of heap being used is one consideration, another is heap fragmentation. When a heap becomes very fragmented, application performance can suffer. In Symbian OS a heap is made up from two lists, one containing the list of allocated cells, the other, the list of free cells. When a heap becomes fragmented these lists become long. Traversing the list of free cells to find a cell capable of containing a particular amount takes longer. In addition, each heap cell has an overhead associated with it that is required to maintain the lists. An allocated heap cell has a 4-byte header overhead, therefore a heap cell big enough to contain 16 bytes has 25% overhead, whereas a heap cell big enough to contain 256 bytes only has a 1.5% overhead. Having a large number of small heap cells results in considerable memory wastage as well as performance issues.
Heap usage and file names
A file name buffer, TFileName, is a good example of a data structure that should be considered carefully. On the one hand it needs to be defined to be large enough to store the longest possible file name, 256 characters. On the other hand few, if any filenames, including the drive and path information, exceed more than around 64 characters on a real phone. In this case, perhaps 192 characters’ worth of heap memory (384 bytes) are left unused for every TFileName defined. If you have a data structure that needs to contain a filename, then you should consider how many of these data structures you might have in your application.
For example, in our SignedAppPhase3 application we store an array of folder entries, and each entry contains a TFileName. Assuming an average full filename of 64 characters and around 32 bytes of storage for the remainder of the structure, over 60% of the heap memory required to store a single entry is not used. It is actually worse than that in the SignedAppPhase3 example since we don’t store the drive and path name within the individual entry TFileName. Therefore maybe only 16 characters of the full TFileName are used. We could be wasting up to 90% of the heap memory allocated to store each entry!
Wasting up to 90% of the memory required to store a data structure could be acceptable if the amounts of memory being considered are small. For the SignedAppPhase3 example we may be wasting 350 bytes per entry. Since we only have a small number of entries, around 10, we would be wasting around 3.5 KB of heap memory. The advantage here is reduced application complexity.
In contrast, if you were writing a proper file manager type application where a folder may contain hundreds or perhaps even thousands of files, you can easily waste hundreds of kilobytes of heap memory. In our current scheme, 1000 entries would waste 359 KB! To avoid this type of issue you should consider whether to use a TFileName at all. A preferable solution would be to store each of the filenames in their own appropriately sized memory cell.
Heap usage and variable arrays
Variable arrays, either in the form of the RArray or CArrayX classes, are commonly used within Symbian OS to store content. Both sets of classes are designed to be able to store an unlimited number of entries of arbitrary size. In both cases, incorrect class usage can cause performance problems or excessive heap usage.
Performance problems can arise if the granularity is not appropriate for the usage of the class. For example, if we set the granularity to one, meaning that we only extend the array to accommodate one more element each time it has to expand, and then proceed to add 100 entries, this will cause the underlying heap cell containing the content to be resized 100 times. It is highly likely that each resize will require a memory reallocation, hence a copy of the data from one heap cell to another.
In contrast, excessive heap usage can occur if we set a granularity to 100 but only ever add a single entry. 99% of the allocated memory is left unused.
In the case of variable arrays, careful use of array granularity, SetReserveL() and Compress() methods should be used to optimize performance and memory usage.
The following table summarizes a typical set of test results obtained from our Performance application:
| Test | Time taken |
Insert 1,000,000 entries into a CArrayX with a granularity of 1
| 4.85 seconds |
Insert 1,000,000 entries into a CArrayX with a granularity of 100,000
| 1.74 seconds |
Insert 1,000,000 entries into a RArray with a granularity of 1
| 5.42 seconds |
Insert 1,000,000 entries into a RArray with a granularity of 100,000
| 0.57 seconds |
Again, rather than looking at the absolute results, you should observe that in the case of CArrayXs around 65% of the time is spent dealing with the memory reallocation. In the case of RArrays it is much worse with about 90% of the time spent handling the memory reallocation.
Heap usage and flat arrays
When using flat arrays, that is, arrays where the content is stored within a single memory cell, some consideration should be given to the size a single memory cell may reach. In our SignedAppPhase3 application, if we had 1000 entries we would require a single memory cell in excess of 500 KB. Attempting to add an entry means that we must find a free cell in excess of 500 KB to expand into, making a total of more than 1MB. The default EPOCHEAPSIZE is 1MB, so you will get an Out of Memory situation.
In this scenario you should try to ensure you do not allow a single memory cell to grow too large. In our particular example, each of our entries is about 550 bytes. Breaking an entry up by making the TFileName field of the entry a separate memory cell would solve several of the issues we have discussed here.
| Pro tip:
Very few applications should have a requirement to change the default heap size settings. If you find your application is running out of heap, it is just as likely that a change of implementation will solve your memory problem as increasing the heap size. The current choice of data structure in the |
RArray verses CArray
One of the reasons cited for moving over to using RArrays is the superior performance they offer. Until now there has been very little hard evidence available on exactly the performance differences.
| Test | Time taken |
10,000,000 array entry accesses on a CArrayX class
| 10.58 seconds |
10,000,000 array entry accesses on a RArray class
| 3.50 seconds |
| 10,000,000 array entry accesses on a object property based standard C array | 1.22 seconds |
Insert 1,000,000 entries into a CArrayX with a granularity of 100,000
| 1.74 seconds |
Insert 1,000,000 entries into a RArray with a granularity of 100,000
| 0.57 seconds |
Our figures show that RArrays are approximately three times faster than CArrayX classes, both at appending entries and accessing them.
| Pro tip:
While these figures demonstrate that |
Matching Color Depths
If the color depth of a window is not the same as the color depth of the physical screen then a translation of colors has to occur when the window is displayed on screen. Similarly, if the color depth of a bitmap does not match that of the window, the color depth must be adjusted. On mobile phones, we have observed that mismatched color depths are around 10 times slower than matching color depth bitmaps.
Other Considerations
Foreground and Background Events
The window server generates numerous events, such as key events, pointer events, redraw events as well as foreground and background events. Most applications do not see the TWsEvent directly; the framework contains significant amounts of code to process these low level events. Applications typically see keys through the OfferKeyEventL() method, pointer events through the HandlePointerEventL() method, and foreground and background events through the HandleForegroundEventL() method.
So what should my application do with foreground and background events? Unfortunately there are no universal rules since applications vary significantly. However when receiving a background event, your application should make every effort to reach a quiescent state, release any unnecessary resources and remain quiescent, performing as little processing as possible. In doing so, you release as many resources as possible back to the system, including the processor resource. The overall system remains responsive and a user is comfortable that your application is not adversely affecting their phone, particularly the battery life.
Some examples might be:
- In a game, you should stop at the current point when you go background and resume in some fashion when you come foreground. It would be quite reasonable to enter automatically a paused mode on background and require the user to resume on foreground. The one thing you should not do is continue running the game, or any polling loop within the game. By the time the user returns to the application the level or game is likely to have finished.
- In a push Email solution, you might not take any action at all. If you are interacting with the view architecture, your view may automatically shut down. In such a case, if a new Email arrives, a part of your application needs to know that there is no UI, and therefore not attempt to update it.
- You have an application that needs to perform a very long calculation. There is no absolute requirement to stop processing; however in such a scenario you should ensure that the processing you are performing does not materially affect the performance of the remainder of the phone. In particular, you should ensure that you are able to respond to any shut-down events that may be automatically generated by the system. In practice, this is no different to ensuring you can respond to any user generated events when foreground.
- In an application that uses a non sharable resource, such as a camera, the resource should be released when your application goes background to ensure other applications that wish to use that resource can do so. The
SignedAppPhase3application in the Multimedia chapter demonstrates how you might go about owning and releasing the camera resource.
Because Symbian OS is a multi-tasking operating system by design, you generally have to do very little. For example, unless you have gone out of your way to stop the window server acting correctly, your process priority will be adjusted when your application moves between foreground and background. This ensures that the application visible to the user is responsive, even if your application is compute intensive when it goes background. It does not, however, stop poorly implemented applications needlessly running in background and potentially draining the battery.
Backup and Restore
Applications are responsible for declaring their own backup and restore policy. Your application can have a zero backup policy, in which case neither the application data nor the application is backed up; therefore it will not be restored either.
In most cases we want our application and its associated data to be backed up, such that when the user restores their phone, both the application and data are restored. This is the policy we have adopted for our Symbian Signed compatible application, SignedAppPhase1, as presented in the Building an application chapter. The application-specific policy is defined within the backup_registration.xml file included in an application installation package. This file must be present in your installer to pass Symbian Signed.
Some applications need actively to participate in backup and restore, particularly those that wish to perform synchronization of data with alternate data sources as opposed to a simple backup of the entire application data.
A full discussion of active backup is beyond the scope of this book. The document PC Connectivity: How To Write Backup Aware Software for Symbian OS v9 provides comprehensive details on all aspects of passive and active backup, along with numerous examples of backup_registration.xml files and the full XML DTD.
Startup and Exiting of Applications
The Symbian Signed Test Criteria discussed in the Symbian Signed chapter give some guidelines on this functionality. In particular, C++ applications should present some visual feedback that they are starting within five seconds of the application being launched. Applications should also be prepared to exit, for example, if the user chooses ‘Close’ from the task manager. In general, enough of the UI needs to be made available within the ViewActivatedL() methods to allow the application to present some kind of user interface. Any slow operations required to fully populate a specific view can be run as separate active objects, the completion of which causes the UI to be updated.
Code
Code for this chapter
RefiningYourApplication_code.zip
