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 and help everyone approach these articles with the right questions. 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): First published in 2021, this article retains its historical Systrace screenshots and attachments. Framework snippets are checked against AOSP android-10.0.0_r1, not the complete vendor implementations shown in the images. 60 Hz / 16.67 ms is an example; frame budgets and buffer paths depend on the system version and captured evidence. Prefer Perfetto for new captures and use FrameTimeline on Android 12 (API 31) or later when available; missing data cannot establish modern frame metrics.
Table of Contents
- Understanding Jank Principles
- System Operation Mechanism Overview
- 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).
Understanding Jank Principles
Jank Phenomena and Impact
This article focuses on visual pacing during continuous animation and scrolling. fps (frames per second) counts frames over an interval; specify whether these were generated, submitted, or presented. If the intended cadence is 120 new presented frames per second and only 110 appear, the target throughput was not sustained. Average fps alone does not locate late frames, describe the distribution of pauses, or establish perceptibility. Evenly paced 60 fps differs from an average of 60 fps with irregular intervals.
Dropped frames have many causes: app-specific issues, system-driven jank, hardware bottlenecks, or overall system lag. Refer to these four articles:
- Overview of Jank Causes in Android - Methodology
- Overview of Jank Causes in Android - System Side
- Overview of Jank Causes in Android - App Side
- Overview of Jank Causes in Android - Low Memory Side
Jank is what users notice first:
- Occasional minor jank degrades user experience, like a stutter when scrolling Weibo or returning to the desktop.
- Severe system-wide jank makes the phone unusable.
- In the high-refresh-rate era, if users are accustomed to 120 fps and the system suddenly switches to 60 fps in an easily perceptible scenario, they will immediately notice and feel it as jank.
Therefore, both apps and systems should aim to avoid jank. Identified jank issues should be prioritized for resolution.
Jank Definition
Overall App Frame Rendering Flow
To understand why jank occurs, we must know how the app’s main thread processes a frame.
From the Perspective of Execution Order
For a hardware-accelerated View/HWUI window, follow the Choreographer callback through the UI thread, RenderThread, GPU, SurfaceFlinger/HWC, and finally presentation. CPU submission, GPU completion, composition completion, and presentation are distinct events. Pipeline stages can overlap across frames; the whole path need not fit inside the figure’s single 16.6 ms interval.

The “GPU” label inside the RenderThread box is misleading: RenderThread runs on the CPU and prepares/submits graphics commands; the GPU executes asynchronously. “Issue” means issuing commands, and swapping a buffer proves neither GPU completion nor presentation. Measure and layout run when required.
From the Perspective of Systrace
The flow is more intuitive when visualized in Systrace (Perfetto):

This historical OPPO Launcher trace illustrates the pipeline. Arrow order alone does not establish a causal chain for one frame: check the window/layer, buffer identity, and fences. A queue-count increase does not prove GPU completion, and an HWC call returning does not prove scanout. Follow actual dependencies across the main thread, RenderThread, GPU, SystemServer, SurfaceFlinger, and scheduling states.
Jank Definition
Here, jank means visual progress that misses the intended cadence during an ongoing update, producing late, stalled, or uneven presentation. A missing draw is only a possible intermediate symptom. Static pages, intentional frame-rate limits, and lower-rate content need not produce a new buffer on every display refresh.
The UI-thread gap below is a point to investigate. First establish that an update was expected, then follow the relevant window through submission and presentation. Queued frames, RenderThread animations, or SurfaceFlinger layer-property animations can decouple a missing main-thread draw from visible motion. The image’s Jank arrow alone is not proof.

