Despite everyone’s efforts to the contrary, all of us tend to make the same mistakes, sometimes over and over again. It’s instructive to occasionally stop and look at the mistakes that our peers or we make, because doing so brings them into clearer focus so that perhaps we’ll be less likely to repeat them in the future.
In this article, I summarize the top ten mistakes I’ve observed both new and practiced developers make when writing applications for the QUALCOMM BREW platform in C or C++. This is by no means an exhaustive list, and I suspect that some readers will likely have favorites that may not be on this list. However, I think it’s representative of the common traps and pitfalls most BREW developers first stumble across and then learn to avoid.
10. Failing to Check Return Values
Nearly all BREW interface methods return values indicating the success or failure of the method call. Most have very fine-grained error reporting, using a bevy of values defined in aeeerror.h to indicate the source of a problem (whether developer misuse, insufficient resources, or another issue). However, many developers fail to check these return values, making it difficult or impossible to determine the source of errors (and often subsequent crashes). This is particularly a problem in areas that aren’t wholly under programmatic control, such as when interacting with the file system (which can be full) or the network (that can disappear unexpectedly at any time). Always test return codes, and always define some sort of error handling. Even a single screen that reports that an unexpected error has occurred and terminates the offending application is better than a random crash.
9. Setting Very Short Timers, or Just Using ISHELL_SetTimer Instead of ISHELL_Resume to Defer an Operation
BREW provides two ways to let you schedule the deferred execution of a function: ISHELL_SetTimer and ISHELL_Resume. You should only use ISHELL_SetTimer when you have some interest in trying to ensure that the function you want to execute should have its execution delayed by some clearly defined interval. Although ISHELL_SetTimer doesn’t guarantee that the interval you specify will be honored, it will do its best to do so, and always ensures that it won’t invoke your function in less time than you specify. ISHELL_Resume, on the other hand, invokes your function later—typically just before or after your application receives its next event. Because ISHELL_Resume requires you to have an AEECallback stored on the heap (not on the stack, which will cause an immediate crash!), many developers prefer instead to use a very short-duration timer to defer execution of a function, which is both inefficient and crash prone. It’s inefficient because the timer mechanism typically defers execution for tens or hundreds of milliseconds after registering a new timer, and crash prone because some handsets simply will crash if you attempt to schedule timers with intervals below some well-observed threshold (typically around 50 ms).
8. Being a Resource Hog
Although BREW-enabled handsets have matured significantly in the last eighteen months (megabytes of heap and multi-hundred megahertz processors are fast becoming the norm), they’re not nearly as powerful as desktop computing platforms. Take great care to allocate only the memory or interfaces you need, and ensure you release all resources in a timely manner (including at application suspend and exit). This is especially important for memory access when you may have lots of small memory regions, because heap fragmentation can still be a problem, especially on older devices.
7. Freeing Already Freed Pointers or Releasing Already Released Interfaces
This is a major issue on any platform without garbage collection, and QUALCOMM BREW is no exception. You should take great pains to never free a pointer (or release an interface) twice—I like the habit of nulling pointers and interfaces after they’re freed, and checking to see if they’re null before freeing (or releasing) them. Closely related to this is abusing—or misunderstanding—BREW’s reference counting mechanism for interfaces. Put simply, if you’re using an interface that someone else gave you, you should always AddRef it to ensure the entity that provided it doesn’t release it. When you’re done, you should release it. Of course, if you’re passing an interface to another method—say, providing a stream to IIMAGE_SetStream—you should be sure to release the stream, because you’re done with it at that point and it’s now the IImage’s responsibility to release the object when it’s finished. You can almost always recognize code written by folks who don’t understand the BREW reference counting mechanism (or by those who have not considered reference counting in their design) by seeing bits of code like this throughout their application:
while( IBASE_Release( (IBase *)pInterface ) ;
6. Failing to Update the Screen (or Updating the Screen Too Much) Using IDISPLAY_Update
This one seems to catch developers new to BREW more than experienced developers. In the first scenario, your application mysteriously fails to draw things on the screen, even though all the requisite code is in place. After arduously checking the coordinates of each draw operation in calls to the various IDisplay, IGraphics, and IImage interfaces, you decide to throw in a call to IDISPLAY_Update, and suddenly everything works! Once you make this discovery, it becomes very tempting to simply call IDISPLAY_Update (or IDISPLAY_UpdateEx, passing TRUE to force an immediate redraw) any time you feel the screen should be redrawn—which will slow your application considerably. The rule for IDISPLAY_Update is simple: invoke it once when you’re finished with all drawing operations. This schedules a screen repaint with the underlying runtime at some (hopefully soon) time in the future. If you’re really concerned that the redraw must happen immediately, you can force an immediate redraw by using IDISPLAY_UpdateEx, passing TRUE as the second parameter to force an immediate redraw. However, bear in mind that abusing this won’t make your application faster, but will actually slow your application down because the system will spend more time repainting the screen than running your application.
5. Doing Too Much Computation Within a Function
With today’s faster processors and BREW 3.1, this is less of a problem, but I remain impressed by the number of engineers I’ve met who neglect to remember that QUALCOMM BREW is a cooperative multitasking environment running on a relatively lightweight computing platform. Expensive computations of any sort are best broken up into small pieces and run in an asynchronous manner using ISHELL_Resume or through the application’s event handler. If you’re used to writing multithreaded applications on other platforms, this can be somewhat of a chore, but if you’re an old hand from the early days of the Macintosh, Newton, or Palm Powered platforms, this is often easier than relying on multithreading. Regardless of today’s hardware capabilities, if you aren’t careful, you can easily construct functions that block the processor for too long. To prevent a handset from locking up permanently—an important feature for a device with no reset button that may be needed at any moment in an emergency—the underlying operating system has a watchdog timer. If your application blocks the processor for too long, the watchdog timer will fire and reboot the handset, terminating your application. This looks suspiciously like any other crash, and can be very challenging to find, especially if it only occurs in some circumstances (say when sorting an extremely large data set).
4. Confusing FREE() and Release() Calls on Memory and Objects
This one amazes me—not because it happens, but because it still happens, to experienced BREW developers. (In fact, I caught myself making exactly this mistake recently, after I’d already written a preliminary draft of this article!) The problem is simple and about as subtle as a bull in a china shop: if you attempt to invoke FREE() on an interface, at best you’ll leak the underlying resources used by the interface, and you’ll frequently corrupt the heap as well, causing a random crash a little while after your erroneous FREE(). In a similar vein, by invoking a Release() method on a heap region, you will witness an immediate crash as BREW attempts to dereference into the (nonexistent) virtual table of the heap region looking for the Release() method. Unfortunately, the heavy dependence on void pointers when writing BREW applications in C makes this mistake very easy, because many BREW applications frequently sidestep strong type checking by the compiler. Worse, many organizations wrap the BREW memory and interface allocation and deallocation interfaces with procedures to permit better tracking of memory leaks and the like, making it harder for developers to spot the rather obvious incongruity of seeing ISHELL_CreateInstance() at the beginning of a function, and FREE() at the end of the function on the freshly created object.
3. Insufficient Testing of Poor Network Coverage for Networked Applications
It’s very difficult to accurately simulate all that can go wrong on a wireless network using the BREW Simulator. Simply pulling the network cable (or turning off the office WiFi access point) won’t do it because the cellular network is a significantly more complex beast than today’s Ethernet networks. Worse, some kinds of network activity, such as voice calls or SMS, can’t be fully simulated at all on your development workstation. Fortunately, there’s an easy way to test marginal or no network coverage: buy a metal cookie tin or empty paint can. When sealed—so there are no cracks between tin and lid—either makes an excellent Faraday cage, which blocks all radio energy to anything inside the cage. By putting the handset under test in the container and closing the lid, you can simulate an out-of-network condition without needing to visit your local fallout shelter or cave. Where I work, nearly everyone has a cookie tin left over from a previous holiday binge (or a paint can from the local hardware store), and simulating poor network coverage is a standard procedure for both software developers and software testers.
2. Insufficient Testing of Suspend and Resume Cases
QUALCOMM BREW is a cooperative platform: You run in the same task as other BREW applications, and must share memory and CPU resources accordingly. One of the ways this is enforced is through the suspend/resume contract you must uphold with the system, in which your application should release as many resources as possible when it receives EVT_SUSPEND, and restore those resources when it receives EVT_RESUME. Unfortunately, although an increasing number of developers are getting very good at implementing suspend and resume cases, returning as much as of the handset’s resources back to the system as possible, it’s an area that’s seldom well-tested until you submit your application to NSTL for True BREW Certification. To avoid this, ensure that you include suspend/resume testing in your test plan early, and test these cases often throughout the development life cycle. Fortunately, this testing is simple—simply send the handset under test an SMS message or phone call while your application is running. Of course, it’s best to do this several times, with your application in different states. As you do so, ensure that the application resumes correctly, restoring the display, audio, and any entered information as if the application had never been suspended.
1. Insufficient Testing on the Target Handsets
Probably the most common and the most dangerous mistake you can make when developing for QUALCOMM BREW is to rely too heavily on the BREW Simulator for application testing and performance profiling. Although the BREW Simulator is an excellent tool, it simply can’t compare to on-device testing, because it differs from the handset in many ways, including:
- Processor architecture. BREW-enabled handsets are based on integrated circuits utilizing an ARM core, not the Intel Pentium architecture used by the development platform. The simulator executes your application as a Windows DLL compiled for the Pentium processor, not as an emulator of the ARM processor.
- Media codec support. The BREW Simulator appears to leverage Microsoft Windows Media codecs for many media types, meaning that what works in the simulator when it comes to multimedia may not work on a handset. Moreover, some handsets have defects in their multimedia implementation through the IMedia interface and its subclasses, meaning that you can’t simply test in simulation and be sure that it works.
- TCP/IP support. The BREW Simulator has a number of features that let you simulate things such as network performance as though you were running over the cellular network. Unfortunately, the BREW Simulator uses the same TCP/IP implementation as the rest of Microsoft Windows, not the TCP/IP stack or physical transport used by a handset, and behavior between the two can vary greatly.
If you’ve made one or more of these mistakes, join the club—you’re not alone! Hopefully, reviewing this list of mistakes will help you avoid them, and perhaps remind you of a few of your own that you may make from time to time.
About the Author
Ray Rischpater is the chief architect at Rocket Mobile, Inc., specializing in the design and development of messaging and information access applications for today’s wireless devices. He is the author of several books on software development, including eBay Application Development and Software Development for the QUALCOMM BREW Platform, both available from Apress, and is an active Amateur Radio operator. Contact Ray at firstname.lastname@example.org.