CN

Android Systrace Responsiveness in Action 3 - Extended Knowledge on Responsiv...

Word count: 3.5kReading time: 21 min
2021/09/13
loading

Jank, slow response, and ANRs can share causes or occur together, but they are not three severity levels of the same problem. When a user says an app feels slow, first distinguish uneven animation, a long delay after an action, and a system-detected failure to respond.

Animation and scrolling stutter is usually called jank. Long startup, screen wake-up, or navigation delays are slow response / high latency. An ANR depends on a specific system timeout condition. Frame-drop rate, startup duration, and ANR rate measure different things.

This is the third article in the Responsiveness series, focusing on extended knowledge when using Systrace to analyze app responsiveness, including startup speed testing, log interpretation, state analysis, and third-party startup libraries.

Versions and historical context (reviewed 2026-10-14): The 2021 figures and chapter structure are retained. Traceview is deprecated; the text uses Android Studio CPU Profiler terminology and corrects the sampling unit. Thread/component examples are checked against AOSP Android 10/11, command units against Android 16, and dependency/lazy initialization behavior against App Startup 1.2.0. Figures from different tools are not one experiment; corrected text takes precedence over old state, component, and readiness annotations.

Table of Contents

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).

Systrace Series Articles:

  1. Introduction to Systrace
  2. Systrace Basics - Prerequisites for Systrace
  3. Systrace Basics - Why 60 fps?
  4. Android Systrace Basics - SystemServer Explained
  5. Systrace Basics - SurfaceFlinger Explained
  6. Systrace Basics - Input Explained
  7. Systrace Basics - Vsync Explained
  8. Systrace Basics - Vsync-App: Detailed Explanation of Choreographer-Based Rendering Mechanism
  9. Systrace Basics - MainThread and RenderThread Explained
  10. Systrace Basics - Binder and Lock Contention Explained
  11. Systrace Basics - Triple Buffer Explained
  12. Systrace Basics - CPU Info Explained
  13. Systrace Smoothness in Action 1: Understanding Jank Principles
  14. Systrace Smoothness in Action 2: Case Analysis - MIUI Launcher Scroll Jank Analysis
  15. Systrace Smoothness in Action 3: FAQs During Jank Analysis
  16. Systrace Responsiveness in Action 1: Understanding Responsiveness Principles
  17. Systrace Responsiveness in Action 2: Responsiveness Analysis - Using App Startup as an Example
  18. Systrace Responsiveness in Action 3: Extended Knowledge on Responsiveness
  19. Systrace Thread CPU State Analysis Tips - Runnable
  20. Systrace Thread CPU State Analysis Tips - Running
  21. Systrace Thread CPU State Analysis Tips - Sleep and Uninterruptible Sleep

1. Interpretation of the Three Process States in Systrace

These are thread scheduling states, not one state for an entire process. A main thread can wait while a worker runs. Older Systrace viewers commonly show Running in green, Runnable in blue, and Sleep as blank space. Colors depend on the viewer; function-slice colors do not identify scheduling state.

Select the thread-state track and inspect its label and scheduling events. Other states, including uninterruptible waits, also exist. Blank or missing data is not automatically Sleep.

1.1 Analyzing the Sleep State

Sleep means the thread is neither running nor waiting to be scheduled on a CPU. It does not by itself distinguish “active/passive” waiting or establish that the wait is harmless. Common causes include Looper polling, synchronous Binder replies, locks/conditions, and explicit sleeps. nativePollOnce waiting with no due message can be normal, but a missing startup task, delayed message, or synchronization barrier may still need investigation.

The figure contains an IWindowSession.remove Binder call. Correlate its interval with thread-state intervals. Synchronous calls can wait for the remote side; oneway calls have different semantics. Call count alone does not establish a bottleneck: inspect accumulated critical-path waits and remote work.

Binder Sleep Trace

Select binder transaction to follow request and reply flows:

Binder Transaction Detail

The selected transaction has a 34.216 ms wall duration, not necessarily 34.216 ms of Sleep or remote CPU work. This figure’s outgoing flow is 29928→1515 and incoming return is 1515→29928. Its bottom annotation mistakenly calls the outgoing flow “incoming.” Verify transaction identity and receiving thread rather than treating any incoming flow as a reply.

Wakeup information helps continue the dependency investigation:

Wakeup Info Trace

Waking Thread Detail

