Different people have different understandings of smoothness (jank/dropped frames) and different perceptions of jitter thresholds. Therefore, before starting this series, it is necessary to clarify the content to avoid misunderstandings. Here are some basic explanations:
- For mobile users, jank encompasses many scenarios: dropped frames when scrolling lists, excessive white screen during app startup, slow screen wake-up when pressing the power button, unresponsive interface followed by a crash, no response when clicking an icon, incoherent window animations, lagging touch response, or stuttering when entering the desktop after a reboot. These scenarios differ slightly from what developers understand as “jank.” Developers categorize these more finely, which is a cognitive gap between developers and users that must be noted when handling feedback from users or testers.
- Developers can initially classify feedback as Smoothness (uneven visual progress), Responsiveness (long waits after an action), or Stability (crashes and ANRs). These can overlap. An icon that appears unresponsive is not necessarily a stability failure; input, application state, and logs determine the diagnosis.
- These are not one Message-timeout mechanism with different thresholds. Long main-thread tasks can delay both input and rendering, but GPU work, SurfaceFlinger, the display stack, scheduling, or remote services can also cause delays. ANRs depend on specific input or component timeout rules; crashes have separate causes such as exceptions or signals. A long Message alone establishes neither.
- This series primarily analyzes smoothness-related issues. Responsiveness and stability will be covered in dedicated articles. Understanding smoothness first will make analyzing responsiveness and stability much easier.
- This series focuses on using Systrace (Perfetto) for analysis. Systrace is our entry point because many factors affect smoothness—some within the app itself and others within the system. Systrace (Perfetto) provides a holistic view of the system’s operation during the problem, helping us initially pinpoint the root cause.
Version note (reviewed 2026-10-14): This FAQ accompanies the 2021 MIUI case and retains the original images. Color rules below are verified in the attachments’ embedded legacy viewer, not a universal Systrace/Perfetto contract. HWUI synchronization and statistics are checked against AOSP android-11.0.0_r1; the screenshots’ full firmware/viewer versions were not recorded. New captures on Android 12 (API 31) or later can use FrameTimeline and supported benchmark metrics. Legacy dot colors are not modern presentation results.
Table of Contents
- What Do the Frame Colors in Systrace Mean?
- No Red Frame Means No Dropped Frame?
- Why Does the Main Thread Wait for the Render Thread?
- Why Doesn’t It Jank When Swiping Without Releasing?
- If It Doesn’t Jank, How Do We Measure Performance?
- Why Can’t I See Jank in Screen Recordings?
- Series Articles
- Attachments
- About Me && Blog
Systrace Series Articles:
- Introduction to Systrace
- Systrace Basics - Prerequisites for Systrace
- Systrace Basics - Why 60 fps?
- Android Systrace Basics - SystemServer Explained
- Systrace Basics - SurfaceFlinger Explained
- Systrace Basics - Input Explained
- Systrace Basics - Vsync Explained
- Systrace Basics - Vsync-App: Detailed Explanation of Choreographer-Based Rendering Mechanism
- Systrace Basics - MainThread and RenderThread Explained
- Systrace Basics - Binder and Lock Contention Explained
- Systrace Basics - Triple Buffer Explained
- Systrace Basics - CPU Info Explained
- Systrace Smoothness in Action 1: Understanding Jank Principles
- Systrace Smoothness in Action 2: Case Analysis - MIUI Launcher Scroll Jank Analysis
- Systrace Smoothness in Action 3: FAQs During Jank Analysis
- Systrace Responsiveness in Action 1: Understanding Responsiveness Principles
- Systrace Responsiveness in Action 2: Responsiveness Analysis - Using App Startup as an Example
- Systrace Responsiveness in Action 3: Extended Knowledge on Responsiveness
- Systrace Thread CPU State Analysis Tips - Runnable
- Systrace Thread CPU State Analysis Tips - Running
- Systrace Thread CPU State Analysis Tips - Sleep and Uninterruptible Sleep
If you are not familiar with the basic use of Systrace (Perfetto), please catch up on the Systrace Basics Series first. This article assumes you are already familiar with using Systrace (Perfetto).
What Do the Frame Colors in Systrace Mean?
These are F dots in the legacy Frames track, not function-slice colors. Selecting one highlights the UI/RenderThread intervals associated by the viewer, subject to its implementation and available trace events.
The original explanation in terms of one/two periods of the current display is inaccurate. The embedded AndroidAuditor in xiaomi_launcher.html uses a fixed 16.67 ms threshold (EXPECTED_FRAME_TIME_MS), applying these rules in order:
| Dot class | Condition in the embedded viewer |
|---|---|
| Red (TERRIBLE) | frame.totalDuration > 33.34 ms |
| Yellow (BAD) | Not red, and either totalDuration > 16.67 ms or an estimated deadline exists and frame.end is later |
| Green (GOOD) | Neither condition above |
totalDuration sums viewer-constructed thread intervals; it is neither a single doFrame’s duration nor Running CPU time. The RenderThread endpoint can be the end of queueBuffer/buffer swapping, not GPU completion or presentation. The viewer estimates deadlines from Vsync information rather than measuring physical display timing. On a 90 Hz display, its red threshold therefore cannot simply be read as about 22.22 ms.
Green Frame
Green means neither BAD nor TERRIBLE was triggered by this viewer. It guarantees neither completion within one actual Vsync period nor smooth on-screen output; read the original image annotation with that correction.