Collect evidence at three levels; these are not equivalent detection rules:
- Visual output: Repeated content is a clue when a new image was expected on each refresh. Repeating each frame of 30 fps video twice on a 60 Hz display can be correct. Separate average fps, display refreshes, and content changes.
- SurfaceFlinger/display stack: Check the target layer’s content, acquire fence, composition deadline, and presentation. A layer without a new buffer can reuse its previous buffer or change properties. SurfaceFlinger may also sleep when nothing needs updating.
- Application: Compare frame start, UI work, RenderThread submission, and GPU completion against that frame’s plan. Missing
queueBufferin one Vsync period is insufficient evidence; timely queueing does not ensure timely GPU completion or presentation.
Long main-thread tasks are a common starting point, not a predetermined diagnosis. Short CPU slices can coexist with runnable delays, lock/Binder waits, GPU delays, or display-stack problems.
The original term Logical Jank describes content or animation that fails to advance despite continued buffer submissions. It is not a universal FrameTimeline category. Exclude static content, video cadence, and intentional repetition before investigating application state and animation timing; identical screenshots alone do not identify the root cause.
Android 12 and Later: Use FrameTimeline
When a Perfetto trace contains android.surfaceflinger.frametimeline, correlate Expected/Actual Timeline by window/layer and token. Check predicted versus actual presentation, jank_type, and on_time_finish. The app Actual endpoint accounts for GPU completion and posting, not just CPU time in doFrame. Buffer Stuffing can preserve smooth motion while increasing input latency. Coverage and fields depend on the system and capture; an absent track is not zero jank. See the Perfetto FrameTimeline documentation.
With legacy Systrace, connect thread, queue, and available fence evidence, explicitly leaving unknown presentation times unresolved. Diagnose ANRs separately using the official guide. An asynchronous broadcast, for example, may await PendingResult.finish() even while the main thread is idle.
System Operation Mechanism Overview
Because there are many causes for jank, analyzing it requires an understanding of Android’s system mechanisms. Here is a brief introduction:
- App Main Thread Operation Principles
- Message, Handler, MessageQueue, and Looper Mechanisms
- Screen Refresh Mechanism and Vsync
- Choreographer Mechanism
- Buffer Flow and TripleBuffer
- Input Flow
System Mechanism - App Main Thread Operation Principles
A normal app process is forked from Zygote and enters ActivityThread.main after runtime initialization. The following excerpts are checked against AOSP android-10.0.0_r1; omission comments represent unshown framework code, not a standalone app example. prepareMainLooper() creates the Looper and MessageQueue. ActivityThread’s instance-field initialization creates its Handler; prepare does not create all three.
1 | // frameworks/base/core/java/android/app/ActivityThread.java |
After initialization, the Looper dispatches eligible Messages serially to their target Handlers. Application, Activity, Service, and ordinary BroadcastReceiver callbacks generally run on the main thread, but not every component method does. ContentProvider.onCreate runs there; remote data operations typically use Binder threads and must support concurrency. Broadcast delivery can also use a specified Handler. Below are selected H constants and the BIND_APPLICATION branch only.
1 | // frameworks/base/core/java/android/app/ActivityThread.java |
For more, see Android Systrace Basics - MainThread and RenderThread Explained.
System Mechanism - Message Mechanism
Looper.loop dispatches currently eligible Messages and waits when none can run; it need not sleep immediately on entry. Delayed messages may not yet be due, and synchronization barriers may block synchronous messages. Input and display events also arrive through native Looper file-descriptor callbacks, so a wakeup need not correspond to an ordinary Java Message.
The core of the Android Message mechanism consists of: Handler, Looper, MessageQueue, and Message.

Many code-level analyses exist, so here is a brief overview of the components:
- Handler: Sends Messages/Runnables to a particular Looper’s thread; constructing one creates no thread. Implicit-current-Looper constructors are deprecated and can throw when no Looper exists. Explicitly choose a target, for example
new Handler(Looper.getMainLooper()). - Looper: Dispatches an eligible Message through its target’s
dispatchMessage(), with separate paths for a Runnable, Handler.Callback, andhandleMessage(). Traditional APM can observe dispatch boundaries throughsetMessageLogging(). That wall-clock duration can include waits and does not cover queue latency, GPU work, or presentation; logging also has overhead. - MessageQueue: This Android 10 implementation manages due times, barriers, and asynchronous messages.
nativePollOncewaits through the native Looper until an event, wakeup, or timeout. Waiting does not require an empty queue, and enqueueing does not guarantee immediate execution. An asynchronous Message still runs on the same Looper thread. - Message: Carries fields such as
what,arg1,arg2,obj, or a Runnable callback; its target Handler determines dispatch.
See the Handler API and ContentProvider threading contract. Check internal framework structures against the relevant OS version.
System Mechanism - Screen Refresh and Vsync
First, understand Screen Refresh Rate. A hardware concept, it refers to how often the screen hardware refreshes the image. E.g., a 60Hz rate means the screen refreshes its display 60 times per second; 90Hz means 90 times.
FPS counts frames at a stated stage: generation, submission, or presentation. A 60 Hz display can show 30 fps content; an app can also produce frames that never reach the screen. Android 11’s multiple-refresh-rate support lets display modes, policy, and content requirements influence the chosen rate. App fps and display refresh rate need not match.
Vsync (Vertical Synchronization) provides timing references for display updates and the graphics pipeline. It coordinates submission and presentation but does not guarantee enough application frames or eliminate jank by itself.
| Target cadence | Interval between target frames |
|---|---|
| 60 fps | About 16.67 ms |
| 90 fps | About 11.11 ms |
| 120 fps | About 8.33 ms |
These are periods, not universal per-thread timeout thresholds. Available budgets depend on frame start, scheduling predictions, pipeline depth, GPU work, and composition deadlines. CPU slices shorter than 16.67 ms do not by themselves prove on-time presentation.
Further reading:
System Mechanism - Choreographer
Choreographer coordinates application frame callbacks. When animation or traversal work requires a frame, it requests timing and runs due callbacks in phases. Static UI need not redraw on every physical refresh. Do not hardcode 16.6 ms: 60 Hz is only an example. The supplied frame time supports animation timing and is not a guarantee of callback execution or final presentation time.
It connects both sides of the pipeline:
- Callbacks: Android 10 includes Input, Animation, Insets Animation, Traversal, and Commit, while the figure shows only the main phases. Not all input waits for the Input phase, and measure/layout need not run every frame. Late-execution logs are clues, not final on-screen jank counts.
- Display timing: FrameDisplayEventReceiver requests/receives Vsync and schedules an asynchronous
doFramemessage on its Looper. This can bypass synchronization barriers, but cannot preempt a running main-thread task. The public Choreographer.postFrameCallback API (API 16 onward) runs its callback once; ongoing animation must request subsequent callbacks according to its lifecycle.
The following image shows Choreographer‘s frame drawing workflow via the Message mechanism when Vsync arrives. Details: Detailed Explanation of Android Rendering Mechanism Based on Choreographer.

