Fixing Android UI stutter on NXP i.MX8M

A display with a half-open notification shade above a row of even frame-time bars

Two of the most common gestures on an Android device stuttered on an NXP i.MX8M Plus panel running official NXP Android 14: scrolling a list to its end and pulling down the notification shade. During our analysis, traces showed the same cause in both cases: Android created an offscreen buffer in exactly the frame that had the least time to spare. We were able to fix both in a way that janky frames while overscrolling a list dropped by 58%, and the worst frame at the start of a shade pull-down became up to 65% shorter.

This post continues our performance series. On the same board, we previously reduced scroll jank by 95% with precompiled system apps, where the bottleneck was the CPU. This time, the time went into waiting for the GPU.

On the i.MX8M Plus, every extra layer costs GPU time

The panel in this setup runs at 1280×800 and 61.5 Hz, which leaves 16.25 ms per frame. The display controller of the i.MX8M Plus drives a single plane. It has no hardware compositor that blends layers on the way to the panel, so every blend and every offscreen buffer is paid for with GPU fill rate and a round trip the render thread has to wait for.

That is why two features that cost almost nothing on a phone chip turned into multi-frame stalls by assuming that composition is cheap.

Stretch overscroll rendered the list into a layer on most frames

Since Android 12, the default overscroll effect is the stretch. When a scrollable view reaches its end, Android draws the content into an offscreen layer and distorts that layer with a shader. The older glow effect draws a shaded arc on top of the content and needs no layer at all.

With a list held against its end, our Perfetto trace showed the stretch layer on 216 of 254 frames. The render thread spent an average of 22 ms per frame flushing layers, while the GPU load stayed at 3 to 5%. The render thread was blocked 84% of that time. The shader itself was cheap; the main cost was a synchronization round trip:

# RenderThread, list scrolled to its end, 254 frames
drawLayer [AndroidEdgeEffectOverscrollEffect] 796x800   216 frames
flush layers                                    avg     22.07 ms
GPU load during flush layers                            3-5 %

# same gesture, glow effect
drawLayer [AndroidEdgeEffectOverscrollEffect]           0 frames
flush layers                                            0

We made the effect selectable with a read-only build property. When it is set, the platform uses the glow effect. Android reads the property once, when the class loads, so running and newly started apps always agree on the effect. Builds that don't set it behave exactly as before.

The notification shade got its surface in the first frame of the drag

The notification shade lives in its own window. While the shade is collapsed, that window is invisible and has no surface. Android only makes it visible once the drag has started, so creating the surface, allocating a full-screen translucent buffer and measuring the whole shade layout all landed in the first frame of the opening animation.

We measured that frame at 110 to 129 ms against the 16.25 ms budget, about 21 ms of it for buffer allocation alone. Every pull-down created a new layer. That single frame was the hitch we saw when the shade starts to move.

Prewarming the shade on touch moves the work before the animation

The status bar reports a touch that may become a drag as soon as the finger goes down, and Android also delivers the matching stop event for a plain tap. We keep the shade window open between those two events. The expensive setup now happens while the finger is still resting on the status bar, before the shade moves.

The window is held open with Android's existing force-open mechanism. It also limits the window's touchable region to the status bar strip, 1280×24 pixels, so the invisible full-screen window never takes touches away from the running app. A two-second timeout ends the prewarm if no stop event arrives.

Results on our build of NXP's Android 14

What we measured Before After
List held at its end: janky frames / median frame 84.1% / 77-81 ms 34.9% / 48 ms
Second list, shorter overscroll: janky frames / median frame 34.3% / 48 ms 4.9% / 31-36 ms
Worst frame at the start of a shade pull-down 110-129 ms about 45 ms
Buffer allocation during the shade drag 26-34 ms none

We measured it with Perfetto on an NXP i.MX8M Plus board with our Android 14 build:

  • Overscroll: three runs per state, with the effect switched at runtime so both states share one build.

  • Notification shade: five gestures per state, with the finger resting briefly before the drag.

The overscroll fix helps while a list is held at its end, which happens all the time on kiosk screens with short lists that users push against. Normal scrolling in the middle of a list is unchanged. The shade prewarm pays off when the finger rests on the status bar for a moment before dragging, as it naturally does. A very fast flick leaves less time to prepare.

New build options in our NXP Android 14 builds

Neither change needs a new GPU driver, a kernel update or a newer Android release. The overscroll fix was converted into a build property, the shade prewarm into a resource overlay, and both keep Android's default behavior unless a product turns them on. The prewarm is opt-in because a tap on the status bar that never becomes a drag costs one unnecessary surface creation. We added both options to our customer build pipeline for NXP products.

This way we could resolve both problems without needing faster hardware. One dropped a layer that only served a visual effect, the other moved the shade setup a moment earlier. On hardware with a single display plane, the useful question about a janky frame is not only how much work it contains, but whether that work had to happen in that frame.

If you build Android devices on NXP hardware and see stutter in everyday gestures, talk to us. We find where the frame time goes and build and tune the Android OS for your device.

Build unique products, boost device performance

See why Emteria is the chosen Android™ customization & management platform for OEM solution builders, and what it can do for your team and customers.

Book live demo
Android-head-use-cases
Back to Blog

Table of contents