Sorry about the red box, but we really need you to update your browser. Read this excellent article if you're wondering why we are no longer supporting this browser version. Go to Browse Happy for browser suggestions and how to update.

UI life cycle

All BlackBerry device applications include an application class that is derived from either the Application class, which is included in the net.rim.device.api.system package, or the UiApplication class, which is included in the net.rim.device.api.ui package. The Application class is the base class for all applications on the device, and applications that are not required to respond to user input should extend this class. The UiApplication class is a subclass of Application, and applications that provide a UI should extend this class.

There are three basic phases to the life cycle of any BlackBerry device application:

  • Starting
  • Running
  • Terminating

Starting

An application can be started on a BlackBerry device in the following ways:

  • By a BlackBerry device user clicking an icon on the Home screen
  • By the OS automatically when the device starts
  • By the OS at a scheduled time
  • By another application

Regardless of how an application is started, the application manager on the device is responsible for starting the process that the application runs within. The ApplicationManager class, which is included in the net.rim.device.api.system package, allows applications to interact with the application manager to perform tasks, including the following:

  • Run an application immediately or at a scheduled time
  • Interact with processes, including retrieving the IDs for foreground applications
  • Post global events

The application manager starts an application by retrieving a new process on the device and creating a thread within that process to invoke one of the entry points of the application. For many applications, the main() method of the application class is the single entry point that is invoked, but you can specify multiple entry points for your applications. You can use multiple entry points to create different ways for a user to start an application. For example, if your application allows users to create a new document, you might provide users with two icons that they can click to start the application. Users could click one icon to open the application to its main screen and the other icon to open the application to the screen that allows them to create a new document.

Running

After the application manager on the BlackBerry device starts an application, the application can either run a series of commands until the series is complete, or it can enter a loop where it waits for and processes events until it receives an event indicating that it should terminate. Typically, an application invokes enterEventDispatcher() of the Application class near the beginning of its main() method, which allows the application to receive events and update the UI of the application.

When an application that has a UI starts, it typically pushes the first screen of the application on to the display stack by invoking pushScreen(). This screen is the first screen that a BlackBerry device user sees when the application starts. This screen can then push other screens on to the display stack in response to specific events, such as user input. Each screen responds to user input when it is on top of the display stack and displayed to the user.

Terminating

You can invoke System.exit() to terminate your application. This method causes the BlackBerry Java Virtual Machine to terminate all of the processes and threads of the application. Alternatively, you can terminate an application by popping the last screen off of the display stack, which results in a call to System.exit().

Because you typically start an application by invoking enterEventDispatcher(), which doesn't terminate, your application should provide a way to terminate. Applications that receive user input might provide a handler for a Close menu item, and applications that don't receive user input might terminate in response to a specific event on the BlackBerry device.

Event thread

Each BlackBerry device application has a special thread associated with it called the event thread. The event thread is the thread that has the event lock, meaning that the thread is responsible for executing all code for drawing and handling events. Only the event thread can process incoming events and update the UI of the associated application.

When an application class invokes enterEventDispatcher() in main(), the thread that started the application acquires the event lock and becomes the event thread. From this point onwards, the application runs in an event processing loop, receiving and responding to events that occur on the BlackBerry device, such as user input. You can create listeners to respond to specific events, or use listeners that are included in the BlackBerry Java SDK, and the event thread invokes these listeners in response to the corresponding events.

Because the event thread is the only thread that can process events and update the UI of an application, you should not use this thread to perform tasks that might fail or take a long time to complete. If you do, your application can become unresponsive and might be terminated by the device. For example, if your application needs to open a network connection, you should create a new thread to perform this task.

Event lock

The event lock in a BlackBerry device application allows a thread to process events and update the UI of that application. When an application class invokes enterEventDispatcher() in main(), the thread that started the application acquires the event lock.

You might need to update the UI of your application from a thread that is not the event thread. For example, a thread that manages network connections might need to update the UI to reflect a change in network status. You can do this in two ways:

  • You can acquire the event lock by invoking getEventLock() of the Application class, and synchronize on the event lock before performing your task. By using this approach, your thread functions like the event thread, and you can update the UI by using this thread.

  • You can inject an event into the message queue of your application by invoking invokeAndWait() or invokeLater() of the Application class. This approach allows your thread to request that a task be completed by the event thread as soon as possible (but not necessarily immediately). You inject an event in the form of an object that implements the Runnable interface. The event thread processes the event by invoking the object's run() method.

