Technical review · 2026-10-13. The original 2019 images, thread listing, and author observations are retained. Technical checks use AOSP
android-9.0.0_r1andandroid-16.0.0_r1, with current entry points including API 31 frame timing and DataStore 1.2.1. The screenshots are not measurements of current app releases; no original-device or complete-trace reproduction was performed.
In the Overview of Jank and Frame Drops in Android - System Layer article, we listed causes of jank originating from the system. In this article, we focus on causes stemming from the App itself. When you encounter lag, before blaming the phone manufacturer, consider if it’s the App’s own inefficiency.
OEM and app teams may use Matrix or their own monitoring. System access can reveal more evidence, but coverage still depends on build, capture configuration, and platform support.
Average FPS alone does not describe smoothness. Two periods averaging 55 FPS can have different frame intervals and pauses. Consider intended cadence, actual presentation, and input response rather than using a change from 60 to 55 as a diagnosis. This four-part series includes:
- 0. Overview of Jank and Frame Drops in Android - Methodology
- 1. Overview of Jank and Frame Drops in Android - System Layer
- 2. Overview of Jank and Frame Drops in Android - Application Layer
- 3. Overview of Jank and Frame Drops in Android - Low Memory
Application-Side Performance Issues
Below are app cases I collected in 2019, shown in the Systrace images from then, mainly covering View/HWUI rendering. App names identify historical examples, not claims about current releases. Locate the critical frame and inspect thread states and dependencies before attributing a long slice entirely to app code.
The Systrace series explains the old views. Prefer Perfetto for current captures. On Android 12 (API 31) and later, where supported and captured, FrameTimeline relates deadlines, frame production, and presentation. Colors and average FPS are not substitutes for that evidence.
1. App Main Thread Execution Overhead
Input, animation, measure, layout, draw, and image decoding on the main thread can delay subsequent frames. Long intervals can contain computation, Runnable scheduling delay, locks, I/O, or synchronization waits. Separate elapsed duration from Running time before choosing an optimization. Not every frame executes every phase.
Measure / Layout Overhead
The image shows measure/layout within long traversal intervals. Hierarchy complexity, repeated requestLayout, and measurement logic are candidates, but first check whether the thread was running. The image alone does not prove an excessively deep hierarchy. Inspect measurement counts, callstacks, and update scope to identify unnecessary whole-tree work.

Draw Overhead
With hardware acceleration, main-thread Draw commonly includes recording or updating drawing commands, not complete GPU rendering. The first image has long Record View#draw work. The second selects about 17.746 ms; this is neither GPU time nor a universal diagnosis based on a fixed 16 ms line. Inspect custom drawing, repeated recording, resource access, and waits.


Animation Callback Overhead
Business logic, allocations, or layout updates inside animation callbacks can add main-thread work. The image also shows Background concurrent copying GC overlapping animation/traversal. The whole concurrent-GC interval is not a main-thread pause, and overlap does not establish the callback as the sole cause. Inspect actual pauses, allocation pressure, and callstacks.

View Initialization Overhead (PlayStore Example)
In the historical PlayStore example, the 13.196 ms selection contains animation and multiple inflate slices. Initialization occurs on the critical path, but the selection is not one inflate call’s CPU time. Check repeated creation and opportunities for deferred work; construction that depends on View/UI-thread rules cannot simply move to an arbitrary worker thread.

List Item Initialization Overhead (WeChat Example)
The 91.972 ms interval includes input, obtainView, inflate, and layout/setupListItem. Inspect item reuse, data/image work during binding, and synchronous access rather than attributing the whole interval to inflation. This is a legacy list path; the image does not identify RecyclerView onBindViewHolder. Analyze the component actually in use.

decodeBitmap Overhead
The main thread repeatedly enters decodeBitmap. Decoding intervals may also include reads, allocation, and waits, so inspect thread state. Read/decode on an appropriate worker where possible, size images for their display use, and deliver results on the UI thread. Account for lifecycle, cancellation, and cache capacity so background work does not merely become resource contention.