Yellow Frame
Yellow can result from a duration above the fixed 16.67 ms threshold or from missing the estimated deadline. It does not imply a duration between one and two periods of this device’s refresh cycle; investigate the underlying events.

Red Frame
In this embedded viewer, red means a constructed totalDuration greater than 33.34 ms. This flags a long app-side work/wait interval, still requiring composition and presentation correlation. No red dots does not imply that 90/120 Hz deadlines were met.

No Red Frame Means No Dropped Frame?
No. Correlate app submission, GPU completion, SurfaceFlinger selection/composition, and presentation rather than replacing app analysis with SurfaceFlinger alone. Either activity or inactivity in SurfaceFlinger is insufficient to classify a target window. See Triple Buffer and, for Android 12+ captures containing the data, FrameTimeline. Separate app completion deadlines from presentation.
Yellow Frame Without Jank
Previously submitted content that is ready in time may sustain display updates during a long app interval. That depends on queue and presentation timing, not an automatic triple-buffer guarantee. Extra queueing can increase input latency, and buffer count is not determined by refresh rate alone. “Without jank” requires presentation evidence, not a yellow dot or continuous SurfaceFlinger activity.
Yellow Frame With Jank
The image marks two consecutive yellow dots. Its 28.432 ms ruler spans the selection, not 28.432 ms for each frame. The original “first janked, second did not” classification is not established by this image or a complete per-frame presentation mapping. The attachment does show a long first RenderThread interval and a later main-thread wait, with dependencies between them. See the next section and Part 2’s event checks.

Why Does the Main Thread Wait for the Render Thread?
The UI thread hands frame state to RenderThread, whose drawing tasks execute serially. If earlier work is unfinished, the next synchronization task can queue and keep the UI thread waiting. Sleep indicates a wait for some event or condition; identify the dependency using call intervals, wakeups, and source, not state alone.
In AOSP android-11.0.0_r1 DrawFrameTask, postAndWait() posts work and waits on a condition variable. After syncFrameState(), run() unblocks UI early only if canUnblockUiThread is true; otherwise it unblocks after the subsequent draw/wait branch. Synchronization ending therefore does not always mean immediate unblocking, and unblocking is not GPU or display completion.
The attachment contains these intervals, in trace seconds:
| Event | Start/end | Wall time |
|---|---|---|
| Previous RT DrawFrame | 28306.209230 → 28306.225749 | 16.519 ms |
| Next UI doFrame | 28306.217610 → 28306.229642 | 12.032 ms |
| Next RT DrawFrame | 28306.225780 → 28306.234950 | 9.170 ms |
| Next RT syncFrameState | 28306.226040 → 28306.229560 | 3.520 ms |
This is consistent with earlier RT work delaying the next synchronization and UI completing afterward. Without complete waiting stacks, do not label all 12.032 ms as waiting or infer every wakeup dependency from proximity alone. Background: MainThread and RenderThread.