System Mechanism - Buffer Flow and TripleBuffer
BufferQueue connects graphics-buffer producers and consumers, passing ownership, metadata, and synchronization information rather than copying the whole image on every queue operation. The endpoints can live in different processes or in one process. Start with the legacy app-Surface-to-SurfaceFlinger queue model below.

In the legacy window-Surface path, the app produces buffers and SurfaceFlinger consumes their graphical content:
dequeueBuffer()obtains a writable slot/buffer, rather than freeing it. Respect the returned synchronization fence before safely reusing its memory.- The app produces content through CPU or GPU work and calls
queueBuffer()with a readiness fence. GPU execution may still be in progress; queue completion does not mean all pixels are ready to read. - The consumer uses
acquireBuffer()to obtain the content and fence, then respects the synchronization dependency. SurfaceFlinger selects suitable content for its schedule; it need not acquire a new image on every Vsync. - Once the consumer no longer needs an old buffer, release and its fence communicate reuse conditions. A CPU-side release returning does not guarantee that display/GPU access has finished.
See BufferQueue and the synchronization framework. An app can have several surfaces/queues; capacity and actual allocations depend on the path and endpoint configuration, not a universal three-buffers-per-app rule.
Modern path: In AOSP android-16.0.0_r1, client-side BLASTBufferQueue acquires a buffer and passes it with its fence to SurfaceFlinger through a SurfaceControl Transaction’s setBuffer. The legacy diagram’s queue-consumer location is not universal for modern windows.
Comparison of double vs. triple buffering:

An additional buffer can increase overlap between production and consumption:
- Less waiting for a free buffer: If one buffer is being consumed and another produced, a spare may let production continue. Dequeue waits often occur on RenderThread or another producer thread and can indirectly block the main thread.
- Absorbing short variations: Queued content that is ready in time can sustain some display updates. The diagram’s reduction from two repeats to one applies only to its illustrated timing, not a universal benefit. It still shows the first repeated frame.
- Limits: More buffers do not shorten GPU execution or fix sustained production/composition overruns. App and SurfaceFlinger Vsync can have different phases. The app graphics stack submits GPU commands; BufferQueue is not a job queue waiting for SurfaceFlinger to start the GPU.
Costs include memory and potentially greater queueing latency. A deeper queue can delay the image reflecting the latest input, so evaluate responsiveness as well as smoothness. Details: Android Systrace Basics - Triple Buffer Explained.
System Mechanism - Input Flow
Input is one common event source. The historical image places the native InputReader and InputDispatcher threads in system_server; verify thread locations and trace labels for the particular build. A long press is generally recognized by gesture logic using elapsed time and events, not read from EventHub as a distinct hardware “long press.”
- InputReader reads device events from EventHub and interprets/transforms them for dispatch.
- InputDispatcher selects targets based on focus, touch targeting, and policy, then publishes to the relevant window/monitor InputChannel. This is not a broadcast to every app.
- OutboundQueue contains events awaiting publication for one Connection, not necessarily all input for an app.
- WaitQueue contains published events awaiting completion acknowledgements.
handled=falsecan also complete an event; queue length is not duration. - PendingInputEventQueue is ViewRootImpl’s app-side queue. Android 10 decrements its count before
deliverInputEvent, so zero does not mean processing or visual feedback is complete. - deliverInputEvent is an asynchronous trace interval for app input-stage delivery, ending at finishInputEvent. It is not the thread wakeup time, and its duration need not be entirely CPU execution.
- The legacy viewer’s Input Response / Rendering Response interaction groupings do not directly measure physical touch-to-light latency.
- Handling input can request a visual update. Fling after release depends on velocity, boundaries, and gesture logic; UP does not guarantee fling. An input completion ACK also does not prove presentation.
Systrace representation:

This image is from Meizu Launcher; earlier images include OPPO Launcher and a Matrix sample. They cannot be joined into one device/capture’s causal chain. Green F markers are legacy-viewer frame analysis, not independent evidence of GPU completion or correct final presentation.
Details: Android Systrace Basics - Input Explained.
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: Systrace for launcher scroll jank (main analysis case).xiaomi_launcher_scroll_all_the_time.zip: Systrace for continuous launcher scrolling.oppo_launcher_scroll.zip: Comparison trace.
On 2026-10-14, all three ZIPs were downloadable, passed CRC checks, and contained legacy Systrace HTML and ftrace data. These are historical captures, not a current-device reproduction. The interactions were not rerun, and the embedded viewer’s compatibility with current browsers was not tested. Use a tool that supports the format; FrameTimeline information absent from these captures cannot be reconstructed as if it had been recorded.
Source references, all android-10.0.0_r1: ActivityThread, Looper, MessageQueue, Choreographer, and ViewRootImpl.
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.
