MicroEJ GUI Programming Practices

Description

This document gives a collection of good practices for Application developers who want to implement efficient GUIs with MicroEJ or to improve their current application’s performance.

Prerequisites

This document assumes minimal knowledge about GUI Application development with MICROEJ VEE (MicroUI and MWT libraries).

Application Code Quality

MicroEJ Java Programming Practices provides generic Java coding rules designed for application footprint and performance.

They especially apply to GUI code, which can have a significant impact on performance.

Also, this documentation does not cover the MicroUI/MicroVG ports: it assumes the VEE Port has already been qualified for performance. For more details, refer to VEE Port Programming Practices.

UI Design

Widget Hierarchy

The widget hierarchy should be kept as simple as possible. Deep hierarchies (many nested containers) and a large number of widgets increase:

  • RAM usage.

  • Layout time (positions and sizes computing).

  • Style computing.

  • Rendering time.

Note

The Widget Library provides the Hierarchy Inspector utility, which allows printing the widget hierarchy. This utility is useful to monitor the number of widgets and the depth of the widget hierarchy.

Hardware Considerations

Consider the hardware constraints when implementing the GUI:

  • CPU frequency.

  • GPU (2D acceleration, Vector Graphics), and other hardware accelerators (DMA-2D, …).

  • Flash and RAM(s) speed and where the different components (application code, static assets, MicroUI images heap, framebuffers, …) are linked to.

The GUI optimization strategies should be defined according to those hardware constraints, for example:

  • Raster drawings if the MCU has a high frequency or if the hardware has good 2D acceleration.

  • Vector drawings if they are supported by the hardware.

  • UI component based on static bitmaps if the hardware doesn’t allow efficient drawings.

  • Cache drawings if the images heap’s RAM is fast.

  • Use static bitmaps if plenty of ROM space and flash-to-RAM (framebuffer) transfer speed is fast.

UI Libraries Usage

Layout and Render Requests

It is important to use the requestLayOut() and requestRender() methods properly.

  • requestLayOut(): this method triggers the layout of a widget. It should only be called if the position or size of a widget has changed. requestLayOut() automatically triggers a render request, thus, requestRender() must not be called after a requestLayOut().

  • requestRender(): this method triggers the render of a widget. it should be called only when a widget’s UI content has changed.

Calling a container’s requestLayOut() or requestRender() requests the layout or render of all its children. Only the part of the widget hierarchy that has changed should be laid out or rendered. And requestRender() must be preferred when no layout is needed.

If a widget has transparency, requesting its render also requests its parent render in the zone of the widget. If the Desktop uses an OverlapRenderPolicy, the render request also requests the overlapping widgets to render in this zone.

Note

The Flush Visualizer utility tool can help identify consecutive renders with no visual change.

Perform the Minimum Number of Operations

When creating a widget, abstract methods must be implemented (computeContentOptimalSize(), renderContent(), etc.). It is important to perform the minimum number of operations in those methods.

renderContent() is the most critical method where too many operations are often done:

  • No formatting, size, or position computing should be done in renderContent(), those operations should be done in onLaidOut() instead.

  • Resource loading from computeOptimalSize() or renderContent() should be avoided:

    • Fonts can be loaded in the stylesheet and retrieved with the Style.getFont() method.

    • Images can be loaded from the widget’s constructor or onAttached() methods.

Memory Configuration

The VEE memories (Managed Heap, Immortals Heap, Threads Pool size) must be calibrated properly.

Special attention should be paid to Managed Heap calibration. The size of the Managed Heap directly impacts the Garbage Collection time, thus, it should be kept as low as possible (with a 10 to 15% margin on the minimum required).

See the Heap Usage Monitoring documentation for more information on how to calibrate the Managed Heap.

Close Resources

Closeable resources (ResourceImage, ResourceVectorImage, VectorFont) must be closed when not used anymore to prevent memory leaks. Widgets’ onAttached() / onDetached() methods are the right place to open/close those resources.

Backgrounds

Backgrounds drawings can take time, a common pitfall is to set a background for all widgets. This will lead all backgrounds to be drawn stacked.

A good practice is to set the stylesheet’s default background to NoBackground, all the widgets will have no background by default. Then, apply a background only to the root widget and to the other widgets that need a different one.

Note

Use the method HierarchyInspector.printHierarchyStyle(getDesktop().getWidget()) to spot all non-NoBackground widgets.

String drawings

If a UI component is slow to render and contains a lot of strings, reducing their drawing time can be done either by:

  • Using renderable strings, this reduces the drawing time of the string at the cost of consuming more heap memory.

  • Using vector fonts can benefit from GPU hardware acceleration (can be more efficient than EJF bitmaps for large font sizes).

Do Not Run non-UI Code in the MicroUI Thread

All UI-related code is run in the MicroUI thread (event handling, drawings). In order to prevent freezes or slowness in the UI rendering, no long-running operation should be executed in the MicroUI thread. The Executor utility class can be used to run the long-running operation in another thread.

Critical UI Components

Some widgets and UI components require special attention to achieve good performance, it is typically widgets that are animated and/or that are drawn on a large portion of the screen.

Scrollable lists and page transitions are examples of UI components that are usually critical.

MicroEJ’s Github provides examples of scrollable lists and transitions, it is a good practice to use those implementations in your application:

  • Scrollable lists:

    • The Example-Java-Widget’s scroll is the default scroll implementation. It manages most scrollable lists use cases (scroll bar, transparency, widgets overlapping the scroll list). This implementation is the right one if the GPU efficiently accelerates its drawings.

    • The Example-Java-Widget’s buffered scroll is an optimized scroll implementation that recopies the content of the display buffer at its new coordinates instead of actually drawing the scroll’s content. This implementation is usually the fastest one but does not manage transparency or widgets overlapping the scroll.

    • If a scrollable list needs to manage transparency or overlapping widgets and needs better performance than the default scroll implementation, the content of the list can be cached into BufferedImage or BufferedVectorImage.

  • Transition containers:

    • ExampleJava-MWT provides transition containers implementations with several transition animations.

Animated Widgets Good Practices

Animations should implement the Animation or MotionAnimation class which use an Animator to drive the animation.

The Animator uses a callback on the MicroUI flush, making sure that the previous frame has been rendered before triggering another one.

A single Animator instance must be used for all the animations. The desktop’s getAnimator provides a single Animator instance.

In some specific cases, for instance when an animation must have a fixed framerate, a Timer can be used to drive the animation. A special attention must be paid to the value of the framerate: if the period of the timer task is lower than the time to draw the frame, this will result in an inconsistant animation rendering.

As animations are generally CPU-intensive, special attention must be paid to their optimization:

  • Only call requestRender() on the animated widget, not on a parent container, to limit the repainted area.

  • Stop the animation in the widget’s onDetached() method so it does not keep requesting renders when the widget is off-screen.

  • Limit the number of simultaneously running animations.

Swipe Handling

For handling swipe touch events (press, drag, release), the SwipeEventHandler should be used, this class helps handle the swipe events properly.