2. uploadBitmap Overhead
Decoding a Bitmap into pixels and making those pixels available as a GPU texture are different stages. Large images, repeatedly modified content, or cache eviction can add upload work. Drawing the same Bitmap does not necessarily upload it again, and not every backend exposes a slice named uploadBitmap.
Inspect upload size/count, newly generated pixels, RenderThread execution, allocation, and synchronization waits. Reduce unnecessary pixels and repeated uploads, and reuse stable content with bounded caching. This fills the original explanatory gap; it does not introduce a new measured example.
3. BuildDrawingCache Overhead
Repeated buildDrawingCache/SW Layer work identifies the software-cache path, not necessarily explicit app calls on every frame or new Bitmap allocation each time. Content invalidation or a software layer can trigger it. Identify the invalidation source and size changes.
Android 9 View.java can reuse a matching-size Bitmap. The old drawing-cache API was deprecated in API 28 and is not a general modern acceleration strategy. The dynamic-sticker image is also a historical example, not a statement about current WeChat.


4. Software Rendering vs. Hardware Rendering
Disabling hardware acceleration for a View window changes rasterization and may add software drawing on the main thread, but does not almost always cause lag. Skia has both software and GPU backends; its use does not identify CPU-only drawing. System composition may still use the GPU/HWC afterward.
The image does not expose the configuration. Check window/View/Canvas acceleration state and API support. A local compatibility need may not require disabling acceleration for the whole Activity. See Hardware Layer details.

5. Main Thread Binder Latency
A synchronous Binder call waits for a reply; time can be spent in the receiver queue, locked work, nested calls, or scheduling waits. The long activityResume interval does not show the receiver stack or lock owner, so it alone cannot establish an AMS lock held by background work. Activity/task services and locking also vary by version.
Inspect redundant remote queries on critical frames where caching or asynchronous work is appropriate, and follow the matching transaction on the system side. Framework lifecycle calls required on the main thread cannot simply be moved elsewhere.

6. Uneven Game Content Drawing (SurfaceView)
Mismatch between game-engine frame production and SurfaceFlinger consumption/presentation can cause repeats or latency; actual buffer drops require queue and presentation evidence. The image shows different phases for GLThread onDrawFrame and SurfaceView updates. Its red box alone does not prove that the game computes too slowly.
Games may intentionally target fewer FPS than the display refresh rate. Inspect CPU, GPU, submission timing, and frame pacing. OpenGL/Vulkan games can use Android Frame Pacing (Swappy) to help schedule presentation, but it does not remove an over-budget workload.

7. WebView Inefficiency
JavaScript, style/layout, painting, images, network work, or host interaction can become bottlenecks in a WebView. This does not make WebView inherently slower than native UI. The image shows only partial Chrome_IOThread and glide-animation activity, without a page profile.
Record the WebView provider/version and use Chrome DevTools for WebViews in a development build (available since Android 4.4 / API 19). Inspect page tasks and rendering, then align them with the system trace. A Chrome thread name alone does not identify the responsible JavaScript.

8. Refresh Rate Mismatch
FPS describes content updates; Hz describes display refreshes. At a fixed 90 Hz, 60 FPS content can require uneven repetition unless a compatible cadence is selected. By contrast, 60 FPS at 120 Hz and 30 FPS at 90 Hz can hold each frame for two and three refreshes respectively. Lower FPS still affects motion detail, and an integer ratio does not imply zero input latency.
The old annotation below says “one frame every three Vsyncs at 90 Hz,” which corresponds to 30 FPS. It illustrates an integer match, not evidence of a 60 FPS / 90 Hz mismatch. Inspect actual presentation intervals and display mode rather than just nominal numbers.

9. Incompatibility with High Refresh Rates
Refresh intervals at 60, 90, and 120 Hz are about 16.67, 11.11, and 8.33 ms. Higher refresh rates generally demand a shorter critical path, but UI, RenderThread, GPU, and composition work are pipelined with distinct deadlines. A refresh interval is not the full budget for any one thread, and 14 ms does not guarantee meeting 60 Hz.
This historical image again shows buildDrawingCache/SW Layer work, not a controlled 60/90 Hz comparison of one app. Compare expected/actual frames, stage timing, and frame counts with workload and system conditions held comparable.