Why Doesn’t It Jank When Swiping Without Releasing?
On this version, I found scrolling smoother while holding my finger down, with more noticeable stutter after release. That is an observation from the time, not a rule that holding prevents jank. Input-related policies, velocity, and rendering work can differ between phases.
The selected CPU 4 policy in the image has upper/lower limits around 2,419,200 / 1,056,000 kHz. This alone proves neither continuous operation of every boost mechanism nor exclusive RenderThread execution on CPUs 4–6, and says nothing conclusive about presentation.
Rechecking PID 3553/TID 3690 in xiaomi_launcher_scroll_all_the_time.html yields 110 complete DrawFrame slices, 19 of which include Running intervals on CPUs 0–2. The attachment includes gesture boundaries, so its filename does not establish that every frame belongs to the holding phase. It does refute an all-file claim that RT never uses the presumed small cores. First identify input phases, then compare topology, workload, deadlines, and presentation.

The 120 ms boost timeout I noted at the time lacks supporting PerfLock requests, configuration, or expiry events. It cannot be derived from this frequency-limit image and is not an Android-wide constant. Treat it as an unverified historical parameter. Repeated input may renew requests, but confirming that requires vendor implementation/event evidence.
If It Doesn’t Jank, How Do We Measure Performance?
If a scenario feels smooth on both devices, how do we compare performance?
Start with dumpsys gfxinfo for the target process’s HWUI statistics. It is not a complete score for all display layers, custom game renderers, or perceived smoothness. Fields and definitions vary by OS version and rendering path.
- Fix the package, display mode, gesture distance/speed, content, warmup, and thermal conditions. Warm up first, then reset once immediately before measurement; repeating reset two or three times is unnecessary.
- Using the original Mi Launcher package as an example, reset its statistics (replace the package for another app):
1 | adb shell dumpsys gfxinfo com.miui.home reset |
- Perform the defined interaction, then capture promptly to avoid later animations contaminating the sample:
1 | adb shell dumpsys gfxinfo com.miui.home framestats |
- Check process/window identity,
Stats since, total frames, reset behavior, and coverage. Missing output is not zero. AOSP android-11.0.0_r1 ThreadedRenderer recognizes reset/framestats, but vendor behavior needs device verification. These commands were not run on a device in this review.
Interpret the fields as follows:
- Janky frames: HWUI’s count/rate under that implementation, not a count of perceived stutters. AOSP 11 compares an adjusted duration with the frame interval, with flag/dequeue handling.
Frame deadline missedis a separate classification count. - 95th percentile: An estimate of the P95 frame duration, not “95% smooth.” This implementation derives it from histogram buckets, so consider resolution and sample size.
- HISTOGRAM: Duration buckets and counts;
5ms=11means eleven frames in that bucket, not an ordered per-frame sequence. GPU HISTOGRAM is separate. - PROFILEDATA: Stage data for retained recent frames, not necessarily every frame since reset. Read column names, units, and Flags instead of hardcoding column numbers; distinguish timestamps from durations.
See AOSP 11 JankTracker and ProfileData. For repeatable modern testing, Macrobenchmark’s FrameTimingMetric includes frameOverrunMs on Android 12 (API 31) and later. Also consider tail durations, presentation cadence, input latency, and power.
Here are the two screenshots I used for comparison at the time. “90 fps” is the target, not a verified per-frame result. The text inconsistently named Reno 5 and Reno 5 Pro; the screenshot identifies only com.oppo.launcher, PID 3434, not an exact model/SoC. It therefore cannot support a chipset ranking.
Mi 10 Pro - 90 fps