Here, wakeup from tid: 1515 identifies the observed waker, not automatically a lock owner, complete wait object, or root cause. Inspect the preceding stack, remote work, and subsequent scheduling. A method named invokeSuspend also does not mean that its OS thread slept throughout the method slice.

1.2 Analyzing the Running State

Running means the thread is scheduled on a CPU. If an interval grows, compare workload and inputs before examining CPU capacity, frequency, cache/memory behavior, and context switches. A function’s wall interval may include multiple Running and waiting intervals; it is not the same as CPU time.

Running State Trace

Core Scheduling Map

The selected execution is on CPU 0; the second image shows a 5.112 ms selection. Neither establishes the CPU topology or numeric frequency. It therefore cannot prove “the wrong little core” or insufficient frequency. Foreground UI/RenderThread work is not guaranteed to stay on big cores: scheduling also depends on energy, thermal limits, affinity, and available capacity.

1.3 Analyzing the Runnable State

A sleeping thread becomes eligible to run when woken, then executes when scheduled. A preempted Running thread can return to Runnable as well. Runnable does not guarantee immediate execution, and very short intervals may be hard to see at the current zoom level.

State Transition Diagram

This older figure primarily illustrates Java thread states, not the Linux scheduler state machine. Java Thread.State RUNNABLE can include waiting for CPU time; RUNNING and READY are not separate enum constants. VM BLOCKED is also different from a kernel uninterruptible wait. A waiter notified by Object.notify must reacquire the monitor; notification does not mean it immediately enters Running.

Runnable Transition Trace

For long Runnable intervals, examine allowed CPUs, priority, cpuset/affinity, competing work, and available capacity. System-wide saturation is one possible cause. Restricted placement or higher-priority work on eligible CPUs can also delay a thread while other CPUs are idle.

2. Using TraceView for Responsiveness Analysis

The historical heading says TraceView, but the screenshot is Android Studio CPU Profiler. Standalone Traceview is deprecated; for Android Studio 3.2 and later, the official guide recommends CPU Profiler. Distinguish method sampling/instrumentation, system tracing, and the viewer used to inspect them.

AS CPU Profiler

2.1 Capturing TraceView During Startup

This is an ART Java/Kotlin method-sampling diagnostic, not a general native-stack capture or end-to-end startup benchmark. Check the device’s am start --help, target debugging permissions, and writable output path first. --sampling is in microseconds: 1000 microseconds equals 1 ms. The original --sampling 1 did not mean one sample per millisecond. See the AOSP Android 16 command help.

1
2
3
4
5
6
7
# Example package/component: replace both with your installed debug target.
# -S force-stops the target before launch; --sampling is in microseconds.
adb shell am start -S -n com.aboback.wanandroidjetpack/.splash.SplashActivity \
--start-profiler /data/local/tmp/startup-methods.trace --sampling 1000
# Stop after the agreed startup scenario has completed.
adb shell am profile stop com.aboback.wanandroidjetpack
adb pull /data/local/tmp/startup-methods.trace .

-S stops the target package to avoid reusing its process; it does not clear file caches/app data or reproduce the Launcher touch path. Resolve launch failures, permission errors, or Activity reuse before interpreting results. An empty trace is not a fast startup. Stop after the agreed scenario completes, rather than assuming ten seconds means fully loaded. Import steps and format compatibility depend on your Studio version.

2.2 Interpreting TraceView

After importing the method trace, confirm process/thread, selected interval, sampling versus instrumentation mode, and timing basis. Green regions in these older figures include bundled third-party libraries, not only the app author’s code. Interpret colors using method names and the viewer’s legend.

In Top Down/Bottom Up views, distinguish inclusive time from self time; do not add parent and child totals twice. Sampling can miss short calls, so its timeline and counts are not a complete invocation log.

Application.onCreate
Application onCreate TraceView

Activity.onCreate
Activity onCreate TraceView

doFrame
doFrame TraceView

WebView Initialization
WebView Init TraceView

This WebView figure includes Tencent TBS/X5 classes. It illustrates that sample’s third-party implementation, not a universal system WebView initialization stack.

2.3 Pros and Cons

Method sampling has statistical error; instrumentation records more method boundaries but can introduce greater runtime overhead. They are different capture modes, not one “TraceView high-frequency sampling” mode. A requested short interval does not guarantee samples arrive at that rate, especially for a one-microsecond request.