You should acquire and synchronize on the event lock if you need to perform a quick or urgent update of the UI. You should inject an event into the message queue if it is acceptable to experience a delay before your task runs. In each case, you should not run tasks that might block or take a long time to complete.

Code sample: Creating the framework for applications with a UI

The following code sample defines both the application class and screen class in the same .java file. It is good practice to place these definitions in separate .java files. Each screen class that your application uses should also be defined in a separate .java file.

import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.container.MainScreen;

//Create the class that represents your application. Extend the 
//UiApplication class to create an application that has a UI.
public class MyApplication extends UiApplication
{

//Implement main() in your application class. This method represents 
//the primary entry point for your application.
    public static void main(String[] args)
    {

//In main(), create an instance of your application class.
    	   MyApplication myApp = new MyApplication();

//Invoke enterEventDispatcher() to start the event thread 
//and allow your application to process events.       
        myApp.enterEventDispatcher();
    }
 
//Implement the constructor for your application class.   
    public MyApplication()
    {

//In the application constructor, invoke pushScreen() to push an instance 
//of your application's first screen on to the display stack.
        pushScreen(new MyApplicationScreen());
    }    
}

//Create the class that represents your application's first screen. 
//Extend the MainScreen class to create a screen that consists of a title 
//section, separator element, and main scrollable section.
class MyApplicationScreen extends MainScreen
{

//Implement the constructor for your screen class.
    public MyApplicationScreen()
    {         

//In the screen constructor, perform any initial tasks to set up your screen. 
//For example, to set a title for your screen, invoke setTitle().   
        setTitle("My First BlackBerry Device Application");
    }
}

Code sample: Overriding layout()

import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.container.MainScreen;

import net.rim.device.api.ui.component.ButtonField;

public class OverridingLayoutDemo extends UiApplication
{
    public static void main(String[] args)
    {
        OverridingLayoutDemo theApp = new OverridingLayoutDemo();
    	   theApp.enterEventDispatcher();
    }
    
    public OverridingLayoutDemo()
    {
    	   pushScreen(new OverridingLayoutDemoScreen());
    }
}

class OverridingLayoutDemoScreen extends MainScreen
{
	   public OverridingLayoutDemoScreen()
	   {
		      setTitle("Overriding Layout Demo");
		
//In the screen constructor, create a new MyCustomButton object. 
//The MyCustomButton class extends the ButtonField class and 
//provides a custom layout() method.
		      MyCustomButton theButton = new MyCustomButton("Click here.");
		
//Add the MyCustomButton object to the screen.
		      add(theButton);		
	   }
}

//Create the MyCustomButton class.
class MyCustomButton extends ButtonField
{

//Implement a constructor for MyCustomButton to accept a String parameter.
//Invoke super() to invoke the constructor of the superclass, ButtonField,
//and set the String parameter as the label of the button.
	   public MyCustomButton(String text)
	   {
		      super(text);
	   }
	
//Implement layout() to specify custom layout instructions for 
//MyCustomButton. In this example, layout() sets the size of the 
//field to either 100 x 100 pixels or the maximum dimensions that 
//are provided by the button's manager, whichever value is smaller.
	   protected void layout(int width, int height)
 	  {
		      setExtent(Math.min(100, width), Math.min(100, width));
	   }
}

Best practice: Reducing the number of layouts

The layout() method of a field is invoked when the contents of the field must be arranged on the screen. This method lays out the content of the field, such as text, graphics, or other fields. Because this method is invoked often, including when fields are added to or removed from the screen and when the BlackBerry device is rotated, it's important to try to reduce the number of calls to layout() in your applications.

Consider the following guidelines:

  • Add groups of fields, instead of individual fields, to a screen. Each time you add a field to a screen, the screen's layout() method is invoked. If you are adding a number of fields to the screen at the same time, you can add all of the fields to a manager and then add the manager to the screen. This approach invokes only a single layout().
  • Remove groups of fields, instead of individual fields, from a screen. If you plan to remove a number of fields in your application, you can add all of the fields to a manager and then remove the manager to remove all of the fields at once. This approach invokes only a single layout().
  • Invoke replace() of the Manager class to remove a field from a manager and replace it with another field. This method invokes only a single layout() and is more efficient than removing a field from the manager and then adding a new one in its place.
  • Add fields and managers to a screen in the screen constructor. Adding and removing fields in the screen constructor does not invalidate the layout of the screen, and layout() is not invoked.
  • Create a placeholder field for content that you can populate later, and add the field to a screen in the screen constructor. Even if you don't have the content ready to add, if you know the size of the content area then you can create a placeholder field and add the content later.