Oppo Reno 5 - 90 fps

Transcribing the screenshot values under their original definitions:
| Metric | Mi screenshot | OPPO screenshot |
|---|---|---|
| Total frames rendered | 76 | 90 |
| Janky frames | 27 (35.53%) | 1 (1.11%) |
| P50 / P90 / P95 / P99 | 8 / 16 / 18 / 22 ms | 5 / 5 / 5 / 13 ms |
| Number Frame deadline missed | 7 | 1 |
| Number High input latency | 69 | 44 |
| GPU P50 / P90 / P95 / P99 | 2 / 3 / 3 / 8 ms | 4 / 5 / 5 / 5 ms |
The Mi sample has a higher HWUI Janky rate and longer tail durations. Frame counts, implementations, content/wallpaper, interaction duration, and thermal conditions were not fully matched, and repeated trials are missing. These samples do not establish an overall device ranking. High input latency is a count, not 69/44 milliseconds; classification counts should not simply be summed into a total jank count.
The GPU comparison is mixed: Mi GPU P95 is lower (3 versus 5 ms), but P99 is higher (8 versus 5 ms). Percentiles are not paired measurements from the same frame: subtracting GPU P95 from total P95 does not yield CPU duration. These summaries cannot exclude GPU/fence problems or prove a CPU bottleneck without corresponding per-frame evidence.
Why Can’t I See Jank in Screen Recordings?
Recording and direct observation can differ, but recording 90 fps content at 60 fps does not guarantee that jank becomes invisible:
- Different sampling cadence: A 90 Hz display refreshes about every 11.11 ms, while 60 fps video samples about every 16.67 ms. Short stalls can be missed or transformed; longer stalls can remain visible. Sampling can also introduce uneven pacing.
- A recording is not optical scanout: Android MediaProjection, for example, outputs through a virtual display and Surface. It does not capture the full physical-panel scanout. Recording/encoding can also alter workload; compare recording-on and recording-off behavior.
- Playback changes observation: Actual video timestamps, variable frame rate, player drops/repeats, and display refresh rate all matter. My observation that the Mi device switched to 60 during playback lacks the recording and mode logs; it is not a universal phone behavior.
Keep the original video and frame timestamps, aligning them with the same capture’s trace, display mode, and input timeline. An external high-speed camera is closer to observing the physical screen, but exposure, sampling rate, rolling shutter, and clock alignment still matter. A smooth-looking recording alone does not establish absence of jank.
Series Articles
- Systrace Smoothness in Action 1: Understanding Jank Principles
- Systrace Smoothness in Action 2: Case Analysis - MIUI Launcher Scroll Jank Analysis
- Systrace Smoothness in Action 3: FAQs During Jank Analysis
Attachments
Attachments are on GitHub: https://github.com/Gracker/SystraceForBlog/tree/master/Android_Systrace_Smooth_In_Action
xiaomi_launcher.zip: Main analysis trace.xiaomi_launcher_scroll_all_the_time.zip: Continuous scrolling trace.oppo_launcher_scroll.zip: Comparison trace.
On 2026-10-14, the three ZIPs were downloadable, passed CRC checks, and contained readable ftrace. Embedded viewer rules were inspected as text without executing its JavaScript. New timings come from historical attachments, not rerun device/recording/PerfLock experiments. The gfxinfo images lack full per-frame source output, limiting further comparison.
About Me && Blog
Below is my personal intro and related links. I look forward to exchanging ideas with fellow professionals. “When three walk together, one can always be my teacher!”
- Blogger Intro: Includes personal WeChat and WeChat group links.
- Blog Content Navigation: A guide for my blog content.
- Curated Excellent Blog Articles - Android Performance Optimization Must-Knows: Welcome to recommend projects/articles.
- Android Performance Optimization Knowledge Planet: The paid community stopped operating in 2026 and no longer accepts new members; this link preserves its history.
One walks faster alone, but a group walks further together.