Use profiles to find hotspots and call relationships, and compare them with explicit timing definitions, sample counts, and overhead controls. Recheck final startup latency with lower-overhead system tracing/benchmarks; a profiling-perturbed method duration is not automatically the user’s real delay.

3. Using SimplePerf for Startup Speed Analysis

simpleperf can sample Java/Kotlin and native call stacks, subject to Android version, permissions, symbols, and unwind information. The official guide documents interpreted/JIT/AOT Java support from Android 9 and profileable release-app support from Android 10. It does not promise complete stacks from any production APK.

Run from your installed NDK/simpleperf scripts directory and check that version’s --help. This example follows the official application profiling guide, specifying the Activity and recording parameters:

1
2
3
python3 app_profiler.py -p com.aboback.wanandroidjetpack -a .splash.SplashActivity \
-r "-e task-clock:u -f 1000 -g --duration 10"
python3 report_html.py

The current script’s -a path attempts to stop an existing process before starting recording and the Activity. Keep the script/NDK version and logs and confirm the resulting startup state. With only -p, ensure the target is not running, wait for the recorder to be ready, then launch manually. Finish recording; do not kill the app just to end the capture. Process discovery/attachment has timing limits and need not cover every earliest instruction. The original historical log below explicitly uses a ten-second recording duration:

1
2
3
4
5
6
$ python app_profiler.py -p com.aboback.wanandroidjetpack
INFO:root:prepare profiling
INFO:root:start profiling1
INFO:root:run adb cmd: ['adb', 'shell', '/data/local/tmp/simpleperf', 'record', '-o', '/data/local/tmp/perf.data', '-e task-clock:u -f 1000 -g --duration 10', '--log', 'info', '--app', 'com.aboback.wanandroidjetpack']
simpleperf I environment.cpp:601] Waiting for process of app com.aboback.wanandroidjetpack
simpleperf I environment.cpp:593] Got process 32112 for package com.aboback.wanandroidjetpack

After a successful capture, report_html.py reads perf.data to generate the report. Keep matching unstripped native libraries, binary_cache, and any required obfuscation mapping. Unknown stacks do not mean no cost.

SimplePerf Report Example

Flamegraph width represents sampled event weight; horizontal position is not startup chronology. The example’s task-clock:u covers user-space on-CPU work, not a direct explanation of Binder/lock waits or kernel cost. Check device support before using off-CPU features, or combine profiles with a system trace. Distinguish missing samples, lost samples, and missing symbols.

Further documentation:

  1. simpleperf README
  2. Android Application Profiling
  3. Android Platform Profiling
  4. Executable Commands Reference
  5. Scripts Reference

4. Locating Other Component Startups in Systrace

4.1 Service Startup

This excerpt is from AOSP android-11.0.0_r1 ActivityThread, with debug logging omitted. It is framework source, not code to paste directly into an app.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public final void scheduleCreateService(IBinder token,
ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
updateProcessState(processState, false);
CreateServiceData s = new CreateServiceData();
s.token = token;
s.info = info;
s.compatInfo = compatInfo;
sendMessage(H.CREATE_SERVICE, s);
}

public final void scheduleBindService(IBinder token, Intent intent,
boolean rebind, int processState) {
updateProcessState(processState, false);
BindServiceData s = new BindServiceData();
s.token = token;
s.intent = intent;
s.rebind = rebind;
sendMessage(H.BIND_SERVICE, s);
}

The system schedules Service creation/binding through ApplicationThread, which posts work to H on the app main thread. A returned startService call does not mean Service.onCreate has run; system scheduling or process creation may still intervene. MessageQueue considers due times, synchronization barriers, and async status, not just submission order.

Service Message Trace

These callbacks follow a doFrame in this sample; this is not a guarantee that Services run after the first frame, nor proof that the frame has been physically presented. The image’s “register receiver” annotation also needs correction: broadcastReceiveComp identifies component broadcast delivery, not receiver registration.

Service Process Trace

4.2 Custom Messages

This is a Handler callback interval with an AndroidX LiveData class in its label. Inspect the Handler/Runnable and nested calls before attributing all such work to custom app logic.

Custom Message Trace

4.3 Service Startup Visualized

The label is LoadedApk$ServiceDispatcher$RunConnection: client-side connection-state dispatch, which can invoke ServiceConnection.onServiceConnected and related callbacks. It is not Service.onCreate itself. See Android 11 LoadedApk; locate creation/binding/argument delivery in the service process as well.