10. Main Thread I/O Operations
Database queries, file operations, serialization, or waiting for worker I/O can delay the main thread. Identify the call and wait site. Put storage work on an appropriate asynchronous path without immediately blocking the UI for its result, and combine unnecessary repeated writes.
SharedPreferences.Editor.commit() synchronously waits for persistence and returns success/failure. apply(), introduced in API 9, updates memory and schedules disk work asynchronously without a write-success result. It does not guarantee a nonblocking main thread: framework lifecycle transitions may wait for pending writes, and a later commit can wait for outstanding asynchronous work. Android 16 SharedPreferencesImpl registers a finisher from apply, while QueuedWork.waitToFinish processes/waits for that work. Initial reads can also wait for loading.
Replacing commit with apply mechanically is therefore insufficient. Where persistence completion or error reporting matters, handle commit and its result off the UI thread. For new settings storage, consider the asynchronous APIs in DataStore 1.2.1, preserving data semantics during migration and avoiding blocking waits on the main thread.
11. WebView/Main Thread Interaction
A WebView can involve host UI, renderer, compositor, and GPU-related threads/processes, depending on its provider and version. The first image identifies sandboxed_process0; the WeChat images separately show toolsmp and the main process. These are not one shared main-thread track.
Synchronous waits, heavy callback work, or message round trips can delay a page, but correlate the same request, callback, and rendering work. Scheduling gaps do not automatically identify a JS-bridge fault, and green host-UI frame markers do not guarantee fresh web content. Combine page profiling, scheduling, and host callstacks.




12. RenderThread Overhead
A long RenderThread DrawFrame interval can include tree preparation, texture work, driver submission, buffer/fence waits, or scheduling delay. It is not automatically excessive GPU commands or GPU execution time. Nor does the UI thread wait for GPU completion on every frame.
Both Android 9 DrawFrameTask and Android 16 DrawFrameTask use the syncFrameState result to decide whether to unblock the UI before drawing or release it later. The pictured syncFrameState/prepareTree work is an investigation lead. Identify exactly what the UI is waiting for and what RenderThread is doing.



13. Same-Named RenderThreads and Synchronization
Thread names can repeat. The HWUI RenderThread::getInstance paths in AOSP Android 9 and Android 16 each maintain one process-local instance. Multiple threads named RenderThread do not alone prove multiple HWUI instances or that the UI waits for all of them.
The screenshot shows PID 4439, while the following ps output is for PID 10300; these are not established as the same recording. Retain the old output as an example of duplicate thread names, then identify ownership through PID/TID, callstacks, creation source, and wait relationships.

Original capture command (historical PID; not executed in this review; grep matches substrings):
1 | adb shell ps -AT | grep 10300 | grep RenderThread |
1 | u0_a170 10300 16228 6709 2693260 305172 SyS_epoll_wait 0 S RenderThread |
The S states and epoll/futex wait sites are snapshots, not proof of deadlock, continuous load, or what the UI is waiting for.
Summary
These were my observations about app versions and the market in 2019.
Android is an evolving platform, but its openness means app quality varies wildly. Unlike the tightly controlled App Store, Android apps come from many sources with varying levels of quality control.
Large companies cared about performance too, but the growing number of features could still hurt it. At the time, I complained about WeChat’s memory use and felt that the newer QQ was less smooth than its earlier versions.
Then there were the tricks for keeping processes alive, waking other apps, hot updates, and background tasks. I could understand why app developers wanted them. From the system side, though, we wanted foreground and background apps to behave well so the app the user was actually using stayed responsive. I hoped app developers would pay attention to that experience too.
When we find app-specific performance flaws that persist across different hardware, we often reach out to the developers to collaborate on a fix.
For more details on what OEMs do, see: What do phone manufacturers actually do when they say they are optimizing Android?
This series includes:
- 0. Overview of Jank and Frame Drops in Android - Methodology
- 1. Overview of Jank and Frame Drops in Android - System Layer
- 2. Overview of Jank and Frame Drops in Android - Application Layer
- 3. Overview of Jank and Frame Drops in Android - Low Memory
Zhihu Article
Original Chinese article and discussion
About Me && Blog
For my introduction and related articles:
- About me
- Blog navigation
- Android performance optimization: skills and tools
- Android performance community: The paid community stopped operating in 2026 and no longer accepts new members; this link preserves its history.