Code sample: Overriding paint()

import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.container.MainScreen;

import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.component.ButtonField;

public class OverridingPaintDemo extends UiApplication
{
    public static void main(String[] args)
    {
        OverridingPaintDemo theApp = new OverridingPaintDemo();
    	   theApp.enterEventDispatcher();
    }
    
    public OverridingPaintDemo()
    {
    	   pushScreen(new OverridingPaintDemoScreen());
    }
}

class OverridingPaintDemoScreen extends MainScreen
{
	   public OverridingPaintDemoScreen()
	   {
		      setTitle("Overriding Paint Demo");
		
//In the screen constructor, create a new MyCustomButton object. 
//The MyCustomButton class extends the ButtonField class and 
//provides a custom paint() method.
		      MyCustomButton theButton = new MyCustomButton();
		
//Add the MyCustomButton object to the screen.
		      add(theButton);		
	   }
}

//Create the MyCustomButton class.
class MyCustomButton extends ButtonField
{

//Implement layout() to specify custom layout instructions for 
//MyCustomButton. In this example, layout() sets the size of 
//the field to 200 x 200 pixels.
	   protected void layout(int width, int height)
	   {
		      setExtent(200, 200);
	   }
	
//Implement paint() to specify how the field draws its 
//content area. In this example, paint() draws two colored 
//rectangles as the content of the button.
    protected void paint(Graphics g)
    {
	       g.setColor(0x00FF00);
		      g.fillRect(0, 0, getWidth(), getHeight());
		
		      g.setColor(0xEE0000);
		      g.fillRoundRect((getWidth() / 4), (getHeight() / 4),
                        (getWidth() / 2), (getHeight() / 2), 10, 10);
   	}  
}

Optimizing painting

The paint() method of a field is invoked many times during the execution of a BlackBerry device application, including when a screen scrolls, is invalidated, or when the focus area changes. When you create your own custom fields, it is important that a field's paint() method is as efficient as possible. By making paint() efficient, you can create a UI that is smooth, responsive, and does not lag when BlackBerry device users interact with it. Your fields, managers, and screens should create as few objects as possible in paint(). You should move object creation from paint() to other methods that are invoked less frequently, such as layout().

Using strings effectively

Creating and manipulating String objects in your application can be time-consuming and use a lot of memory. For example, operations such as string concatenation create new String objects and should be performed as infrequently as possible.

You should try to create and cache static String objects outside of paint(). If you know in advance that the contents of a String object won't change while your application is running, you can move the creation of the String object into another method that is invoked less frequently. A good place to cache String objects is in the constructor of your screen. The constructor is invoked only once, when the screen is created, and you can save time and memory by creating and storing String objects in this method.

Using bitmaps effectively

Bitmaps that you use in your applications consume a lot of memory. Operations that you perform on bitmaps, such as scaling, can quickly consume the memory that is available to your application and slow its performance considerably.

You should try to create and cache bitmaps outside of paint(). If your bitmaps won't change while your application is running, you can move the creation and manipulation of bitmaps to other methods. You should consider creating your bitmaps in the screen constructor, so that they are created only once.

Using fonts effectively

You can use a variety of fonts in your applications. You can choose to use a font that is provided in the BlackBerry Java SDK or you can import a custom font. You can also derive a font that has a specific style and size from an existing font on the BlackBerry device.

Typically, fonts don't change while an application is running, and so you don't need to create and derive fonts in paint(). Instead, you should override applyFont() and perform font operations in this method. The applyFont() method is invoked when a screen is created, as well as when the default system font on the device changes. You can also measure text and cache the height of the font in layout(), and then use these values in paint() to draw the contents of a field.

Caching the dimensions of a field

The dimensions of a field or manager change only at specific times while your application is running. For example, the size of a field won't change in paint(); this method only draws the contents of the field using the space that is provided by the field's manager. The size of a field might change in layout(), where field arrangement takes place. You can use this knowledge to move any calculations of field size or positioning from paint() to layout().

For example, you can invoke the static Display.getWidth() and Display.getHeight() to retrieve the width and height of the screen, and cache these values in layout(). Because these values change only if the BlackBerry device is rotated, you don't need to retrieve them each time paint() is invoked. In general, though, you should use the dimensions of your field instead of the dimensions of the screen to position and lay out your fields.