Service Trace Marker

4.4 BroadcastReceiver Execution

broadcastReceiveComp is the execution interval for delivery to a manifest component. Handler message numbers alone are not stable cross-version identifiers.

Receiver Trace Marker

The next image shows registration called from Activity.onStart. The registration call’s thread and a later onReceive callback’s thread are separate matters. Dynamic receivers normally receive callbacks on the main thread; overloads with a scheduler Handler can change delivery. Registration does not automatically choose its calling thread for future callbacks. Match registration lifetime with unregistration.

Receiver Registration Trace

4.5 ContentProvider Startup Timing

In the normal Android 11 process-binding path, initial providers for that process are installed after Application creation/attachment and before Application.onCreate. Every Activity launch does not initialize every provider in the APK, and each query does not rerun onCreate. The publishContentProviders marker reports installed providers to the system; that Binder interval is not the total provider initialization cost.

Provider Init Trace 1

Provider Init Trace 2

5. Can AppStartup Optimize Startup Speed?

Third-Party Library Initialization

Some libraries quietly initialize themselves even though you never called their initialization method in Application.onCreate. A common way to do that is through a manifest ContentProvider. This depends on process assignment, enabled state, and process binding; it does not mean every provider declared anywhere in the APK is initialized on every launch.

SDK behavior changes across versions. For example, WorkManager uses App Startup from 2.6; instructions for removing its old standalone provider cannot be applied unchanged to newer versions. Inspect current dependencies and the final merged manifest rather than inferring behavior from an SDK name.

Provider Init Flow

This figure summarizes normal process-initialization ordering. Separate automatic initializers can add startup work and obscure dependencies. Developers are not categorically unable to control them: disabling, replacing, or deferring initialization depends on each SDK’s documented contract.

AppStartup Library

App Startup lets participating Initializers share InitializationProvider and declare prerequisites through dependencies(). It does not automatically convert arbitrary SDK providers or promise a fixed global order among unrelated initializers. AppInitializer in 1.2.0 recursively initializes dependencies, caches results, and detects cycles. Automatic initialization normally runs on the main thread; a manual call does not automatically move work to a worker thread.

The original “50+ providers” threshold and “less than 20 ms for 20–30 libraries” lack reproducible device/version/baseline and repeated-sample evidence. They are not general performance limits. Gains depend on critical-path work removed or deferred; merging entry points does not erase SDK initialization work.

To defer an existing Initializer, remove its automatic-discovery metadata within the app manifest’s <application>. Declare xmlns:tools="http://schemas.android.com/tools" on the root manifest element:

1
2
3
4
5
6
7
8
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data android:name="com.example.ExampleLoggerInitializer"
tools:node="remove" />
</provider>

Removing this metadata alone does not defer the initializer if another automatic initializer still depends on it. Inspect the dependency graph, then initialize before first use on the thread required by the SDK:

1
2
AppInitializer.getInstance(context)
.initializeComponent(ExampleLoggerInitializer.class);

ExampleLoggerInitializer is a placeholder for your existing Initializer implementation; import androidx.startup.AppInitializer. Do not remove metadata without providing initialization, or delete the shared provider merely to defer one component. The app/SDK must still handle asynchronous completion and multiple processes.

Summary

  1. Consolidate participating components under one entry point with explicit dependencies.
  2. Move nonessential initialization off the startup critical path, checking TTID, TTFD, and first-use latency for shifted cost.
  3. Ordering does not automatically reduce work or make it asynchronous; measure repeatedly under matched conditions.

Action Items for App Developers:

  1. Inspect the target build’s merged manifest/final APK for providers, processes, and initializer metadata, not just source-manifest counts.
  2. Record initialization threads, wall/CPU time, and dependencies to identify the critical path.
  3. Check SDK requirements: what can be deferred, who triggers it, and how readiness is guaranteed before use.
  4. Verify cold launch, background entry points, multiple processes, and first use after changes. Keep failed/missing samples distinct and compare matching compilation/data conditions.

6. Using IdleHandler in App Startup Scenarios

IdleHandler runs on the Looper thread owning its MessageQueue. In AOSP Android 10 MessageQueue.next, idle handlers can run when the queue is empty or its head message is not yet due. All future messages need not have completed. An already-due synchronization barrier also affects this condition.

IdleHandler Timing in Systrace

