While portability is one of LWUIT's best attributes, it is also one of the hardest features to grasp. LWUIT is portable as a library and it also enables application porting in such a way that binary code or source can be compatible across different Java ME profiles.
Much has been said in the past about Java device fragmentation (write once debug everywhere). To understand LWUIT's portability you must first understand the original problems and the solutions LWUIT provides for each problem:
Low quality or buggy implementations of the specification
This problem was far more severe with older (prior to CLDC 1.1) devices that LWUIT does not support. Thanks to modern TCKs, the virtual machine (VM) in modern devices is compatible, and furthermore the UI layer on which LWUIT is based is very narrow and relatively robust across devices.
Low power, low memory devices
Again with newer CLDC 1.1 devices this is not as much of a problem as it used to be, but there are still concerns. See Chapter 2 for a discussion on increasing performance and reducing memory overhead (sometimes trading off one for the other).
Varying screen resolutions
LWUIT ships with a very fast low memory overhead scaling algorithm that doesn't lose transparency information. For extreme cases where the algorithm is not enough, LWUIT supports pluggable themes, allowing the UI to be customized with images more fitting to the resolution of the phone.
Varying input methods
LWUIT detects soft buttons automatically, and navigation is already portable. LWUIT supports touch screens seamlessly out of the box. Text input works with the device native text box, ensuring proper input.
Over-The-Air (OTA) code size limitations
This problem is solving itself, given relaxed carrier restrictions and increasing JAR file size allocations. LWUIT fully supports obfuscation and works properly with obfuscators that remove redundant code.
Non-UI related pitfalls (networking issues, RMS incompatibility, etcetera)
LWUIT currently focuses only on UI related issues, so you must find your own solution for the many minor issues related to these problems. For most common use cases failure occurs because the device expects the “right thing”. For example, networking is problematic on some devices due to a connection that was never closed, and so forth.
Performance is a huge problem in portability. Problems in performance often creep on an application only to appear later in its life cycle. Performance is often a trade-off, mostly of memory for CPU or visa versa. The easiest way to improve performance is to reduce functionality.
Since LWUIT has pluggable theming you can substitute a simple theme without changing code. This makes it easier to see whether the problem is in the UI itself.
The following subsections discuss the specifics of memory and responsiveness. One thing to keep in mind is that performance and memory use on an emulator is no indication of device performance and memory overhead.
This section discussions factors that impact memory and speed.
Memory is problematic, especially when programming small devices. When using LWUIT you must understand how memory directly relates to resolution and bit depth.
Assume you have two devices, a 16-bit color (65536 colors) device with 128x128 resolution that has 2 megabytes of memory, and a 24-bit color device (1.6 million colors) with a 320x240 resolution and 3 megabytes of memory. Which device provides more memory for a LWUIT application? The answer is not so simple.
Assume both devices have a background image set and scaled, so they need enough RAM to hold the uncompressed image in memory.
The smaller device needs 32,768 bytes just for a background buffer of the screen. The larger device requires 307,200 bytes for the same buffer!
Because screen buffers are needed both for the current form, the current transition (twice), and the MIDP implementation, the amount of memory the larger device consumes is staggering! How did we reach these numbers?
The simple formula is:
screen width * screen height * bytes per pixel = memory
Therefore:
16 bit: 128 * 128 * 2 = 32,768
24 bit: 320 * 240 * 4 = 307,200
Notice that in the 24-bit device 24 bits are counted as an integer because there is no 24-bit primitive and implementations treat 24-bit color as 32-bit color.
So getting back to the two devices: In the worst case scenario four buffers are immediately consumed, and the remaining RAM compares as follows:
16 bit: 2,097,152 – 32,768 * 4 = 1,966,125
24 bit: 3,145,728 – 307,200 * 4 = 1,916,928
It turns out the 24-bit device has more RAM to begin with but doesn't have as much RAM to work with!
Note: All of these calculations don't take into account the additional memory overhead required for LWUIT and your application. |
Thankfully, LWUIT offers a partial solution in the form of encoded images, which allow the device to cleanup unnecessary bitmap data from memory when it is scarce.
Encoded images work by using a weak/soft reference to a keep the encoded version of an image. For example, a PNG or JPEG is usually compressed at a very high ratio producing a much smaller byte size than the ones mentioned above (typically a 240x320 image can be 4-5kb or even less!). The EncodedImage keeps in memory the actual JPEG or PNG data, when image information such as pixels, dimensions etc. is needed the native Image object is dynamically created and maintained in a weak/soft reference for caching.
This allows the garbage collection to remove the image from memory when additional memory is needed, however its potentially expensive since an image might be created multiple times. It is also expensive to scale an encoded image since scaling is far more expensive for these cases.
The encoded image is the default image type returned when loading an image through a resource file.
Using encoded images, a UI-heavy application can be run on a 2 megabyte 320x240 24-bit color device. Note that using tiled images or a solid color to fill the background is much “cheaper” than the savings reachable using encoded images.
UI speed is often a user perception rather than a “real” performance issue. Slow performance happens, but a developer's opinion of performance may not match an end-user's perception. The best way to measure the speed of a UI is to give devices to a focus group of objective people and ask them how the UI “feels”.
That said, the following subsections you can monitor the event dispatch thread and LWUIT performance.
Performance often suffers because of slow paints. This often occurs when the EDT is being used without being released. It's important not to “hold” the EDT and release it immediately when performing long running tasks. For further details on releasing the EDT see Display methods callSerially
, callSeriallyAndWait
, and invokeAndBlock
.
The EDT might be blocked due to unrelated work on a different thread. Bad thread scheduling on devices causes this problem, in part because many hardware devices ignore thread priorities.
On some devices networking can cause a visible stall in the UI, a problem for which there is no “real” solution. The workaround for such cases is logical rather than technical. In this case a standard progress indicator stalls during a networking operation. It might work better to use a progress indicator heuristic that moves slower or does not move at all so the user is less likely to notice the interruption in the display.
Different transition types have different performance overheads on devices. Play with the transition selection and possibly disable transitions if necessary.
Indexed images carry a performance overhead. It shouldn't be excessive, but when using many animations or indexed images you can expect a slower repaint cycle, especially on devices without a JIT or fast CPU.
Light mode often trades speed for memory overhead. If there is plenty of memory and low performance, explicitly turning off light mode (after Display.init()
) might impact speed.
This section describes the device bugs and limitations the LWUIT development team found while in the process of creating demos and applications. While this is not an exhaustive list, you can apply these principles if you encounter device issues of your own.
The LWUIT development team encountered several device bugs and limitations (but not nearly as many as were expected). The first rule of bug investigation is:
It is not a VM bug.
Often developers blame the VM for bugs. Despite many rumors, the development team hasn't found a CLDC 1.1 VM with a significant bug (they reproduced crashes, but they were all due to bad API implementations).
The VM and GC seem to work pretty flawlessly, which means several things should work. You should be able to rely on proper exception handling and proper class loading behavior. This essentially allows you to use Java technology for exception handling and class loading to work with multiple devices, instead of the “problematic” preprocessor statements used in the past.
The preprocessor approach was essential in the past when targeting all phones (even seriously broken VMs) with code size requirements that were very low. Today's market has changed considerably, both in the quality of the common devices and in the space or OTA code size available for a typical application.
The advantages of avoiding preprocessor are mostly in code maintenance (refactoring, compiler checks, etcetera), simplicity in reusing object oriented paradigms, and easier deployment (one JAR file for all or most devices).
Rather than beat around the bush, here are a few examples of actual device behaviors:
A device throws an exception in a certain condition when using an API. This happens with several devices that fail in drawRGB
. The solution is to catch the exception and activate a flag to invoke a different code path designed for that device class only.
Some devices have a bug with API X or with a specific usage of API X. Avoid that API or usage if possible. For example, many devices have a problem with flushGraphics(int, int, int, int)
, but all devices tested worked perfectly with flushGraphics()
.
As you can see, you can rely on Java working properly and throwing exceptions, making it possible to implement workarounds on the fly.
The rules for dealing with device limitations are very similar to the rules for dealing with device bugs. If a missing API is invoked in code, it throws an exception because it doesn't exist. You can catch that exception and activate a flag disabling the functionality related to the feature. For example, your application might offer a location based feature based on JSR 179. You can wrap the calls related to JSR 179 code in try/catch and disable the functionality if a Throwable is thrown by the code (for example, NoSuchMethodError
or ClassNotFoundException
).
An example of this approach exists in the M3G class from LWUIT which is designed to run on devices that do not support JSR 184. The Log class is also designed in a similar way. It can utilize the FileConnector
when the API is available in order to log to the device file system rather than RMS.
Limitations are often related to appearance, number of colors, device speed, device resolution, and so forth. These can be worked around using a multitude of themes and picking the right default theme upon startup. Use the methods in Display to check general device capabilities, then enable or disable some features.
For example, some devices support only three alpha levels (0%, 50%, 100%). This causes anti-aliased fonts to look horrible on those devices especially when using white over black color schemes. Devices like these can be easily detected using Display.numAlphaLevels()
and such themes can be disabled on these devices (or simply excluded from the default state). Similar properties such as numColors
are available on display.
Speed and memory constraints are much harder to detect on the fly. TotalMemory
is often incorrect on devices and speed is notoriously hard to detect. True memory heap can be detected by allocating byte arrays until an OutOfMemoryError
is thrown. While the VM is not guaranteed to be stable after an OOM it generally recovers nicely. Store the amount of memory in RMS to avoid the need to repeat this exercise.
The best solution is to allow your users as much configurability as possible (to themes, animations, transitions, etcetera) thus giving them the choice to tailor the application to their device needs.
One of the biggest problems in Java ME programming is the selection of device resolutions. This problem is aggravated by lack of scaling support and the small selection of devices with SVG device. A bigger problem than multiple resolutions is the problem of varying aspect ratios, even changing in runtime on the same device! (For example some slider devices change resolution and aspect ratio on the fly.)
LWUIT solves the lack of scaling support by including a fast low overhead scaling algorithm that keeps the image's alpha channel intact. Scaling on devices is far from ideal for some image types. It is recommended that designers avoid “fine details” in images that are destined for scaling.
Since images and themes can be stored in resource bundles, such bundles can be conditionally used to support different resolutions. This solution is not practical on a grand scale with a single JAR file strategy, however, for some basic resolution and important images this is a very practical solution, especially when dynamically downloading resources from a server.
This section describes input methods that LWUIT supports.
Soft buttons for common devices in the market are detected automatically by LWUIT. If LWUIT fails to detect a specific device a developer can still set the key code for the soft keys using setter methods in Display.
LWUIT supports 3 SoftButton navigation common in newer phones from Sony Ericsson and Nokia. The 3 SoftButton mode can be activated via the Display class. In this mode the center “fire” key acts as a soft button.
Some devices, most commonly older Sony Ericsson devices, have a special hardcoded back button device. You can assign a command as a “back command” using the form method for determining the back command. This ensures that only one command at any given time is deemed as a back command. The back command can also be configured using the Display methods. Currently the back button is only supported on Sony Ericsson devices.
Touch screens are supported out of the box, however, designing a UI for finger operation is very different from designing a UI for general use. Finger operations expect everything to be accessible via taps (not keys).
A touch interface expects widgets to be big enough to fit the size of a human finger. This is somewhat counter-intuitive because normally you might want to cram as much UI detail as possible into a single screen to avoid scrolling.
Component sizes can be easily customized globally using the theme. Simply set the default padding attribute to a large enough value (e.g. 5, 5, 5, 5) and all widgets “grow” to suit finger navigation. It is also a good practice to use buttons for touch devices and avoid menus where possible.
The only problem is that currently there is no standard code that allows you to detect touch screen devices on the fly. However such information can be easily placed in the Java application descriptor (JAD) file for the application to query.
This list is rather limited since the development team doesn't have much to say about most devices. Most of the common CLDC 1.1 devices just work out of the box without much of a hassle. This section describes behaviors that might catch developers off guard. This is by no means an exhaustive or definitive list.
The RAZR family doesn't support different levels of translucency -only 50% translucency is supported. This causes anti-aliased fonts to look bad on these devices.
.cod
FileCreate a new project in JDE and name it appropriately. Select project type: "Empty Midlet project".
Right click on the project and choose the "add file to project" option and choose the JAR file from your projects /dist directory.
Right click on the project and choose "properties".
In the "Application" tab insert the name of your main MIDlet class.
Build and run the project.
Generally series 40 devices work well. Some “high end” S40 devices only contain 2mb of memory yet have 24-bit color 320x240 resolutions. These devices have 3mb installed but only 2mb is accessible to Java applications.
The Nokia S40 emulator provides a very good approximation of the devices.
Sony Ericsson makes good Java devices that are indexed with memory and have 16-bit color for even better memory.
The Back button, as discussed in Back Button exists in SE until JP-8, at which point a new interface based on three soft keys was introduced.
Native Networking Sony Ericsson threads in SE tend to freeze the GUI. The devices in JP-7 and newer completely ignore thread priorities as well.
Test on manufacturers emulators. While development is easier using the Java ME SDK, the Sprint Plugin for Java ME SDK, or the Sprint Wireless Toolkit, there is no substitute for occasional emulator testing. An emulator provides more accurate memory readings especially related to images and buffers.