A React Native live sports-scores screen stutters on Android as scores stream in; how do you use an Android Studio system trace to find which thread is at fault?
answer
- profileable build, real device
- Capture System Activities task
- frame boundaries in the trace
- mqt_v_js vs UI thread vs RenderThread
- long DrawFrame means GPU work
basics
~20 sCapture a system trace from a profileable build on a real device, then compare threads against frame boundaries: the JavaScript thread (mqt_v_js) running across frames means a JavaScript problem; the UI thread or RenderThread overrunning means native view or GPU work.
solid answer
~50 sI run the release variant as profileable, put the device on the scores screen, start the Android Studio Profiler's **Capture System Activities** task, let a few score updates arrive, and stop. In the trace, or in Perfetto after exporting it, I find my process and three threads: the UI thread named after the package, the JavaScript thread (`mqt_v_js` in the New Architecture runtime) and `RenderThread`. Then I read them against the frame boundaries. If `mqt_v_js` is busy across boundaries while the others idle, the problem is JavaScript and I move to a JavaScript profile to find the function or component. If the UI thread or `RenderThread` overruns — long `DrawFrame` slices — it is native: too much GPU work per frame, or views being created mid-interaction. The trace tells me where to look next; it doesn't name the component.
go deeper
Know that Android traces come from the Android Studio Profiler and that they show the JavaScript thread, the UI thread and RenderThread side by side.
Explain how to capture from a profileable build and how to read slices against frame boundaries to tell JavaScript overruns from native ones.
Diagnose from the trace: separate JavaScript, GPU and view-creation patterns, pick the next tool for each, and confirm the fix with a second release-like trace.
Decide when native traces become part of performance reviews, which low-end reference devices the team captures on, and how findings feed frame budgets.
## What a system trace is for A **system trace** records what every thread in the process did, slice by slice, on one shared clock. For a React Native screen that stutters, it answers the first question of any jank investigation: **is the time going into JavaScript or into native rendering?** Every later step depends on that answer, and no JavaScript-only tool can give it. At 60 Hz each frame has about **16.7 ms**. A frame is dropped when the work it needs has not finished by the next frame boundary. ## Capturing the trace The React Native profiling guide's procedure, adapted to the scores screen: 1. **Use a physical device** that shows the stutter, connected over USB. 2. **Run the app as profileable** from Android Studio (open the project's `android` folder), with the **release** variant selected. Profileable lets the profiler attach with little overhead and without a debuggable build's costs; the release variant keeps JavaScript out of dev mode. 3. **Navigate to the scores screen**, then start the **Capture System Activities** task in the Profiler pane. 4. **Let several score updates arrive** while you scroll — reproduce the exact stutter — then press *Stop recording*. 5. **Inspect** in Android Studio, or select the recording under *Past Recordings*, choose *Export recording*, and open it in **Perfetto**. The standalone `systrace` tool is gone from Android platform-tools; this is its replacement. ## Finding the right threads | Thread | How it is named | What runs there | |---|---|---| | UI (main) thread | your package name, or UI thread | Android measure, layout and draw, `Choreographer` frame callbacks, mounting native views | | JavaScript thread | `mqt_v_js` in the New Architecture runtime | Hermes executing your React code | | Native modules thread | `mqt_v_native` | native module work queued off the JavaScript thread | | RenderThread | `RenderThread` | the GPU commands for each frame (`DrawFrame`, `queueBuffer`) | Older guides and screenshots show `mqt_js` and `mqt_native_modules`; those are the legacy architecture's names. React Native 0.87's runtime names its queues `mqt_v_js` and `mqt_v_native`. Kernel name limits can truncate the package name, and a thread may show as `<...>` — identify it by the slices it runs. ## Reading it against frame boundaries Turn on frame or VSync highlighting so each frame period is visible, then look at the frames where the stutter happened: - **JavaScript is the bottleneck** when `mqt_v_js` is busy almost continuously and its slices cross frame boundaries while the UI thread waits. On a scores screen this is typical when every update re-renders the whole list or reformats every row. - **Native rendering is the bottleneck** when the UI thread or `RenderThread` has slices crossing boundaries. Long `DrawFrame` time means the GPU is still draining the previous frame's work. - **New views mid-interaction** look like a burst of JavaScript work followed by an expensive traversal on the UI thread — new rows or panels being created and mounted while the user scrolls. ## A worked reading of the scores screen Suppose each score push arrives about once a second and the stutter lasts a few frames each time. In the trace: 1. **Zoom to one push.** Find the first frame that runs late after the update arrives. 2. **Check `mqt_v_js` first.** If a single long slice starts with the update and runs through three or four frame periods, JavaScript owns those dropped frames. 3. **Then check the UI thread and `RenderThread`.** If they finish their own work early in each frame and then wait, they are victims, not causes. 4. **Repeat on a second push.** One slow frame can be noise; the same pattern on every push is the bug. ## What to do with each verdict - **JavaScript:** the trace shows engine frames, not your functions. Record a Hermes sampling profile of the same interaction to find the hot function, and a React commit profile to find the component that renders too much; then re-measure in release. - **GPU work:** the guide suggests `renderToHardwareTextureAndroid` for complex, static content that is being animated, and making sure `needsOffscreenAlphaCompositing` is not enabled — it is off by default and greatly increases per-frame GPU load. - **View creation:** postpone creating new UI until after the interaction, or simplify what is created. - **Unexplained native CPU:** *Find CPU Hotspots (Java/Kotlin Method Recording)* shows which native methods run, but it is slow, so read proportions, not durations. ## Common mistakes - Capturing from a debug build, where dev-mode JavaScript makes `mqt_v_js` look guilty of everything. - Looking for your component names in the trace; they are not there. - Blaming the UI thread because it is busy, without checking whether its work actually crosses a frame boundary. - Recording minutes of activity instead of the few seconds around the stutter, which buries the frames that matter.
- Your trace shows long DrawFrame slices on RenderThread while scores animate in React Native. What do you change first?Reduce per-frame GPU work. Check that no animated view has `needsOffscreenAlphaCompositing` enabled — it is off by default and costly. For complex content that moves but does not change, try `renderToHardwareTextureAndroid` while it animates and turn it off afterwards, since it costs memory. Then capture another trace to confirm frames fit.
- The trace shows mqt_v_js saturated during each score update. Why isn't the trace enough to fix it?A system trace sees Hermes engine frames, not your JavaScript functions or React components. It proves the JavaScript thread is the bottleneck; a Hermes sampling profile shows which function is hot, and a React commit profile shows which component renders too much. Fix, then re-measure in a release-like build.
- Why do some React Native guides show a JavaScript thread called mqt_js while your trace shows mqt_v_js?The names come from React Native's message-queue threads. The legacy bridge set up `mqt_js` and `mqt_native_modules`; the New Architecture runtime, the only one since 0.82, creates `mqt_v_js` and `mqt_v_native`. Older docs and screenshots predate that.
saying these in an interview costs you the question
- Capture the system trace from a debug build for convenience
- The trace shows which React component rendered slowly
- A busy UI thread always means the native side is at fault
- needsOffscreenAlphaCompositing is on by default and must be disabled
- Look for the JavaScript thread under the name mqt_js in 0.87