The original “all messages completed” annotation is too strong. Network work, background tasks, GPU work, or the next draw may remain unfinished while the main queue is momentarily idle. A continuously busy queue can also postpone an idle handler indefinitely.

  1. Defer short, nonessential work. Registering a main-queue IdleHandler in Activity.onCreate does not guarantee execution after the first frame or on a background thread. A long callback delays subsequent input/frame work. Returning false from queueIdle removes it; true retains it for future idle opportunities, not a separate loop or guaranteed timer. Remove handlers that are no longer needed when their owner’s lifecycle ends.

IdleHandler Lazy Load Example

  1. Report full display from a business readiness condition. Activity.reportFullyDrawn() is available from API 19 to report critical content/usability; IdleHandler is not required. The old example below waits for a data-loaded signal and then one idle opportunity. Neither data arrival nor an idle queue proves the list and images have appeared.

reportFullyDrawn Code Example

The Android M (API 23) guard relates to using Looper.getQueue(), not the introduction of reportFullyDrawn. Define readiness for success, empty data, and error states, and avoid duplicate registration, destroyed Activities, or stale request callbacks reporting readiness. AndroidX readiness-reporting helpers still need the same explicit business conditions.

reportFullyDrawn Trace Marker

The overview image’s onWindowFocusChange should be onWindowFocusChanged. Focus does not establish initial display or content readiness, and am start -W does not measure the same event as the focus callback. Accounting across Splash→Main depends on the actual launch sequence and OS implementation. Retain separate initial/full-display definitions using the official TTID/TTFD guide.

Accurate Startup Metrics

This historical output shows SplashActivity Displayed +968 ms, MainActivity Displayed +475 ms, and Fully drawn +3s221ms; am start -W reports TotalTime 968 and WaitTime 975. These fields have different boundaries and Activity associations. Do not add/subtract them to derive a stage, or assume +3s221ms begins with physical contact on the Launcher icon. The screenshot command also sleeps five seconds before reading an existing logcat buffer, which can include older records. Correlate logs by time, package/Activity, and the specific launch.

Correct TTFD reporting complements TTID; it does not make TTID an incorrect measurement. Missing reports, data failures, and incomplete captures are distinct outcomes, not zeros.

Series Articles

  1. Systrace Responsiveness in Action 1: Understanding Responsiveness Principles
  2. Systrace Responsiveness in Action 2: Responsiveness Analysis - Using App Startup as an Example
  3. Systrace Responsiveness in Action 3: Extended Knowledge on Responsiveness
  4. Link to Systrace Basics Series

References

  1. Analysis of Android App Startup Flow
  2. Investigation: Can App Startup Really Reduce Startup Time?
  3. Jetpack App Startup Explained
  4. App Startup Official Guide
  5. Complete Record of Android App Startup Optimization
  6. Android Application Profiling

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!”

  1. Blogger Intro: Includes personal WeChat and WeChat group links.
  2. Blog Content Navigation: A guide for my blog content.
  3. Curated Excellent Blog Articles - Android Performance Optimization Must-Knows: Welcome to recommend projects/articles.
  4. 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.

Scan WeChat QR Code

CATALOG
  1. 1. Table of Contents
  • 1. Interpretation of the Three Process States in Systrace
    1. 1. 1.1 Analyzing the Sleep State
    2. 2. 1.2 Analyzing the Running State
    3. 3. 1.3 Analyzing the Runnable State
  • 2. Using TraceView for Responsiveness Analysis
    1. 1. 2.1 Capturing TraceView During Startup
    2. 2. 2.2 Interpreting TraceView
    3. 3. 2.3 Pros and Cons
  • 3. Using SimplePerf for Startup Speed Analysis
  • 4. Locating Other Component Startups in Systrace
    1. 1. 4.1 Service Startup
    2. 2. 4.2 Custom Messages
    3. 3. 4.3 Service Startup Visualized
    4. 4. 4.4 BroadcastReceiver Execution
    5. 5. 4.5 ContentProvider Startup Timing
  • 5. Can AppStartup Optimize Startup Speed?
    1. 0.0.1. Third-Party Library Initialization
    2. 0.0.2. AppStartup Library
  • 0.1. Summary
    1. 0.1.1. Action Items for App Developers:
  • 6. Using IdleHandler in App Startup Scenarios
  • Series Articles
  • References
  • About Me && Blog