Table of Contents
Understanding Profiling in Android
Profiling is the process of measuring how your app uses CPU, memory, battery, and other resources while it runs. In Android development, you use profiling tools to find performance problems that are hard to see from code inspection alone. Instead of guessing why an app is slow or uses too much memory, a profiler shows you what is actually happening while the app runs on a device or emulator.
A typical profiling workflow starts by running the app, reproducing the slow or problematic behavior, and then capturing measurements during that period. After that, you inspect graphs and timelines, drill down into details, and identify places where your code can be improved. Profiling should be done on a real or realistic environment, because behavior in a profiler can be different from a trivial test run.
Always profile with a realistic build type and on realistic hardware. Debug-only behavior and slow emulators can hide or exaggerate performance problems.
In this chapter, you will focus on the main Android profiling tools, how to open them, and what each is best suited for, without going deep into general optimization techniques that are covered elsewhere.
Android Studio Profiler Overview
Android Studio includes a unified Profiler window that combines several profilers in a single timeline. You can open it by selecting View > Tool Windows > Profiler, or by clicking the Profiler icon at the bottom of Android Studio.
When you choose a connected device or emulator and select an app process, Android Studio Profiler shows a timeline with several tracks. The common tracks are CPU, memory, network, and energy. Each track is a graph over time that responds to your actions in the app. For example, if you scroll a list, the CPU usage may spike. If you load images from the internet, the network track shows activity.
The Profiler lets you start and stop detailed recordings for specific profilers such as CPU or memory. High level graphs run continuously, but detailed traces are captured in sessions. You can save these sessions and examine them later, which is useful when you want to compare different runs after a code change.
Profiling in Android Studio works best when the app is running in a debuggable build. Release builds can be profiled as well, but some extra information such as method names may be less detailed unless you adjust build and ProGuard or R8 settings.
CPU Profiler
The CPU Profiler helps you understand where your app spends time when it runs. It shows CPU usage over time, and it can record traces that show method calls, thread activity, and how long operations take. This is essential when you want to find slow frames, janky scrolling, or expensive operations happening on the main thread.
To use it, select your app process in the Profiler window and click on the CPU track. When you start a recording, you can choose between different trace configurations such as sample based or instrumented profiling. Sample based traces record call stacks at fixed intervals, which is less intrusive but less precise. Instrumented traces record entry and exit for methods, which is more precise but can affect app performance while profiling.
Once you have captured a trace, you see a timeline with threads and a list of methods. You can switch between a top down view, which starts from high level calls and drills into child methods, and a bottom up view, which groups by methods and shows who called them. This way you can identify which methods take the most time overall, and which paths lead to expensive operations.
An important use of CPU Profiler is to check the main thread. If you see long running work on the main thread, that can cause visible lag. You can click on a spike in CPU usage, filter by thread, and see exactly which methods run during that spike.
Never guess where your CPU bottleneck is. Use CPU Profiler to identify actual hotspots before making performance changes.
Memory Profiler
The Memory Profiler is focused on how your app allocates and uses memory. It shows a graph of memory usage over time and allows you to capture heap dumps and allocation records. This is especially useful when you suspect memory leaks or when the app uses more memory than expected.
In the Profiler window, click the memory track to open Memory Profiler. The main view shows heap size and allocations. You can trigger garbage collection and take a heap dump. A heap dump is a snapshot of all objects currently in memory, grouped by type and reference paths. With it, you can see which objects are using the most memory and why they are being kept alive.
Memory Profiler can also record allocations over a period of time. During an allocation recording, you can see which classes are being allocated frequently and where in your code those allocations originate. This can reveal situations where you allocate objects in tight loops or during every frame of an animation.
When you inspect objects in a heap dump, you can follow reference chains to see why an object is not collected. If you find a long chain that includes activities or fragments that should have been destroyed, you may have a leak caused by static references, listeners, or other incorrect ownership patterns.
Use heap dumps and allocation tracking when you suspect leaks or out of memory issues. Large dumps can be slow to capture, so repeat them only when necessary.
Network Profiler
The Network Profiler helps you see network requests that your app makes over HTTP or HTTPS. It presents a timeline of network activity and a list of individual requests, along with details such as URL, request and response headers, payload size, and timing information.
To open it, click on the network track in the Profiler window. As you exercise your app by triggering API calls or image fetches, the network graph shows spikes and activity segments. Below the timeline, you see a table of calls with columns for method, status, size, and duration.
You can click on a specific request to inspect details, such as:
URL and HTTP method.
Start and end times and total duration.
Number of bytes sent and received.
Request and response headers and bodies when available.
This information helps you detect slow or repeated requests. For example, if the same resource is fetched multiple times in a short period, you may need better caching. If certain requests take too long, you can consider background work or improved server endpoints.
Network Profiler also helps you confirm that your app uses HTTPS correctly and that sensitive data is not sent in plain text. While full security analysis uses other tools and techniques, profiling is a quick way to inspect actual live traffic from your app.
Energy Profiler
The Energy Profiler focuses on how your app affects battery usage. It collects information related to CPU, network, GPS, wakelocks, and alarms, and presents it in a combined view. Excessive background work, high frequency alarms, or constant GPS usage can drain the battery, and the Energy Profiler helps you spot such patterns.
From the Profiler window, select the energy track to open Energy Profiler. The main timeline shows estimate of energy use over time, with colored indicators for different energy related resources. You can see when your app schedules jobs, acquires wakelocks, or triggers location updates.
Energy Profiler is especially useful when profiling background behavior. For example, if your app keeps the device awake after the user leaves it, or polls a server too often, the profiler timeline makes that obvious. You can then adjust your use of background work APIs or location updates.
Although energy estimates in the profiler are based on models, not on direct battery drain measurements, they are very valuable to compare different versions of your app. If after a change the energy graph becomes more active while user visible behavior has not improved, you may have introduced wasteful work.
System Tracing and Perfetto
System Tracing, based on Perfetto on modern Android versions, is a lower level profiling tool that gives a global view of activity on the device. It is not limited to your app, and it records system wide events such as CPU scheduling, graphics pipeline stages, disk I/O, and much more. You can open it from Android Studio through Tools > Perfetto or from the Profiler when you want deeper analysis.
Perfetto traces are highly detailed and can be recorded for a short period while you reproduce performance issues. The output is a timeline that you inspect in a dedicated viewer. Unlike high level profilers, it shows fine grained events with microsecond resolution. You can correlate when your app threads run, when frames are drawn, and when the system processes input events.
System Tracing is particularly useful for diagnosing jank or stutter that is not obvious from CPU or memory alone. It can reveal that the graphics pipeline is blocked, that the main thread is waiting on another thread, or that disk operations are delaying rendering.
Because Perfetto records system wide events, it can generate large traces and requires some practice to interpret. It is best used when simpler profiling has identified a problematic time range, and you now need detailed low level insight into what is happening inside that window.
Using `adb` for Performance Inspection
While Android Studio provides a rich graphical environment for profiling, some performance related inspections can be done from the command line with adb. This is useful for automated scripts, continuous integration environments, or quick checks on devices without full IDE access.
One common command is adb shell dumpsys, which provides detailed reports about various system services. For example, adb shell dumpsys meminfo <package_name> shows memory statistics for your app, such as heap usage, allocations, and per process breakdowns. You can run this before and after a certain user flow to see how memory changes.
Another example is adb shell top, which displays CPU usage for running processes. While less detailed than CPU Profiler, it is a fast way to see if your app is using too much CPU when idle or in the background.
You can also use adb shell gfxinfo <package_name> to get frame rendering statistics. This reports the time spent in different stages of rendering for each frame and can indicate whether your app consistently misses the target frame time. For a 60 Hz display the target frame time is about $16.67$ milliseconds per frame, and for 90 Hz it is about $11.11$ milliseconds per frame.
Use adb based tools when you need quick numbers, automated checks, or profiling on devices where Android Studio is not available. They complement, not replace, the graphical profilers.
Integrating Profiling into Your Workflow
Profiling should not be a one time activity at the end of development. Instead, include it regularly in your workflow so that performance problems are found early, when they are easier to address.
A practical approach is to define a few key user journeys, such as app startup, main screen interaction, data loading, and background sync. For each of these, use the Profiler tools to capture CPU, memory, network, and energy behavior. Save some of these sessions so that after code changes, you can compare new recordings with previous ones.
It is helpful to profile both debug and release like builds. Debug builds are easier to inspect, but release builds reflect closer what users will run. Some performance characteristics only show up with optimizations and code shrinking applied.
Remember that profiling itself can affect performance, especially with detailed traces or heap dumps. That is why you should capture short and focused sessions. Start recording just before reproducing an issue and stop as soon as enough data is collected. Then spend time analyzing, not recording.
By learning which profiling tool is best for each kind of problem, you can quickly move from a report such as "the app feels slow" to a specific trace that shows "this method runs too often on the main thread" or "this API call is repeated unnecessarily." Profiling tools are the bridge between vague symptoms and concrete changes in your code.